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/Data/SAUB/SetpointsRequestDto.cs b/AsbCloudApp/Data/SAUB/SetpointsRequestDto.cs index 5ecb4932..ed81c0ef 100644 --- a/AsbCloudApp/Data/SAUB/SetpointsRequestDto.cs +++ b/AsbCloudApp/Data/SAUB/SetpointsRequestDto.cs @@ -39,7 +39,8 @@ namespace AsbCloudApp.Data.SAUB /// время в секундах актуальности этого запроса /// [Required] - public int ObsolescenceSec { get; set; } + [Range(10 * 60, 4 * 60 * 60)] + public int ObsolescenceSec { get; set; } = 10 * 60; /// /// набор уставок: {"название переменной панели"; "рекомендуемое значение"} 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 7356f9a3..7cfc86f9 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -229,6 +229,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); diff --git a/AsbCloudWebApi/Controllers/SAUB/SetpointsController.cs b/AsbCloudWebApi/Controllers/SAUB/SetpointsController.cs index 996c8298..59711fa3 100644 --- a/AsbCloudWebApi/Controllers/SAUB/SetpointsController.cs +++ b/AsbCloudWebApi/Controllers/SAUB/SetpointsController.cs @@ -18,8 +18,6 @@ namespace AsbCloudWebApi.Controllers.SAUB { private readonly ISetpointsService setpointsService; private readonly IWellService wellService; - private const int ObsolescenceSecMin = 30; - private const int ObsolescenceSecMax = 6 * 60 * 60; public SetpointsController(ISetpointsService setpointsService, IWellService wellService) { @@ -68,11 +66,6 @@ namespace AsbCloudWebApi.Controllers.SAUB setpoints.IdWell = idWell; setpoints.IdState = 1; - if (setpoints is null - || setpoints.ObsolescenceSec > ObsolescenceSecMax - || setpoints.ObsolescenceSec < ObsolescenceSecMin) - return this.ValidationBadRequest(nameof(setpoints.ObsolescenceSec), "Wrong ObsolescenceSec"); - if (!setpoints.Setpoints.Any()) return this.ValidationBadRequest(nameof(setpoints.Setpoints), "Wrong setpoints count");