using AsbCloudApp.Data.ProcessMap; using AsbCloudApp.Data.SAUB; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Services; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.ProcessMap { #nullable enable public partial class ProcessMapReportService : IProcessMapReportService { private readonly IWellService wellService; private readonly IWellOperationRepository wellOperationRepository; private readonly IProcessMapPlanRepository processMapPlanRepository; private readonly ITelemetryDataSaubService telemetryDataSaubService; public ProcessMapReportService( IWellService wellService, IWellOperationRepository wellOperationRepository, IProcessMapPlanRepository processMapPlanRepository, ITelemetryDataSaubService telemetryDataSaubService) { this.wellService = wellService; this.wellOperationRepository = wellOperationRepository; this.processMapPlanRepository = processMapPlanRepository; this.telemetryDataSaubService = telemetryDataSaubService; } /// public async Task> GetProcessMapReportAsync(int idWell, CancellationToken token) { var well = wellService.GetOrDefault(idWell) ?? throw new ArgumentInvalidException("idWell not found", nameof(idWell)); var idTelemetry = well.IdTelemetry ?? throw new ArgumentInvalidException("telemetry by well not found", nameof(idWell)); var processMapPlan = await processMapPlanRepository.GetByIdWellAsync(idWell, token); if (!processMapPlan.Any()) return Enumerable.Empty(); var telemetryDataStat = (await telemetryDataSaubService.GetTelemetryDataStatAsync(idTelemetry, token)).ToArray(); if (!telemetryDataStat.Any()) return Enumerable.Empty(); var result = CalcByIntervals(processMapPlan, telemetryDataStat); return result; } private IEnumerable CalcByIntervals(IEnumerable processMapPlan, TelemetryDataSaubStatDto[] telemetryDataStat) { var processMapIntervals = CalcDepthIntervals(processMapPlan); var result = new List(processMapIntervals.Count() * 4); var telemetryIndexStart = Array.FindIndex(telemetryDataStat, t => t.WellDepthMin >= processMapIntervals.First().DepthStart); if (telemetryIndexStart < 0) return Enumerable.Empty(); IDictionary sectionTypes = wellOperationRepository.GetSectionTypes(); foreach (var interval in processMapIntervals) { var processMapPlanInterval = processMapPlan .Where(p => p.DepthStart >= interval.DepthStart && p.DepthEnd <= interval.DepthEnd); var telemetryIndexEnd = Array.FindIndex(telemetryDataStat, telemetryIndexStart, t => t.WellDepthMin >= interval.DepthEnd); if (telemetryIndexEnd < 0) telemetryIndexEnd = telemetryDataStat.Length - 1; var telemetryDataInterval = telemetryDataStat.AsSpan(telemetryIndexStart, telemetryIndexEnd - telemetryIndexStart); IEnumerable subIntervalsResult = CalcSubIntervals(interval, processMapPlanInterval, telemetryDataInterval, sectionTypes); result.AddRange(subIntervalsResult); telemetryIndexStart = telemetryIndexEnd; } return result; } private static IEnumerable<(double DepthStart, double DepthEnd)> CalcDepthIntervals(IEnumerable processMapPlan) { if(!processMapPlan.Any()) yield break; var intervalStarts = processMapPlan .OrderBy(i => i.DepthStart) .Select(p => p.DepthStart) .Distinct() .ToArray(); for (var i = 1; i < intervalStarts.Length; i++) yield return (intervalStarts[i - 1], intervalStarts[i]); yield return (intervalStarts[^1], processMapPlan.Max(p=>p.DepthEnd)); } private static IEnumerable CalcSubIntervals( (double DepthStart, double DepthEnd) interval, IEnumerable processMapPlanInterval, Span telemetryDataInterval, IDictionary sectionTypes) { var telemetryDataIntervalLength = telemetryDataInterval.Length; if (telemetryDataInterval.Length == 0) return Enumerable.Empty(); var result = new List(); var telemetryIndexStart = 0; var subInterval = interval; for (var i = telemetryIndexStart + 1; i < telemetryDataIntervalLength; i++) { if (IsDifferent(telemetryDataInterval[telemetryIndexStart], telemetryDataInterval[i])) { subInterval.DepthEnd = telemetryDataInterval[i - 1].WellDepthMax; var telemetryRowSpan = telemetryDataInterval[telemetryIndexStart..(i - 1)]; if (!telemetryRowSpan.IsEmpty) { var intervalReportRow = CalcSubIntervalReportRow(subInterval, processMapPlanInterval, telemetryRowSpan, sectionTypes); result.Add(intervalReportRow); } telemetryIndexStart = i; subInterval.DepthStart = subInterval.DepthEnd; } } subInterval.DepthEnd = interval.DepthEnd; var intervalReportRowLast = CalcSubIntervalReportRow(subInterval, processMapPlanInterval, telemetryDataInterval[telemetryIndexStart..telemetryDataIntervalLength], sectionTypes); result.Add(intervalReportRowLast); return result; } private static ProcessMapReportDto CalcSubIntervalReportRow( (double DepthStart, double DepthEnd) subInterval, IEnumerable processMap, Span telemetryRowSpan, IDictionary sectionTypes) { var telemetryFirst = telemetryRowSpan[0]; var telemetryLast = telemetryRowSpan[^1]; var mode = GetIdMode(telemetryRowSpan); var processMapByMode = processMap.FirstOrDefault(p => p.IdMode == mode.IdMode); var processMapFirst = processMap.First(); var idWellSectionType = processMapByMode?.IdWellSectionType ?? processMapFirst.IdWellSectionType; var telemetryStat = AnalyzeTelemetry(telemetryRowSpan); var result = new ProcessMapReportDto { IdWell = processMapByMode?.IdWell ?? processMapFirst.IdWell, IdWellSectionType = idWellSectionType, WellSectionTypeName = sectionTypes[idWellSectionType], DepthStart = subInterval.DepthStart, DepthEnd = subInterval.DepthEnd, DateStart = telemetryFirst.DateMin, MechDrillingHours = (telemetryLast.DateMax - telemetryFirst.DateMin).TotalHours, DrillingMode = mode.ModeName, DeltaDepth = telemetryLast.WellDepthMax - telemetryFirst.WellDepthMin, PressureDiff = telemetryStat.Pressure.MakeParams(processMapByMode?.Pressure.Plan, telemetryStat.SpUsageDepthTotal), AxialLoad = telemetryStat.AxialLoad.MakeParams(processMapByMode?.AxialLoad.Plan, telemetryStat.SpUsageDepthTotal), TopDriveTorque = telemetryStat.RotorTorque.MakeParams(processMapByMode?.TopDriveTorque.Plan, telemetryStat.SpUsageDepthTotal), SpeedLimit = telemetryStat.BlockSpeed.MakeParams(processMapByMode?.RopPlan, telemetryStat.SpUsageDepthTotal), Rop = telemetryStat.Rop, Usage = telemetryStat.UsageSaub, }; return result; } private static TelemetryStat AnalyzeTelemetry(Span telemetry) { var stat = new TelemetryStat(); for (int i = 0; i < telemetry.Length; i++) stat.UpdateStat(telemetry[i]); return stat; } private static (int IdMode, string ModeName) GetIdMode(Span telemetryRowSpan) { /// Режим работы САУБ в телеметрии: /// 0 - "РУЧНОЙ" /// 1 - "БУРЕНИЕ В РОТОРЕ" /// 3 - "БУРЕНИЕ В СЛАЙДЕ" for (int i = 0; i < telemetryRowSpan.Length; i++) { var idMode = telemetryRowSpan[i].IdMode; if (idMode == 0) return (0, "Ручной"); if (idMode == 1) return (1, "Ротор"); if (idMode == 3) return (2, "Слайд"); } return (0, "Ручной"); } private static bool IsDifferent(TelemetryDataSaubStatDto intervalStart, TelemetryDataSaubStatDto current) { if (intervalStart.WellDepthMin > current.WellDepthMin) return true; if (intervalStart.IdMode != current.IdMode) return true; if (Math.Abs(intervalStart.PressureSp - current.PressureSp) > 5d) return true; if (Math.Abs(intervalStart.AxialLoadSp - current.AxialLoadSp) > 1d) return true; if (Math.Abs(intervalStart.RotorTorqueSp - current.RotorTorqueSp) > 5d) return true; var blockSpeedSpDiff = Math.Abs(intervalStart.BlockSpeedSp - current.BlockSpeedSp); if (blockSpeedSpDiff > 5d) { if (intervalStart.BlockSpeedSp <= 30) return true; else if (intervalStart.BlockSpeedSp > 30 && blockSpeedSpDiff > 15d) return true; else if (intervalStart.BlockSpeedSp > 80 && blockSpeedSpDiff > 20d) return true; } return false; } } class ParamStat { private double spWSum; private double pvWSum; private double limitMaxWSum; private double deltaDepthSum; private readonly Func getterSp; private readonly Func getterPv; private readonly Func? getterLimitMax; private readonly int idFeedRegulator; private TelemetryDataSaubStatDto? previous; public double SpUsageDepth { get; private set; } public ParamStat(Func getterSp, Func getterPv, Func? getterLimitMax, int idFeedRegulator) { this.getterSp = getterSp; this.getterPv = getterPv; this.getterLimitMax = getterLimitMax; this.idFeedRegulator = idFeedRegulator; } public void UpdateStat(TelemetryDataSaubStatDto current) { if(previous is not null) { var deltaDepth = current.WellDepthMin - previous.WellDepthMin; if (deltaDepth > 0) { var deltaDepthHalf = deltaDepth / 2; double CalculateWeight(Func getter) => (getter(previous!) + getter(current)) * deltaDepthHalf; spWSum += CalculateWeight(getterSp); pvWSum += CalculateWeight(getterPv); if(getterLimitMax is not null) limitMaxWSum += CalculateWeight(getterLimitMax!); if (current.IdFeedRegulator is not null) { if(current.IdFeedRegulator == idFeedRegulator) SpUsageDepth += deltaDepth; } else { var pvErr = (getterSp(current) - getterPv(current)) / getterSp(current); if (pvErr < 0.03d) //3% SpUsageDepth += deltaDepth; } deltaDepthSum += deltaDepth; } } previous = current; } public ProcessMapReportParamsDto MakeParams(double? spPlan, double SpUsageDepthTotal) => new ProcessMapReportParamsDto { SetpointPlan = spPlan, SetpointFact = DivideValByDepth(spWSum), Fact = DivideValByDepth(pvWSum), Limit = (getterLimitMax is not null) ? DivideValByDepth(limitMaxWSum) : null, SetpointUsage = deltaDepthSum > 0d ? SpUsageDepth / SpUsageDepthTotal : null, }; private double? DivideValByDepth(double? val) { if(val is null || val == 0d || deltaDepthSum == 0d) return null; return val / deltaDepthSum; } } class TelemetryStat { public ParamStat Pressure { get; } public ParamStat AxialLoad {get; } public ParamStat RotorTorque {get; } public ParamStat BlockSpeed {get; } public double SpUsageDepthTotal => Pressure.SpUsageDepth + AxialLoad.SpUsageDepth + RotorTorque.SpUsageDepth + BlockSpeed.SpUsageDepth; private TelemetryDataSaubStatDto? previous; private double depthSum = 0d; private double hoursSum = 0d; public double? Rop => hoursSum == 0d ? null : depthSum / hoursSum; private double depthWithSaub = 0d; public double UsageSaub => depthWithSaub / depthSum; public TelemetryStat() { BlockSpeed = new(t => t.BlockSpeedSp, t => t.BlockSpeed, null, 1); Pressure = new(t => t.PressureSp, t => t.Pressure, t=>t.PressureDeltaLimitMax, 2); RotorTorque = new(t => t.RotorTorqueSp, t => t.RotorTorque, t=>t.RotorTorqueLimitMax, 3); AxialLoad = new(t => t.AxialLoadSp, t => t.AxialLoad, t=>t.AxialLoadLimitMax, 4); } public void UpdateStat(TelemetryDataSaubStatDto current) { if(previous is not null) { var deltaDepth = current.WellDepthMin - previous.WellDepthMin; if(deltaDepth > 0) { var deltaHours = (current.DateMin - previous.DateMax).TotalHours; depthSum += deltaDepth; hoursSum += deltaHours; if(current.IdMode == 1 || current.IdMode == 3) depthWithSaub += deltaDepth; } } previous = current; Pressure.UpdateStat(current); AxialLoad.UpdateStat(current); RotorTorque.UpdateStat(current); BlockSpeed.UpdateStat(current); } }; #nullable disable }