diff --git a/AsbCloudApp/Data/DataSaubStatDto.cs b/AsbCloudApp/Data/DataSaubStatDto.cs
index 639154df..a3b80a5d 100644
--- a/AsbCloudApp/Data/DataSaubStatDto.cs
+++ b/AsbCloudApp/Data/DataSaubStatDto.cs
@@ -2,7 +2,7 @@
namespace AsbCloudApp.Data
{
- public class DataSaubStatDto
+ public class DataSaubStatDto:IId
{
///
///
diff --git a/AsbCloudApp/Data/LimitingParameterDto.cs b/AsbCloudApp/Data/LimitingParameterDto.cs
index b4f43a09..19b182a5 100644
--- a/AsbCloudApp/Data/LimitingParameterDto.cs
+++ b/AsbCloudApp/Data/LimitingParameterDto.cs
@@ -8,6 +8,30 @@ namespace AsbCloudApp.Data
///
public class LimitingParameterDto
{
+ ///
+ /// Нет ограничения
+ ///
+ public const int NoLimit = 0;
+
+ ///
+ /// МСП
+ ///
+ public const int RopPlan = 1;
+
+ ///
+ /// Давление
+ ///
+ public const int Pressure = 2;
+
+ ///
+ /// Осевая нагрузка
+ ///
+ public const int AxialLoad = 3;
+
+ ///
+ /// Момент
+ ///
+ public const int RotorTorque = 4;
///
/// Идентификатор скважины
///
diff --git a/AsbCloudApp/Data/ProcessMaps/Report/ProcessMapReportDataSaubStatDto.cs b/AsbCloudApp/Data/ProcessMaps/Report/ProcessMapReportDataSaubStatDto.cs
new file mode 100644
index 00000000..6255726f
--- /dev/null
+++ b/AsbCloudApp/Data/ProcessMaps/Report/ProcessMapReportDataSaubStatDto.cs
@@ -0,0 +1,93 @@
+using System;
+
+namespace AsbCloudApp.Data.ProcessMaps.Report;
+
+///
+/// Модель РТК
+///
+public class ProcessMapReportDataSaubStatDto
+{
+ ///
+ /// Время, затраченное на бурение интервала, в часах
+ ///
+ public double DrilledTime { get; set; } = 0;
+
+ ///
+ /// Id секции скважины
+ ///
+ public int IdWellSectionType { get; set; }
+
+ ///
+ /// Название секции скважины
+ ///
+ public string WellSectionTypeName { get; set; } = null!;
+
+ ///
+ /// Глубина по стволу от, м
+ ///
+ /// на начало интервала
+ ///
+ ///
+ public double DepthStart { get; set; }
+
+ ///
+ /// Глубина по стволу до, м
+ ///
+ /// на конец интервала
+ ///
+ ///
+ public double DepthEnd { get; set; }
+
+ ///
+ /// Дата/ время
+ ///
+ /// на начало интервала
+ ///
+ ///
+ public DateTime DateStart { get; set; }
+
+ ///
+ /// Режим бурения (Ротор/слайд/ручной)
+ ///
+ public string DrillingMode { get; set; } = null!;
+
+ ///
+ /// Проходка, м
+ ///
+ public double? DeltaDepth { get; set; }
+
+ ///
+ /// Перепад давления, атм
+ ///
+ public ProcessMapReportDataSaubStatParamsDto PressureDiff { get; set; } = new();
+
+ ///
+ /// Нагрузка, т
+ ///
+ public ProcessMapReportDataSaubStatParamsDto AxialLoad { get; set; } = new();
+
+ ///
+ /// Момент на ВСП, кНхМ
+ ///
+ public ProcessMapReportDataSaubStatParamsDto TopDriveTorque { get; set; } = new();
+
+ ///
+ /// Ограничение скорости, м/ч
+ ///
+ public ProcessMapReportDataSaubStatParamsDto SpeedLimit { get; set; } = new();
+
+ ///
+ /// Обороты ВСП, об/мин
+ ///
+ public ProcessMapReportDataSaubStatParamsDto TopDriveSpeed { get; set; } = new();
+
+ ///
+ /// Расход, л/с
+ ///
+ public ProcessMapReportDataSaubStatParamsDto Flow { get; set; } = new();
+
+ ///
+ /// Механическая скорость, м/ч
+ ///
+ public PlanFactDto Rop { get; set; } = new();
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/ProcessMaps/Report/ProcessMapReportDataSaubStatParamsDto.cs b/AsbCloudApp/Data/ProcessMaps/Report/ProcessMapReportDataSaubStatParamsDto.cs
new file mode 100644
index 00000000..1ed34027
--- /dev/null
+++ b/AsbCloudApp/Data/ProcessMaps/Report/ProcessMapReportDataSaubStatParamsDto.cs
@@ -0,0 +1,37 @@
+namespace AsbCloudApp.Data.ProcessMaps.Report;
+
+///
+/// Параметры РТК
+///
+public class ProcessMapReportDataSaubStatParamsDto
+{
+ ///
+ /// Уставка план
+ ///
+ public double? SetpointPlan { get; set; }
+
+ ///
+ /// Уставка факт
+ ///
+ public double? SetpointFact { get; set; }
+
+ ///
+ /// Факт (средневзвешенное)
+ ///
+ public double? FactWavg { get; set; }
+
+ ///
+ /// Факт (максимум)
+ ///
+ public double? FactMax { get; set; }
+
+ ///
+ /// Ограничение
+ ///
+ public double? Limit { get; set; }
+
+ ///
+ /// Процент бурения по уставке, %
+ ///
+ public double? SetpointUsage { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Extensions/ChangeLogExtensions.cs b/AsbCloudApp/Extensions/ChangeLogExtensions.cs
new file mode 100644
index 00000000..83f28fbe
--- /dev/null
+++ b/AsbCloudApp/Extensions/ChangeLogExtensions.cs
@@ -0,0 +1,32 @@
+using AsbCloudApp.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace AsbCloudApp.Extensions
+{
+ ///
+ /// Расширения для поиска в истории
+ ///
+ public static class ChangeLogExtensions
+ {
+ ///
+ /// Действительные на момент времени значения
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable WhereActualAtMoment(this IEnumerable items, DateTimeOffset moment)
+ where T : ChangeLogAbstract
+ {
+ var actualItems = items
+ .Where(item => item.Creation <= moment)
+ .Where(item => item.Obsolete is null || item.Obsolete >= moment);
+
+ return actualItems;
+ }
+ }
+}
diff --git a/AsbCloudApp/Repositories/IDataSaubStatRepository.cs b/AsbCloudApp/Repositories/IDataSaubStatRepository.cs
index efab95b7..9dfa351c 100644
--- a/AsbCloudApp/Repositories/IDataSaubStatRepository.cs
+++ b/AsbCloudApp/Repositories/IDataSaubStatRepository.cs
@@ -1,4 +1,5 @@
using AsbCloudApp.Data;
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +11,16 @@ namespace AsbCloudApp.Repositories
///
public interface IDataSaubStatRepository
{
+ ///
+ /// Получение записей по ключу телеметрии
+ ///
+ /// ключ телеметрии
+ /// начальная дата
+ /// конечная дата
+ ///
+ ///
+ Task> GetAsync(int idTelemetry, DateTimeOffset geDate, DateTimeOffset leDate, CancellationToken token);
+
///
/// Получение последних по дате окончания бурения записей в разрезе телеметрий
///
diff --git a/AsbCloudApp/Requests/DataSaubStatRequest.cs b/AsbCloudApp/Requests/DataSaubStatRequest.cs
new file mode 100644
index 00000000..b4400f3e
--- /dev/null
+++ b/AsbCloudApp/Requests/DataSaubStatRequest.cs
@@ -0,0 +1,41 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace AsbCloudApp.Requests
+{
+ ///
+ /// Параметры запроса для построения отчёта
+ ///
+ public class DataSaubStatRequest
+ {
+ ///
+ /// Изменение уставки факт перепада давления от первого значения в начале интервала
+ /// Не менее 5 атм и не более 15(50) атм;
+ ///
+ [Range(5, 15, ErrorMessage = "Изменение уставки факт перепада давления не может быть меньше 5 и больше 15 атм")]
+ public double DeltaPressure { get; set; } = 5d;
+
+ ///
+ /// Изменение уставки факт осевой нагрузки от первого значения в начале интервала
+ /// Не менее 1 т и не более 5(20) т;
+ ///
+ [Range(1, 5, ErrorMessage = "Изменение уставки факт осевой нагрузки не может быть меньше 1 и больше 5 т")]
+ public double DeltaAxialLoad { get; set; } = 1d;
+
+ ///
+ /// Изменение уставки момента от первого значения в начале интервала
+ /// Не менее 5 кН*м и не более 10(20) кН*м.
+ ///
+ [Range(5, 10, ErrorMessage = "Изменение уставки момента не может быть меньше 5 и больше 10 кН*м")]
+ public double DeltaRotorTorque { get; set; } = 5d;
+
+ ///
+ /// Изменение ограничения нагрузки от первого значения в начале интервала
+ ///
+ public double DeltaAxialLoadSp => 1.0;
+
+ ///
+ /// Изменение ограничения момента от первого значения в начале интервала
+ ///
+ public double DeltaRotorTorqueSp => 5.0;
+ }
+}
diff --git a/AsbCloudApp/Services/ProcessMaps/WellDrilling/IProcessMapReportDataSaubStatService.cs b/AsbCloudApp/Services/ProcessMaps/WellDrilling/IProcessMapReportDataSaubStatService.cs
new file mode 100644
index 00000000..d234cd2f
--- /dev/null
+++ b/AsbCloudApp/Services/ProcessMaps/WellDrilling/IProcessMapReportDataSaubStatService.cs
@@ -0,0 +1,26 @@
+using AsbCloudApp.Data.ProcessMaps.Report;
+using AsbCloudApp.Requests;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudApp.Services.ProcessMaps.WellDrilling
+{
+ ///
+ /// Получить РТК-отчет по бурению
+ ///
+ public interface IProcessMapReportDataSaubStatService
+ {
+ ///
+ /// Получения строк РТК-отчёта
+ ///
+ /// ключ скважины
+ /// параметры запроса
+ ///
+ ///
+ Task> GetAsync(int idWell, DataSaubStatRequest request, CancellationToken token);
+ }
+}
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index f99aa054..5e1ea903 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -228,6 +228,8 @@ namespace AsbCloudInfrastructure
IChangeLogRepository,
ProcessMapPlanBaseRepository>();
+ services.AddTransient();
+
services.AddTransient();
services.AddTransient();
diff --git a/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs b/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs
index 92a228d8..23eba5f9 100644
--- a/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs
+++ b/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs
@@ -40,6 +40,23 @@ namespace AsbCloudInfrastructure.Repository
return result;
}
+ public async Task> GetAsync(int idTelemetry, DateTimeOffset geDate, DateTimeOffset leDate, CancellationToken token)
+ {
+ var timeSpan = TimeSpan.FromHours(telemetryService.GetTimezone(idTelemetry).Hours);
+ var geDateUtc = geDate.ToUniversalTime();
+ var leDateUtc = leDate.ToUniversalTime();
+
+ var stats = await db.Set()
+ .Where(s => s.IdTelemetry == idTelemetry)
+ .Where(s => s.DateStart >= geDateUtc)
+ .Where(s => s.DateEnd <= leDateUtc)
+ .ToArrayAsync(token);
+
+ var result = stats.Select(s => ConvertToDto(s, timeSpan));
+
+ return result;
+ }
+
public async Task InsertRangeAsync(IEnumerable dataSaubStats, CancellationToken token)
{
var entities = dataSaubStats.Select(data => ConvertToEntity(data));
diff --git a/AsbCloudInfrastructure/Services/LimitingParameterService.cs b/AsbCloudInfrastructure/Services/LimitingParameterService.cs
index 695321de..c4323597 100644
--- a/AsbCloudInfrastructure/Services/LimitingParameterService.cs
+++ b/AsbCloudInfrastructure/Services/LimitingParameterService.cs
@@ -17,11 +17,11 @@ namespace AsbCloudInfrastructure.Services
private readonly IWellService wellService;
private readonly Dictionary feedRegulatorData = new ()
{
- { 0, "Нет ограничения" },
- { 1, "МСП" },
- { 2, "Давление" },
- { 3, "Осевая нагрузка" },
- { 4, "Момент" }
+ { LimitingParameterDto.NoLimit, "Нет ограничения" },
+ { LimitingParameterDto.RopPlan, "МСП" },
+ { LimitingParameterDto.Pressure, "Давление" },
+ { LimitingParameterDto.AxialLoad, "Осевая нагрузка" },
+ { LimitingParameterDto.RotorTorque, "Момент" }
};
public LimitingParameterService(ILimitingParameterRepository limitingParameterRepository,
diff --git a/AsbCloudInfrastructure/Services/ProcessMaps/Report/ProcessMapReportDataSaubStatService.cs b/AsbCloudInfrastructure/Services/ProcessMaps/Report/ProcessMapReportDataSaubStatService.cs
new file mode 100644
index 00000000..9ad31d53
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/ProcessMaps/Report/ProcessMapReportDataSaubStatService.cs
@@ -0,0 +1,344 @@
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.ProcessMapPlan;
+using AsbCloudApp.Data.ProcessMaps.Report;
+using AsbCloudApp.Exceptions;
+using AsbCloudApp.Extensions;
+using AsbCloudApp.Repositories;
+using AsbCloudApp.Requests;
+using AsbCloudApp.Services;
+using AsbCloudApp.Services.ProcessMaps.WellDrilling;
+using AsbCloudDb.Model;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudInfrastructure.Services.ProcessMaps.Report;
+
+public class ProcessMapReportDataSaubStatService : IProcessMapReportDataSaubStatService
+{
+ private readonly IWellService wellService;
+ private readonly IChangeLogRepository processMapPlanBaseRepository;
+ private readonly IDataSaubStatRepository dataSaubStatRepository;
+ private readonly IWellOperationRepository wellOperationRepository;
+ private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
+
+ public ProcessMapReportDataSaubStatService(IWellService wellService,
+ IChangeLogRepository processMapPlanBaseRepository,
+ IDataSaubStatRepository dataSaubStatRepository,
+ IWellOperationRepository wellOperationRepository,
+ IWellOperationCategoryRepository wellOperationCategoryRepository
+ )
+ {
+ this.wellService = wellService;
+ this.processMapPlanBaseRepository = processMapPlanBaseRepository;
+ this.dataSaubStatRepository = dataSaubStatRepository;
+ this.wellOperationRepository = wellOperationRepository;
+ this.wellOperationCategoryRepository = wellOperationCategoryRepository;
+ }
+
+ public async Task> GetAsync(int idWell, DataSaubStatRequest request, CancellationToken token)
+ {
+ var well = await wellService.GetOrDefaultAsync(idWell, token)
+ ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина с Id: {idWell} не найдена");
+
+ if (!well.IdTelemetry.HasValue)
+ return Enumerable.Empty();
+
+ var requestProcessMapPlan = new ProcessMapPlanBaseRequestWithWell(idWell);
+ var processMapPlanWellDrillings = await processMapPlanBaseRepository.Get(requestProcessMapPlan, token);
+
+ if (!processMapPlanWellDrillings.Any())
+ return Enumerable.Empty();
+
+ var geDepth = processMapPlanWellDrillings.Min(p => p.DepthStart);
+ var leDepth = processMapPlanWellDrillings.Max(p => p.DepthEnd);
+
+ var requestWellOperationFact = new WellOperationRequest()
+ {
+ IdWell = idWell,
+ OperationType = WellOperation.IdOperationTypeFact,
+ GeDepth = geDepth,
+ LeDepth = leDepth
+ };
+ var wellOperations = await wellOperationRepository
+ .GetAsync(requestWellOperationFact, token);
+ if (!wellOperations.Any())
+ return Enumerable.Empty();
+
+ var geDate = wellOperations.Min(p => p.DateStart);
+ var leDate = wellOperations.Max(p => (p.DateStart.AddHours(p.DurationHours)));
+ var dataSaubStats =
+ (await dataSaubStatRepository.GetAsync(well.IdTelemetry.Value, geDate, leDate, token)).ToArray();
+
+ if (!dataSaubStats.Any())
+ return Enumerable.Empty();
+
+ var wellOperationCategories = wellOperationCategoryRepository.Get(false);
+ var wellSectionTypes = wellOperationRepository.GetSectionTypes();
+
+ var result = CalcByIntervals(
+ request,
+ processMapPlanWellDrillings,
+ dataSaubStats,
+ wellOperations,
+ wellOperationCategories,
+ wellSectionTypes);
+
+ return result;
+ }
+
+ private static IEnumerable CalcByIntervals(
+ DataSaubStatRequest request,
+ IEnumerable processMapPlanWellDrillings,
+ Span dataSaubStats,
+ IEnumerable wellOperations,
+ IEnumerable wellOperationCategories,
+ IEnumerable wellSectionTypes
+ )
+ {
+ var list = new List();
+ var firstElemInInterval = dataSaubStats[0];
+
+ int GetSection(DataSaubStatDto data)
+ => wellOperations.MinBy(o => data.DateStart - o.DateStart)!.IdWellSectionType;
+
+ ProcessMapPlanDrillingDto? GetProcessMapPlan(int idWellSectionType, DataSaubStatDto data)
+ => processMapPlanWellDrillings
+ .Where(p => p.IdWellSectionType == idWellSectionType)
+ .Where(p => p.DepthStart <= data.DepthStart)
+ .Where(p => p.DepthEnd >= data.DepthStart)
+ .Where(p => IsModeMatchOperationCategory(p.IdMode, data.IdCategory))
+ .WhereActualAtMoment(data.DateStart)
+ .FirstOrDefault();
+
+ var idWellSectionType = GetSection(firstElemInInterval);
+ var prevProcessMapPlan = GetProcessMapPlan(idWellSectionType, firstElemInInterval);
+ var indexStart = 0;
+
+ for (var i = 1; i < dataSaubStats.Length; i++)
+ {
+ var currentElem = dataSaubStats[i];
+ idWellSectionType = GetSection(currentElem);
+ var processMapPlan = GetProcessMapPlan(idWellSectionType, currentElem);
+
+ if (IsNewInterval(currentElem, firstElemInInterval, request) || i == dataSaubStats.Length - 1 || processMapPlan != prevProcessMapPlan)
+ {
+ prevProcessMapPlan = processMapPlan;
+ var length = i - indexStart;
+
+ var span = dataSaubStats.Slice(indexStart, length);
+
+ indexStart = i;
+ firstElemInInterval = currentElem;
+
+ var firstElemInSpan = span[0];
+ var lastElemInISpan = span[^1];
+
+ var wellOperationCategoryName = wellOperationCategories
+ .Where(c => c.Id == firstElemInSpan.IdCategory)
+ .FirstOrDefault()?.Name ?? string.Empty;
+
+ var wellSectionType = wellSectionTypes
+ .Where(c => c.Id == idWellSectionType)
+ .First();
+
+ var elem = CalcStat(processMapPlan, span, wellOperationCategoryName, wellSectionType);
+ if (elem is not null)
+ list.Add(elem);
+ }
+ }
+ return list;
+ }
+
+ private static bool IsModeMatchOperationCategory(int idMode, int idCategory)
+ {
+ return (idMode == 1 && idCategory == 5003) || (idMode == 2 && idCategory == 5002);
+ }
+
+ private static ProcessMapReportDataSaubStatDto? CalcStat(
+ ProcessMapPlanDrillingDto? processMapPlanFilteredByDepth,
+ Span span,
+ string wellOperationCategoryName,
+ WellSectionTypeDto wellSectionType
+ )
+ {
+ var firstElemInInterval = span[0];
+ var lastElemInInterval = span[^1];
+
+ var deltaDepth = lastElemInInterval.DepthEnd - firstElemInInterval.DepthStart;
+
+ var aggregatedValues = CalcAggregate(span);
+
+ var result = new ProcessMapReportDataSaubStatDto()
+ {
+ IdWellSectionType = wellSectionType.Id,
+ DateStart = firstElemInInterval.DateStart.DateTime,
+ WellSectionTypeName = wellSectionType.Caption,
+ DepthStart = firstElemInInterval.DepthStart,
+ DepthEnd = lastElemInInterval.DepthEnd,
+ DeltaDepth = deltaDepth,
+ DrilledTime = aggregatedValues.DrilledTime,
+ DrillingMode = wellOperationCategoryName,
+ PressureDiff = new ProcessMapReportDataSaubStatParamsDto()
+ {
+ SetpointPlan = processMapPlanFilteredByDepth?.DeltaPressurePlan,
+ SetpointFact = firstElemInInterval.PressureSp - firstElemInInterval.PressureIdle,
+ FactWavg = aggregatedValues.Pressure,
+ Limit = processMapPlanFilteredByDepth?.DeltaPressureLimitMax,
+ SetpointUsage = aggregatedValues.SetpointUsagePressure
+ },
+ AxialLoad = new ProcessMapReportDataSaubStatParamsDto()
+ {
+ SetpointPlan = processMapPlanFilteredByDepth?.AxialLoadPlan,
+ SetpointFact = aggregatedValues.AxialLoadSp,
+ FactWavg = aggregatedValues.AxialLoad,
+ Limit = processMapPlanFilteredByDepth?.AxialLoadLimitMax,
+ SetpointUsage = aggregatedValues.SetpointUsageAxialLoad
+ },
+ TopDriveTorque = new ProcessMapReportDataSaubStatParamsDto()
+ {
+ SetpointPlan = processMapPlanFilteredByDepth?.TopDriveTorquePlan,
+ SetpointFact = aggregatedValues.RotorTorqueSp,
+ FactWavg = aggregatedValues.RotorTorque,
+ FactMax = aggregatedValues.RotorTorqueMax,
+ Limit = processMapPlanFilteredByDepth?.TopDriveTorqueLimitMax,
+ SetpointUsage = aggregatedValues.SetpointUsageRotorTorque
+ },
+ SpeedLimit = new ProcessMapReportDataSaubStatParamsDto
+ {
+ SetpointPlan = processMapPlanFilteredByDepth?.RopPlan,
+ SetpointFact = aggregatedValues.BlockSpeedSp,
+ FactWavg = deltaDepth / aggregatedValues.DrilledTime,
+ SetpointUsage = aggregatedValues.SetpointUsageRopPlan
+ },
+ TopDriveSpeed = new ProcessMapReportDataSaubStatParamsDto
+ {
+ SetpointPlan = processMapPlanFilteredByDepth?.TopDriveSpeedPlan,
+ FactWavg = aggregatedValues.RotorSpeed,
+ FactMax = aggregatedValues.RotorSpeedMax
+ },
+ Flow = new ProcessMapReportDataSaubStatParamsDto
+ {
+ SetpointPlan = processMapPlanFilteredByDepth?.FlowPlan,
+ FactWavg = aggregatedValues.MaxFlow,
+ Limit = processMapPlanFilteredByDepth?.FlowLimitMax,
+ },
+ Rop = new PlanFactDto
+ {
+ Plan = processMapPlanFilteredByDepth?.RopPlan,
+ Fact = deltaDepth / aggregatedValues.DrilledTime
+ },
+ };
+ return result;
+ }
+
+ private static (
+ double Pressure,
+ double AxialLoadSp,
+ double AxialLoad,
+ double RotorTorqueSp,
+ double RotorTorque,
+ double RotorTorqueMax,
+ double BlockSpeedSp,
+ double RotorSpeed,
+ double RotorSpeedMax,
+ double MaxFlow,
+ double SetpointUsagePressure,
+ double SetpointUsageAxialLoad,
+ double SetpointUsageRotorTorque,
+ double SetpointUsageRopPlan,
+ double DrilledTime
+ ) CalcAggregate(Span span)
+ {
+ var sumPressure = 0.0;
+ var sumAxialLoadSp = 0.0;
+ var sumAxialLoad = 0.0;
+ var sumRotorTorqueSp = 0.0;
+ var sumRotorTorque = 0.0;
+ var sumBlockSpeedSp = 0.0;
+ var sumRotorSpeed = 0.0;
+ var maxFlow = 0.0;
+ var maxRotorTorque = 0.0;
+ var maxRotorSpeed = 0.0;
+ var sumDiffDepthByPressure = 0.0;
+ var sumDiffDepthByAxialLoad = 0.0;
+ var sumDiffDepthByRotorTorque = 0.0;
+ var sumDiffDepthByRopPlan = 0.0;
+
+ var diffDepthTotal = 0.0;
+ var drilledTime = 0.0;
+
+ for (var i = 0; i < span.Length; i++)
+ {
+ var diffDepth = span[i].DepthEnd - span[i].DepthStart;
+
+ sumPressure += diffDepth * (span[i].Pressure - (span[i].PressureIdle ?? 0.0));
+ sumAxialLoadSp += diffDepth * (span[i].AxialLoadSp ?? 0);
+ sumAxialLoad += diffDepth * span[i].AxialLoad;
+ sumRotorTorqueSp += diffDepth * (span[i].RotorTorqueSp ?? 0);
+ sumRotorTorque += diffDepth * span[i].RotorTorque;
+ sumBlockSpeedSp += diffDepth * (span[i].BlockSpeedSp ?? 0);
+ sumRotorSpeed += diffDepth * span[i].RotorSpeed;
+ maxFlow = span[i].Flow > maxFlow ? span[i].Flow : maxFlow;
+ maxRotorTorque = span[i].RotorTorque > maxRotorTorque ? span[i].RotorTorque : maxRotorTorque;
+ maxRotorSpeed = span[i].RotorSpeed > maxRotorSpeed ? span[i].RotorSpeed : maxRotorSpeed;
+
+ if (span[i].IdFeedRegulator == LimitingParameterDto.Pressure)
+ sumDiffDepthByPressure += diffDepth;
+ if (span[i].IdFeedRegulator == LimitingParameterDto.AxialLoad)
+ sumDiffDepthByAxialLoad += diffDepth;
+ if (span[i].IdFeedRegulator == LimitingParameterDto.RotorTorque)
+ sumDiffDepthByRotorTorque += diffDepth;
+ if (span[i].IdFeedRegulator == LimitingParameterDto.RopPlan)
+ sumDiffDepthByRopPlan += diffDepth;
+
+ diffDepthTotal += diffDepth;
+ drilledTime += (span[i].DateEnd - span[i].DateStart).TotalHours;
+ }
+ return (
+ Pressure: sumPressure / diffDepthTotal,
+ AxialLoadSp: sumAxialLoadSp / diffDepthTotal,
+ AxialLoad: sumAxialLoad / diffDepthTotal,
+ RotorTorqueSp: sumRotorTorqueSp / diffDepthTotal,
+ RotorTorque: sumRotorTorque / diffDepthTotal,
+ RotorTorqueMax: maxRotorTorque,
+ BlockSpeedSp: sumBlockSpeedSp / diffDepthTotal,
+ RotorSpeed: sumRotorSpeed / diffDepthTotal,
+ RotorSpeedMax: maxRotorSpeed,
+ MaxFlow: maxFlow,
+ SetpointUsagePressure: sumDiffDepthByPressure / diffDepthTotal,
+ SetpointUsageAxialLoad: sumDiffDepthByAxialLoad / diffDepthTotal,
+ SetpointUsageRotorTorque: sumDiffDepthByRotorTorque / diffDepthTotal,
+ SetpointUsageRopPlan: sumDiffDepthByRopPlan / diffDepthTotal,
+ DrilledTime: drilledTime
+ );
+ }
+
+ private static bool IsNewInterval(DataSaubStatDto currentElem, DataSaubStatDto firstElem, DataSaubStatRequest request)
+ {
+ static bool IsNewElemBySpeed(double currentSpeed, double firstSpeed)
+ {
+ //2. Изменение уставки скорости подачи от первого значения в начале интервала при условии:
+ //скорость > 80 м/ч => изменение уставки на ± 20 м/ч;
+ //скорость > 30 м/ч => изменение уставки на ± 15 м/ч;
+ //скорость <= 30 м/ч => изменение уставки на ± 5 м/ч;
+ if (firstSpeed > 80)
+ return Math.Abs(currentSpeed - firstSpeed) >= 20;
+ else if (firstSpeed > 30)
+ return Math.Abs(currentSpeed - firstSpeed) >= 15;
+ else
+ return Math.Abs(currentSpeed - firstSpeed) >= 5;
+ }
+
+ var isNewElem = (currentElem.IdCategory != firstElem.IdCategory)
+ || (Math.Abs(currentElem.Pressure - firstElem.Pressure) >= request.DeltaPressure)
+ || (Math.Abs(currentElem.AxialLoad - firstElem.AxialLoad) >= request.DeltaAxialLoad)
+ || (Math.Abs(currentElem.RotorTorque - firstElem.RotorTorque) >= request.DeltaRotorTorque)
+ || (Math.Abs((currentElem.AxialLoadSp ?? 0) - (firstElem.AxialLoadSp ?? 0)) >= request.DeltaAxialLoadSp)
+ || (Math.Abs((currentElem.RotorTorqueSp ?? 0) - (firstElem.RotorTorqueSp ?? 0)) >= request.DeltaRotorTorqueSp)
+ || (IsNewElemBySpeed(currentElem.Speed, firstElem.Speed));
+ return isNewElem;
+ }
+}
diff --git a/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs b/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs
index d9665e4d..52b2044d 100644
--- a/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs
+++ b/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs
@@ -275,7 +275,7 @@ public class DailyReportServiceTest
wellOperationRepositoryMock.GetSectionTypes()
.ReturnsForAnyArgs(new[] { fakeSectionType });
- detectedOperationServiceMock.GetAsync(Arg.Any(), Arg.Any())
+ detectedOperationServiceMock.GetAsync(Arg.Any(), Arg.Any())
.ReturnsForAnyArgs(fakeWellOperationSlipsTime);
subsystemServiceMock.GetStatAsync(Arg.Any(), Arg.Any())
diff --git a/AsbCloudWebApi.Tests/Services/ProcessMaps/ProcessMapReportDataSaubStatServiceTest.cs b/AsbCloudWebApi.Tests/Services/ProcessMaps/ProcessMapReportDataSaubStatServiceTest.cs
new file mode 100644
index 00000000..6054c135
--- /dev/null
+++ b/AsbCloudWebApi.Tests/Services/ProcessMaps/ProcessMapReportDataSaubStatServiceTest.cs
@@ -0,0 +1,292 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.ProcessMapPlan;
+using AsbCloudApp.Data.ProcessMaps;
+using AsbCloudApp.Repositories;
+using AsbCloudApp.Requests;
+using AsbCloudApp.Services;
+using AsbCloudDb.Model;
+using AsbCloudInfrastructure.Repository;
+using AsbCloudInfrastructure.Services.ProcessMaps;
+using AsbCloudInfrastructure.Services.ProcessMaps.Report;
+using DocumentFormat.OpenXml.Bibliography;
+using DocumentFormat.OpenXml.Spreadsheet;
+using NSubstitute;
+using Xunit;
+
+namespace AsbCloudWebApi.Tests.Services.ProcessMaps;
+
+public class ProcessMapReportDataSaubStatServiceTest
+{
+
+
+ private IWellService wellService
+ = Substitute.For();
+
+ private IChangeLogRepository processMapPlanBaseRepository
+ = Substitute.For>();
+
+ private IWellOperationRepository wellOperationRepository
+ = Substitute.For();
+
+ private IWellOperationCategoryRepository wellOperationCategoryRepository
+ = Substitute.For();
+
+ private IDataSaubStatRepository dataSaubStatRepository
+ = Substitute.For();
+
+ private ProcessMapReportDataSaubStatService service;
+
+ private readonly static SimpleTimezoneDto timezone = new() { Hours = 2 };
+ private static readonly DateTimeOffset dateStart = new (2024, 01, 01, 00, 11, 11, timezone.Offset);
+ private readonly static WellDto well = new()
+ {
+ Id = 1,
+ IdTelemetry = 1,
+ Timezone = timezone
+ };
+ private readonly static IEnumerable processMapPlan = new List()
+ {
+ new() {
+ DepthStart = 0,
+ DepthEnd = 100,
+ IdMode = 1,
+ IdWell = well.Id,
+ IdWellSectionType = 1,
+ AxialLoadPlan = 0.2,
+ AxialLoadLimitMax = 0.3,
+ DeltaPressurePlan = 0.4,
+ DeltaPressureLimitMax = 0.5,
+ TopDriveTorquePlan = 0.6,
+ TopDriveTorqueLimitMax = 0.7,
+ TopDriveSpeedPlan = 0.8,
+ TopDriveSpeedLimitMax = 0.9,
+ FlowPlan = 0.10,
+ FlowLimitMax = 0.11,
+ RopPlan = 0.12,
+ UsageSaub = 0.12,
+ UsageSpin = 0.14,
+ Comment = "r",
+ },
+ new() {
+ DepthStart = 0,
+ DepthEnd = 100,
+ IdMode = 2,
+ IdWell = well.Id,
+ IdWellSectionType = 1,
+ AxialLoadPlan = 0.12,
+ AxialLoadLimitMax = 0.13,
+ DeltaPressurePlan = 0.14,
+ DeltaPressureLimitMax = 0.15,
+ TopDriveTorquePlan = 0.16,
+ TopDriveTorqueLimitMax = 0.17,
+ TopDriveSpeedPlan = 0.18,
+ TopDriveSpeedLimitMax = 0.19,
+ FlowPlan = 0.110,
+ FlowLimitMax = 0.111,
+ RopPlan = 0.112,
+ UsageSaub = 0.112,
+ UsageSpin = 0.114,
+ Comment = "s",
+ },
+ new() {
+ DepthStart = 100,
+ DepthEnd = 200,
+ IdMode = 1,
+ IdWell = well.Id,
+ IdWellSectionType = 1,
+ AxialLoadPlan = 0.22,
+ AxialLoadLimitMax = 0.23,
+ DeltaPressurePlan = 0.24,
+ DeltaPressureLimitMax = 0.25,
+ TopDriveTorquePlan = 0.26,
+ TopDriveTorqueLimitMax = 0.27,
+ TopDriveSpeedPlan = 0.28,
+ TopDriveSpeedLimitMax = 0.29,
+ FlowPlan = 0.210,
+ FlowLimitMax = 0.211,
+ RopPlan = 0.212,
+ UsageSaub = 0.212,
+ UsageSpin = 0.214,
+ Comment = "r",
+ },
+ new() {
+ DepthStart = 100,
+ DepthEnd = 200,
+ IdMode = 2,
+ IdWell = well.Id,
+ IdWellSectionType = 1,
+ AxialLoadPlan = 0.32,
+ AxialLoadLimitMax = 0.33,
+ DeltaPressurePlan = 0.34,
+ DeltaPressureLimitMax = 0.35,
+ TopDriveTorquePlan = 0.36,
+ TopDriveTorqueLimitMax = 0.37,
+ TopDriveSpeedPlan = 0.38,
+ TopDriveSpeedLimitMax = 0.39,
+ FlowPlan = 0.310,
+ FlowLimitMax = 0.311,
+ RopPlan = 0.312,
+ UsageSaub = 0.312,
+ UsageSpin = 0.314,
+ Comment = "s",
+ },
+ };
+ private readonly static IEnumerable operations = new List()
+ {
+ new()
+ {
+ Id = 1,
+ IdWell = well.Id,
+ IdWellSectionType = 1,
+ IdCategory = WellOperationCategory.IdRotor,
+ IdParentCategory = WellOperationCategory.IdMechanicalDrilling,
+ IdType = WellOperation.IdOperationTypeFact,
+ DepthStart = 0,
+ DepthEnd = 10,
+ DateStart = dateStart,
+ DurationHours = 1,
+ },
+ new()
+ {
+ Id = 1,
+ IdWell = well.Id,
+ IdWellSectionType = 2,
+ IdCategory = WellOperationCategory.IdRotor,
+ IdParentCategory = WellOperationCategory.IdMechanicalDrilling,
+ IdType = WellOperation.IdOperationTypeFact,
+ DepthStart = 50,
+ DepthEnd = 100,
+ DateStart = dateStart,
+ DurationHours = 1,
+ }
+ };
+ private readonly static IEnumerable operationCategories = new List()
+ {
+ new(){Id = WellOperationCategory.IdRotor, IdParent = WellOperationCategory.IdMechanicalDrilling, Name = "РОТОР"},
+ new(){Id = WellOperationCategory.IdSlide, IdParent = WellOperationCategory.IdMechanicalDrilling, Name = "СЛАЙД"},
+ };
+ private readonly static IEnumerable sectionTypes = new List() {
+ new(){ Id = 1, Caption = "Секция 1"},
+ new(){ Id = 2, Caption = "Секция 2"},
+ new(){ Id = 3, Caption = "Секция 3"},
+ new(){ Id = 4, Caption = "Секция 4"},
+ };
+ private readonly static IEnumerable dataSaubStat = new List() {
+ new(){
+ Id = 1,
+ IdTelemetry = 1,
+ IdCategory = WellOperationCategory.IdRotor,
+ DateStart = dateStart,
+ DateEnd = dateStart.AddHours(3.25),
+ DepthStart = 0,
+ DepthEnd = 20,
+ Speed = 0.1,
+ BlockSpeedSp = 0.2,
+ Pressure = 0.3,
+ PressureIdle = 0.4,
+ PressureSp = 0.5,
+ AxialLoad = 0.6,
+ AxialLoadSp = 0.7,
+ AxialLoadLimitMax = 0.8,
+ RotorTorque = 0.9,
+ RotorTorqueSp = 0.11,
+ RotorTorqueLimitMax = 0.12,
+ RotorSpeed = 0.14,
+ Flow = 0.17,
+ IdFeedRegulator = LimitingParameterDto.AxialLoad,
+ EnabledSubsystems = 15,
+ HasOscillation = false,
+ },
+ new(){
+ Id = 1,
+ IdTelemetry = 1,
+ IdCategory = WellOperationCategory.IdRotor,
+ DateStart = dateStart.AddHours(3.25),
+ DateEnd = dateStart.AddHours(4.25),
+ DepthStart = 20,
+ DepthEnd = 190,
+ Speed = 0.21,
+ BlockSpeedSp = 0.22,
+ Pressure = 0.23,
+ PressureIdle = 0.24,
+ PressureSp = 0.25,
+ AxialLoad = 0.26,
+ AxialLoadSp = 0.27,
+ AxialLoadLimitMax = 0.28,
+ RotorTorque = 0.29,
+ RotorTorqueSp = 0.211,
+ RotorTorqueLimitMax = 0.212,
+ RotorSpeed = 0.214,
+ Flow = 0.217,
+ IdFeedRegulator = LimitingParameterDto.AxialLoad,
+ EnabledSubsystems = 15,
+ HasOscillation = false,
+ },
+ new(){
+ Id = 1,
+ IdTelemetry = 1,
+ IdCategory = WellOperationCategory.IdRotor,
+ DateStart = dateStart.AddHours(4.25),
+ DateEnd = dateStart.AddHours(16),
+ DepthStart = 190,
+ DepthEnd = 290,
+ Speed = 0.31,
+ BlockSpeedSp = 0.32,
+ Pressure = 0.33,
+ PressureIdle = 0.34,
+ PressureSp = 0.35,
+ AxialLoad = 0.36,
+ AxialLoadSp = 0.37,
+ AxialLoadLimitMax = 0.38,
+ RotorTorque = 0.39,
+ RotorTorqueSp = 0.231,
+ RotorTorqueLimitMax = 0.232,
+ RotorSpeed = 0.234,
+ Flow = 0.237,
+ IdFeedRegulator = LimitingParameterDto.AxialLoad,
+ EnabledSubsystems = 15,
+ HasOscillation = false,
+ }
+ };
+
+ public ProcessMapReportDataSaubStatServiceTest()
+ {
+ wellService.GetOrDefaultAsync(Arg.Any(), Arg.Any())
+ .Returns(well);
+
+ processMapPlanBaseRepository.Get(Arg.Any(), Arg.Any())
+ .Returns(processMapPlan);
+
+ wellOperationRepository.GetAsync(Arg.Any(), Arg.Any())
+ .Returns(operations);
+
+ wellOperationRepository.GetSectionTypes()
+ .Returns(sectionTypes);
+
+ wellOperationCategoryRepository.Get(Arg.Any())
+ .Returns(operationCategories);
+
+ dataSaubStatRepository.GetAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ .Returns(dataSaubStat);
+
+ service = new ProcessMapReportDataSaubStatService(wellService, processMapPlanBaseRepository, dataSaubStatRepository, wellOperationRepository, wellOperationCategoryRepository);
+ }
+
+ [Fact]
+ public async Task GetAsync_return_data()
+ {
+ // arrange
+ DataSaubStatRequest request = new() { };
+
+ // act
+ var result = await service.GetAsync(well.Id, request, CancellationToken.None);
+
+ // assert
+ Assert.NotEmpty(result);
+ }
+}
diff --git a/AsbCloudWebApi/Controllers/ProcessMaps/ProcessMapWellDrillingController.cs b/AsbCloudWebApi/Controllers/ProcessMaps/ProcessMapWellDrillingController.cs
index 538303a8..857e4a68 100644
--- a/AsbCloudWebApi/Controllers/ProcessMaps/ProcessMapWellDrillingController.cs
+++ b/AsbCloudWebApi/Controllers/ProcessMaps/ProcessMapWellDrillingController.cs
@@ -1,11 +1,8 @@
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using AsbCloudApp.Data;
+using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
+using AsbCloudApp.Data.ProcessMaps.Report;
using AsbCloudApp.Repositories;
+using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps;
using AsbCloudApp.Services.ProcessMaps.WellDrilling;
@@ -14,6 +11,11 @@ using AsbCloudWebApi.SignalR.Clients;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace AsbCloudWebApi.Controllers.ProcessMaps;
@@ -23,6 +25,7 @@ namespace AsbCloudWebApi.Controllers.ProcessMaps;
public class ProcessMapWellDrillingController : ProcessMapBaseController
{
private readonly IProcessMapReportWellDrillingService processMapReportWellDrillingService;
+ private readonly IProcessMapReportDataSaubStatService processMapReportDataSaubStatService;
private readonly IProcessMapReportWellDrillingExportService processMapReportWellDrillingExportService;
private readonly IProcessMapPlanImportService processMapPlanImportService;
@@ -33,7 +36,8 @@ public class ProcessMapWellDrillingController : ProcessMapBaseController wellSectionRepository,
IHubContext telemetryHubContext,
ITelemetryService telemetryService,
@@ -42,6 +46,7 @@ public class ProcessMapWellDrillingController : ProcessMapBaseController
/// Id
+ /// параметры запроса
///
///
- [HttpGet("report")]
- [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
- public async Task GetReportAsync(int idWell, CancellationToken cancellationToken)
+ [HttpPost("report")]
+ [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
+ public async Task GetReportAsync(int idWell, DataSaubStatRequest request, CancellationToken cancellationToken)
{
- var report = await processMapReportWellDrillingService.GetAsync(idWell, cancellationToken);
+ var report = await processMapReportDataSaubStatService.GetAsync(idWell, request, cancellationToken);
return Ok(report);
}
@@ -89,9 +95,9 @@ public class ProcessMapWellDrillingController : ProcessMapBaseController
[HttpPost("import/{options}")]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
- public async Task ImportAsync(int idWell,
- int options,
- [Required] IFormFile file,
+ public async Task ImportAsync(int idWell,
+ int options,
+ [Required] IFormFile file,
CancellationToken cancellationToken)
{
await AssertUserHasAccessToEditProcessMapAsync(idWell, cancellationToken);