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 { 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 telemetryStat = new TelemetryStat(telemetryRowSpan); var processMapByMode = processMap.FirstOrDefault(p => p.IdMode == telemetryStat.IdMode); var processMapFirst = processMap.First(); var idWellSectionType = processMapByMode?.IdWellSectionType ?? processMapFirst.IdWellSectionType; var result = new ProcessMapReportDto { IdWell = processMapByMode?.IdWell ?? processMapFirst.IdWell, IdWellSectionType = idWellSectionType, WellSectionTypeName = sectionTypes[idWellSectionType], DepthStart = subInterval.DepthStart, DepthEnd = subInterval.DepthEnd, DateStart = telemetryStat.DateStart, MechDrillingHours = telemetryStat.MechDrillingHours, DrillingMode = telemetryStat.ModeName, DeltaDepth = telemetryStat.DeltaDepth, PressureDiff = telemetryStat.Pressure.MakeParams(processMapByMode?.Pressure.Plan), AxialLoad = telemetryStat.AxialLoad.MakeParams(processMapByMode?.AxialLoad.Plan), TopDriveTorque = telemetryStat.RotorTorque.MakeParams(processMapByMode?.TopDriveTorque.Plan), SpeedLimit = telemetryStat.BlockSpeed.MakeParams(processMapByMode?.RopPlan), Rop = telemetryStat.Rop, UsagePlan = processMapByMode?.UsageSaub ?? telemetryStat.UsagePredictPlan, UsageFact = telemetryStat.UsageSaub, }; return result; } 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 readonly int idMode; private TelemetryDataSaubStatDto? previous; public double SpUsageDepth { get; private set; } private static double spUsageTotal; public ParamStat(Func getterSp, Func getterPv, Func? getterLimitMax, int idFeedRegulator, int idMode) { this.getterSp = getterSp; this.getterPv = getterPv; this.getterLimitMax = getterLimitMax; this.idFeedRegulator = idFeedRegulator; this.idMode = idMode; spUsageTotal = 0d; } 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; spUsageTotal += deltaDepth; } } else { var pvErr = (getterSp(current) - getterPv(current)) / getterSp(current); if (pvErr < 0.03d) //3% { SpUsageDepth += deltaDepth; spUsageTotal += deltaDepth; } } deltaDepthSum += deltaDepth; } } previous = current; } public ProcessMapReportParamsDto MakeParams(double? spPlan) { var result = new ProcessMapReportParamsDto { SetpointPlan = spPlan, Fact = DivideValByDepth(pvWSum), }; if (idMode == 0) { result.SetpointFact = null; result.Limit = null; result.SetpointUsage = null; } else { result.SetpointFact = DivideValByDepth(spWSum); result.Limit = (getterLimitMax is not null) ? DivideValByDepth(limitMaxWSum) : null; result.SetpointUsage = deltaDepthSum > 0d ? 100d * SpUsageDepth / spUsageTotal : null; } return result; } 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; } 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 { get; } public double UsagePredictPlan { get; } public DateTime DateStart { get; } public float DeltaDepth { get; } public int IdMode { get; } public string ModeName { get; } public double MechDrillingHours { get; } public TelemetryStat(Span telemetry) { var telemetryFirst = telemetry[0]; var telemetryLast = telemetry[^1]; IdMode = telemetryFirst.IdMode; ModeName = GetModeName(IdMode); DateStart = telemetryFirst.DateMin; DeltaDepth = telemetryLast.WellDepthMax - telemetryFirst.WellDepthMin; MechDrillingHours = (telemetryLast.DateMax - telemetryFirst.DateMin).TotalHours; BlockSpeed = new(t => t.BlockSpeedSp, t => t.BlockSpeed, null, 1, IdMode); Pressure = new(t => t.PressureSpDelta, t => t.PressureDelta, t=>t.PressureDeltaLimitMax, 2, IdMode); RotorTorque = new(t => t.RotorTorqueSp, t => t.RotorTorque, t=>t.RotorTorqueLimitMax, 3, IdMode); AxialLoad = new(t => t.AxialLoadSp, t => t.AxialLoad, t=>t.AxialLoadLimitMax, 4, IdMode); for (int i = 0; i < telemetry.Length; i++) UpdateStat(telemetry[i]); UsageSaub = 100d * depthWithSaub / depthSum; UsagePredictPlan = IdMode != 0 ? 100d : 0d; } private 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); } private static string GetModeName(int idMode) => idMode switch { 1 => "Ротор", 3 => "Слайд", _ => "Ручной", }; } }