diff --git a/AsbCloudApp/Data/LimitingParameterDto.cs b/AsbCloudApp/Data/LimitingParameterDto.cs index b4f43a09..73a8a5b7 100644 --- a/AsbCloudApp/Data/LimitingParameterDto.cs +++ b/AsbCloudApp/Data/LimitingParameterDto.cs @@ -8,6 +8,30 @@ namespace AsbCloudApp.Data /// public class LimitingParameterDto { + /// + /// Нет ограничения + /// + public static int NoLimit = 0; + + /// + /// МСП + /// + public static int RopPlan = 1; + + /// + /// Давление + /// + public static int Pressure = 2; + + /// + /// Осевая нагрузка + /// + public static int AxialLoad = 3; + + /// + /// Момент + /// + public static int RotorTorque = 4; /// /// Идентификатор скважины /// diff --git a/AsbCloudApp/Repositories/IDataSaubStatRepository.cs b/AsbCloudApp/Repositories/IDataSaubStatRepository.cs index efab95b7..d03554ea 100644 --- a/AsbCloudApp/Repositories/IDataSaubStatRepository.cs +++ b/AsbCloudApp/Repositories/IDataSaubStatRepository.cs @@ -10,6 +10,14 @@ namespace AsbCloudApp.Repositories /// public interface IDataSaubStatRepository { + /// + /// Получение записей по ключу телеметрии + /// + /// ключ телеметрии + /// + /// + Task> GetAsync(int idTelemetry, CancellationToken token); + /// /// Получение последних по дате окончания бурения записей в разрезе телеметрий /// diff --git a/AsbCloudApp/Requests/DataSaubStatRequest.cs b/AsbCloudApp/Requests/DataSaubStatRequest.cs new file mode 100644 index 00000000..78bedc14 --- /dev/null +++ b/AsbCloudApp/Requests/DataSaubStatRequest.cs @@ -0,0 +1,33 @@ +namespace AsbCloudApp.Requests +{ + /// + /// Параметры запроса для построения отчёта + /// + public class DataSaubStatRequest + { + /// + /// Изменение уставки факт перепада давления от первого значения в начале интервала + /// + public double DeltaPressure { get; set; } + + /// + /// Изменение уставки факт осевой нагрузки от первого значения в начале интервала + /// + public double DeltaAxialLoad { get; set; } + + /// + /// Изменение уставки момента от первого значения в начале интервала + /// + public double DeltaRotorTorque { get; set; } + + /// + /// Изменение ограничения нагрузки от первого значения в начале интервала + /// + 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 5b131299..e940e12a 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..3e18e42e 100644 --- a/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs +++ b/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs @@ -40,6 +40,19 @@ namespace AsbCloudInfrastructure.Repository return result; } + public async Task> GetAsync(int idTelemetry, CancellationToken token) + { + var timeSpan = TimeSpan.FromHours(telemetryService.GetTimezone(idTelemetry).Hours); + + var stats = await db.Set() + .Where(s => s.IdTelemetry == idTelemetry) + .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..93751e36 --- /dev/null +++ b/AsbCloudInfrastructure/Services/ProcessMaps/Report/ProcessMapReportDataSaubStatService.cs @@ -0,0 +1,318 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.ProcessMapPlan; +using AsbCloudApp.Data.ProcessMaps.Report; +using AsbCloudApp.Exceptions; +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; + + public ProcessMapReportDataSaubStatService(IWellService wellService, + IChangeLogRepository processMapPlanBaseRepository, + IDataSaubStatRepository dataSaubStatRepository, + IWellOperationRepository wellOperationRepository + ) + { + this.wellService = wellService; + this.processMapPlanBaseRepository = processMapPlanBaseRepository; + this.dataSaubStatRepository = dataSaubStatRepository; + this.wellOperationRepository = wellOperationRepository; + } + + 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 dataSaubStats = + (await dataSaubStatRepository.GetAsync(well.IdTelemetry.Value, token)).ToArray(); + + if (!dataSaubStats.Any()) + return Enumerable.Empty(); + + var requestWellOperationFact = new WellOperationRequest() + { + IdWell = idWell, + OperationType = WellOperation.IdOperationTypeFact + }; + var wellOperations = await wellOperationRepository + .GetAsync(requestWellOperationFact, token); + if (!wellOperations.Any()) + return Enumerable.Empty(); + + var timeZone = TimeSpan.FromHours(wellService.GetTimezone(idWell).Hours); + var result = CalcByIntervals(request, processMapPlanWellDrillings, dataSaubStats, wellOperations, timeZone); + + return result; + } + + private IEnumerable CalcByIntervals( + DataSaubStatRequest request, + IEnumerable processMapPlanWellDrillings, + Span dataSaubStats, + IEnumerable wellOperations, + TimeSpan timeZone + ) + { + var list = new List(); + var firstElemInInterval = dataSaubStats[0]; + + var indexStart = 0; + for (var i = 1; i < dataSaubStats.Length; i++) + { + var currentElem = dataSaubStats[i]; + if (IsNewInterval(currentElem, firstElemInInterval, request) || i == dataSaubStats.Length - 1) + { + var length = i - indexStart; + var elem = CalcStat(processMapPlanWellDrillings, dataSaubStats, indexStart, length, wellOperations, timeZone); + if (elem != null) + list.Add(elem); + + indexStart = i; + firstElemInInterval = currentElem; + } + } + return list; + } + + private ProcessMapReportDataSaubStatDto? CalcStat( + IEnumerable processMapPlanDrillingDtos, + Span dataSaubStats, + int indexStart, + int length, + IEnumerable wellOperations, + TimeSpan timeZone + ) + { + var span = dataSaubStats.Slice(indexStart, length); + var firstElemInInterval = span[0]; + var lastElemInInterval = span[^1]; + + var nearestOperation = wellOperations?.MinBy(o => firstElemInInterval.DateStart - o.DateStart); + if (nearestOperation is null) + return null; + + var processMapPlanFilteredByDepth = processMapPlanDrillingDtos + .Where(x => x.IdWellSectionType == nearestOperation.IdWellSectionType) + .Where(x => x.DepthStart >= firstElemInInterval.DepthStart) + .Where(x => x.DepthEnd <= lastElemInInterval.DepthEnd) + .ToArray(); + if (!processMapPlanFilteredByDepth.Any()) + return null; + + var deltaDepth = lastElemInInterval.DepthEnd - firstElemInInterval.DepthStart; + var drilledTime = (lastElemInInterval.DateEnd - firstElemInInterval.DateStart).TotalHours; + + var aggregatedValues = CalcAggregate(span); + + return new ProcessMapReportDataSaubStatDto() + { + DateStart = firstElemInInterval.DateStart.ToOffset(timeZone).DateTime, + WellSectionTypeName = nearestOperation.WellSectionTypeName ?? string.Empty, + DepthStart = firstElemInInterval.DepthStart, + DepthEnd = lastElemInInterval.DepthEnd, + DeltaDepth = deltaDepth, + DrilledTime = drilledTime, + DrillingMode = nearestOperation.CategoryName ?? string.Empty, + PressureDiff = new ProcessMapReportDataSaubStatParamsDto() + { + SetpointPlanMax = processMapPlanFilteredByDepth.Max(p => p.DeltaPressurePlan), + SetpointPlanMin = processMapPlanFilteredByDepth.Min(p => p.DeltaPressurePlan), + SetpointFact = firstElemInInterval.PressureSp - firstElemInInterval.PressureIdle, + FactWavg = aggregatedValues.Pressure, + Limit = processMapPlanFilteredByDepth.Max(p => p.DeltaPressureLimitMax), + SetpointUsage = aggregatedValues.SetpointUsagePressure + }, + AxialLoad = new ProcessMapReportDataSaubStatParamsDto() + { + SetpointPlanMax = processMapPlanFilteredByDepth.Max(p => p.AxialLoadPlan), + SetpointPlanMin = processMapPlanFilteredByDepth.Min(p => p.AxialLoadPlan), + SetpointFact = aggregatedValues.AxialLoadSp, + FactWavg = aggregatedValues.AxialLoad, + Limit = processMapPlanFilteredByDepth.Max(p => p.AxialLoadLimitMax), + SetpointUsage = aggregatedValues.SetpointUsageAxialLoad + }, + TopDriveTorque = new ProcessMapReportDataSaubStatParamsDto() + { + SetpointPlanMax = processMapPlanFilteredByDepth.Max(p => p.TopDriveTorquePlan), + SetpointPlanMin = processMapPlanFilteredByDepth.Min(p => p.TopDriveTorquePlan), + SetpointFact = aggregatedValues.RotorTorqueSp, + FactWavg = aggregatedValues.RotorTorque, + FactMax = aggregatedValues.RotorTorqueMax, + Limit = processMapPlanFilteredByDepth.Max(p => p.TopDriveTorqueLimitMax), + SetpointUsage = aggregatedValues.SetpointUsageRotorTorque + }, + SpeedLimit = new ProcessMapReportDataSaubStatParamsDto + { + SetpointPlanMax = processMapPlanFilteredByDepth.Max(p => p.RopPlan), + SetpointPlanMin = processMapPlanFilteredByDepth.Min(p => p.RopPlan), + SetpointFact = aggregatedValues.BlockSpeedSp, + FactWavg = deltaDepth / drilledTime, + SetpointUsage = aggregatedValues.SetpointUsageRopPlan + }, + Turnover = new ProcessMapReportDataSaubStatParamsDto + { + SetpointPlanMax = processMapPlanFilteredByDepth.Max(p => p.TopDriveSpeedPlan), + SetpointPlanMin = processMapPlanFilteredByDepth.Min(p => p.TopDriveSpeedPlan), + FactWavg = aggregatedValues.RotorSpeed, + FactMax = aggregatedValues.RotorSpeedMax + }, + Flow = new ProcessMapReportDataSaubStatParamsDto + { + SetpointPlanMax = processMapPlanFilteredByDepth.Max(p => p.FlowPlan), + SetpointPlanMin = processMapPlanFilteredByDepth.Min(p => p.FlowPlan), + FactWavg = aggregatedValues.MaxFlow, + Limit = processMapPlanFilteredByDepth.Max(p => p.FlowLimitMax), + }, + Rop = new PlanFactDto + { + Plan = CalcRopPlan(processMapPlanFilteredByDepth), + Fact = deltaDepth / drilledTime + }, + }; + } + + private ( + 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 + ) 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; + for (var i = 0; i < span.Length; i++) + { + var diffDepth = span[i].DepthEnd - span[i].DepthStart; + + sumPressure += diffDepth * span[i].Pressure; + 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 += span[i].DepthEnd - span[i].DepthStart; + if (span[i].IdFeedRegulator == LimitingParameterDto.AxialLoad) + sumDiffDepthByAxialLoad += span[i].DepthEnd - span[i].DepthStart; + if (span[i].IdFeedRegulator == LimitingParameterDto.RotorTorque) + sumDiffDepthByRotorTorque += span[i].DepthEnd - span[i].DepthStart; + if (span[i].IdFeedRegulator == LimitingParameterDto.RopPlan) + sumDiffDepthByRopPlan += span[i].DepthEnd - span[i].DepthStart; + + diffDepthTotal += diffDepth; + + + } + 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 + ); + } + + private double CalcRopPlan(ProcessMapPlanDrillingDto[] processMapPlanFilteredByDepth) + { + var sumRopPlan = 0.0; + var diffDepthTotal = 0.0; + + for (var i = 0; i < processMapPlanFilteredByDepth.Length; i++) + { + var diffDepth = processMapPlanFilteredByDepth[i].DepthEnd - processMapPlanFilteredByDepth[i].DepthStart; + sumRopPlan += diffDepth * processMapPlanFilteredByDepth[i].RopPlan; + diffDepthTotal += diffDepth; + } + return sumRopPlan / diffDepthTotal; + } + + private bool IsNewInterval(DataSaubStatDto currentElem, DataSaubStatDto firstElem, DataSaubStatRequest request) + { + 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/Controllers/ProcessMaps/ProcessMapWellDrillingController.cs b/AsbCloudWebApi/Controllers/ProcessMaps/ProcessMapWellDrillingController.cs index 538303a8..94f99db6 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) + [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);