DD.WellWorkover.Cloud/AsbCloudInfrastructure/Services/ProcessMap/ProcessMapReportService.cs
2023-04-06 10:25:51 +05:00

381 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
/// <inheritdoc/>
public async Task<IEnumerable<ProcessMapReportDto>> 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<ProcessMapReportDto>();
var telemetryDataStat = (await telemetryDataSaubService.GetTelemetryDataStatAsync(idTelemetry, token)).ToArray();
if (!telemetryDataStat.Any())
return Enumerable.Empty<ProcessMapReportDto>();
var result = CalcByIntervals(processMapPlan, telemetryDataStat);
return result;
}
private IEnumerable<ProcessMapReportDto> CalcByIntervals(IEnumerable<ProcessMapPlanDto> processMapPlan, TelemetryDataSaubStatDto[] telemetryDataStat)
{
var processMapIntervals = CalcDepthIntervals(processMapPlan);
var result = new List<ProcessMapReportDto>(processMapIntervals.Count() * 4);
var telemetryIndexStart = Array.FindIndex(telemetryDataStat, t => t.WellDepthMin >= processMapIntervals.First().DepthStart);
if (telemetryIndexStart < 0)
return Enumerable.Empty<ProcessMapReportDto>();
IDictionary<int, string> 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<ProcessMapReportDto> subIntervalsResult = CalcSubIntervals(interval, processMapPlanInterval, telemetryDataInterval, sectionTypes);
result.AddRange(subIntervalsResult);
telemetryIndexStart = telemetryIndexEnd;
}
return result;
}
private static IEnumerable<(double DepthStart, double DepthEnd)> CalcDepthIntervals(IEnumerable<ProcessMapPlanDto> 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<ProcessMapReportDto> CalcSubIntervals(
(double DepthStart, double DepthEnd) interval,
IEnumerable<ProcessMapPlanDto> processMapPlanInterval,
Span<TelemetryDataSaubStatDto> telemetryDataInterval,
IDictionary<int, string> sectionTypes)
{
var telemetryDataIntervalLength = telemetryDataInterval.Length;
if (telemetryDataInterval.Length == 0)
return Enumerable.Empty<ProcessMapReportDto>();
var result = new List<ProcessMapReportDto>();
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<ProcessMapPlanDto> processMap,
Span<TelemetryDataSaubStatDto> telemetryRowSpan,
IDictionary<int, string> 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<TelemetryDataSaubStatDto> 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<TelemetryDataSaubStatDto> 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<TelemetryDataSaubStatDto, double> getterSp;
private readonly Func<TelemetryDataSaubStatDto, double> getterPv;
private readonly Func<TelemetryDataSaubStatDto, double>? getterLimitMax;
private readonly int idFeedRegulator;
private TelemetryDataSaubStatDto? previous;
public double SpUsageDepth { get; private set; }
public ParamStat(Func<TelemetryDataSaubStatDto, double> getterSp,
Func<TelemetryDataSaubStatDto, double> getterPv,
Func<TelemetryDataSaubStatDto, double>? 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<TelemetryDataSaubStatDto, double> 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
}