DD.WellWorkover.Cloud/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs

471 lines
18 KiB
C#

using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Data.WellOperation;
using AsbCloudApp.Repositories;
namespace AsbCloudInfrastructure.Services.WellOperationService;
public class OperationsStatService : IOperationsStatService
{
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly IWellService wellService;
private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService,
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
{
this.db = db;
this.memoryCache = memoryCache;
this.wellService = wellService;
this.telemetryDataCache = telemetryDataCache;
}
public async Task<StatClusterDto?> GetOrDefaultStatClusterAsync(int idCluster, int idCompany, CancellationToken token)
{
var cluster = (await memoryCache
.GetOrCreateBasicAsync(db.Set<Cluster>(), token))
.FirstOrDefault(c => c.Id == idCluster);
if (cluster is null)
return null;
var allWellsByCompany = await wellService.GetAsync(new() { IdCompany = idCompany }, token).ConfigureAwait(false);
var idWellsByCompany = allWellsByCompany.Select(w => w.Id).Distinct();
var wells = await db.Wells
.Include(w => w.WellOperations)
.Where(o => o.IdCluster == idCluster)
.Where(w => idWellsByCompany.Contains(w.Id))
.Select(w => w.Id)
.ToListAsync(token);
var statsWells = await GetWellsStatAsync(wells, token).ConfigureAwait(false);
var statClusterDto = new StatClusterDto
{
Id = idCluster,
Caption = cluster.Caption,
StatsWells = statsWells,
};
return statClusterDto;
}
public async Task<IEnumerable<StatWellDto>> GetWellsStatAsync(IEnumerable<int> idWells, CancellationToken token)
{
var wells = await db.Wells
.Include(w => w.WellOperations)
.Where(w => idWells.Contains(w.Id))
.AsNoTracking()
.ToListAsync(token);
var statsWells = new List<StatWellDto>(wells.Count);
foreach (var well in wells)
{
var statWellDto = await CalcWellStatAsync(well, token);
statsWells.Add(statWellDto);
}
return statsWells;
}
public async Task<StatWellDto?> GetOrDefaultWellStatAsync(int idWell,
CancellationToken token = default)
{
var well = await db.Wells
.Include(w => w.WellOperations)
.FirstOrDefaultAsync(w => w.Id == idWell, token)
.ConfigureAwait(false);
if(well is null)
return null;
var statWellDto = await CalcWellStatAsync(well, token);
return statWellDto;
}
public async Task<ClusterRopStatDto?> GetOrDefaultRopStatAsync(int idWell, CancellationToken token)
{
var clusterWellsIds = await wellService.GetClusterWellsIdsAsync(idWell, token)
.ConfigureAwait(false);
if (clusterWellsIds is null)
return null;
var idLastSectionType = await (from o in db.WellOperations
where o.IdWell == idWell &&
o.IdType == 1
orderby o.DepthStart
select o.IdWellSectionType)
.LastOrDefaultAsync(token)
.ConfigureAwait(false);
if (idLastSectionType == default)
return null;
var operations = await (from o in db.WellOperations
where clusterWellsIds.Contains(o.IdWell) &&
o.IdType == 1 &&
o.IdWellSectionType == idLastSectionType
select o)
.ToListAsync(token)
.ConfigureAwait(false);
var statsList = new List<StatOperationsDto>(clusterWellsIds.Count());
foreach (var clusterWellId in clusterWellsIds)
{
var currentWellOps = operations.Where(o => o.IdWell == clusterWellId);
var timezoneOffsetHours = wellService.GetTimezone(clusterWellId).Hours;
var stat = CalcStat(currentWellOps, timezoneOffsetHours);
if(stat is not null)
statsList.Add(stat);
};
if (!statsList.Any())
return null;
var clusterRops = new ClusterRopStatDto()
{
RopMax = statsList.Max(s => s.Rop),
RopAverage = statsList.Average(s => s.Rop)
};
return clusterRops;
}
private async Task<StatWellDto> CalcWellStatAsync(Well well, CancellationToken token)
{
var timezone = wellService.GetTimezone(well.Id);
var wellType = (await memoryCache
.GetOrCreateBasicAsync(db.Set<WellType>(), token))
.FirstOrDefault(t => t.Id == well.IdWellType);
var statWellDto = new StatWellDto
{
Id = well.Id,
Caption = well.Caption,
WellType = wellType?.Caption ?? "",
IdState = well.IdState,
State = wellService.GetStateText(well.IdState),
LastTelemetryDate = wellService.GetLastTelemetryDate(well.Id),
Companies = await wellService.GetCompaniesAsync(well.Id, token)
};
if (well.WellOperations is null)
return statWellDto;
var wellOperations = well.WellOperations
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthEnd);
if (!wellOperations.Any())
return statWellDto;
statWellDto.Sections = CalcSectionsStats(wellOperations, timezone.Hours);
statWellDto.Total = GetStatTotal(wellOperations, well.IdState, timezone.Hours);
statWellDto.TvdLagDays = CalcTvdLagDays(wellOperations);
statWellDto.TvdDrillingDays = CalcDrillingDays(wellOperations);
return statWellDto;
}
private static double? CalcDrillingDays(IEnumerable<WellOperation> wellOperations)
{
var operationsOrdered = wellOperations
.OrderBy(o => o.DateStart);
var factOperations = operationsOrdered
.Where(o => o.IdType == WellOperation.IdOperationTypeFact);
if (!factOperations.Any())
return null;
var operationFrom = factOperations.First();
var operationTo = factOperations.Last();
return (operationTo.DateStart.AddHours(operationTo.DurationHours) - operationFrom.DateStart).TotalDays;
}
private static double? CalcTvdLagDays(IEnumerable<WellOperation> wellOperations)
{
var operationsOrdered = wellOperations
.OrderBy(o => o.DateStart);
var factOperations = operationsOrdered
.Where(o => o.IdType == WellOperation.IdOperationTypeFact);
var lastCorrespondingFactOperation = factOperations
.LastOrDefault(o => o.IdPlan is not null);
if (lastCorrespondingFactOperation is null)
return null;
var lastCorrespondingPlanOperation = wellOperations
.FirstOrDefault(o => o.Id == lastCorrespondingFactOperation.IdPlan);
if (lastCorrespondingPlanOperation is null)
return null;
var factEnd = lastCorrespondingFactOperation.DateStart.AddHours(lastCorrespondingFactOperation.DurationHours);
var planEnd = lastCorrespondingPlanOperation.DateStart.AddHours(lastCorrespondingPlanOperation.DurationHours);
var lagDays = (factEnd - planEnd).TotalDays;
return lagDays;
}
private IEnumerable<StatSectionDto> CalcSectionsStats(IEnumerable<WellOperation> operations, double timezoneOffsetH)
{
var sectionTypeIds = operations
.Select(o => o.IdWellSectionType)
.Distinct();
var sectionTypes = memoryCache
.GetOrCreateBasic(db.Set<WellSectionType>())
.Where(s => sectionTypeIds.Contains(s.Id))
.ToDictionary(s => s.Id);
var sections = new List<StatSectionDto>(sectionTypes.Count);
var operationsPlan = operations.Where(o => o.IdType == WellOperation.IdOperationTypePlan);
var operationsFact = operations.Where(o => o.IdType == WellOperation.IdOperationTypeFact);
foreach ((var id, var sectionType) in sectionTypes)
{
var section = new StatSectionDto
{
Id = id,
Caption = sectionType.Caption,
Plan = CalcSectionStat(operationsPlan, id, timezoneOffsetH),
Fact = CalcSectionStat(operationsFact, id, timezoneOffsetH),
};
sections.Add(section);
}
return sections;
}
private static PlanFactDto<StatOperationsDto> GetStatTotal(IEnumerable<WellOperation> operations,
int idWellState, double timezoneOffsetH)
{
var operationsPlan = operations.Where(o => o.IdType == WellOperation.IdOperationTypePlan);
var operationsFact = operations.Where(o => o.IdType == WellOperation.IdOperationTypeFact);
var factEnd = CalcStat(operationsFact, timezoneOffsetH);
if (factEnd is not null && idWellState != 2)
factEnd.End = null;
var section = new PlanFactDto<StatOperationsDto>
{
Plan = CalcStat(operationsPlan, timezoneOffsetH),
Fact = factEnd,
};
return section;
}
private static StatOperationsDto? CalcSectionStat(IEnumerable<WellOperation> operations, int idSectionType, double timezoneOffsetHours)
{
var sectionOperations = operations
.Where(o => o.IdWellSectionType == idSectionType)
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthStart);
return CalcStat(sectionOperations, timezoneOffsetHours);
}
private static StatOperationsDto? CalcStat(IEnumerable<WellOperation> operations, double timezoneOffsetHours)
{
if (!operations.Any())
return null;
var races = GetCompleteRaces(operations, timezoneOffsetHours);
var section = new StatOperationsDto
{
Start = operations.FirstOrDefault()?.DateStart.ToOffset(TimeSpan.FromHours(timezoneOffsetHours)),
End = operations.Max(o => o.DateStart.ToOffset(TimeSpan.FromHours(timezoneOffsetHours)).AddHours(o.DurationHours)),
WellDepthStart = operations.Min(o => o.DepthStart),
WellDepthEnd = operations.Max(o => o.DepthStart),
Rop = CalcROP(operations),
RouteSpeed = CalcAvgRaceSpeed(races),
BhaDownSpeed = CalcBhaDownSpeed(races),
BhaUpSpeed = CalcBhaUpSpeed(races),
CasingDownSpeed = CalcCasingDownSpeed(operations),
NonProductiveHours = operations
.Where(o => WellOperationCategory.NonProductiveTimeSubIds.Contains(o.IdCategory))
.Sum(o => o.DurationHours),
};
return section;
}
private static double CalcROP(IEnumerable<WellOperation> operationsProps)
{
var drillingOperations = operationsProps.Where(o => WellOperationCategory.MechanicalDrillingSubIds.Contains(o.IdCategory));
var dDepth = 0d;
var dHours = 0d;
foreach (var operation in drillingOperations)
{
var deltaDepth = operation.DepthEnd - operation.DepthStart;
dDepth += deltaDepth;
dHours += operation.DurationHours;
}
return dDepth / (dHours + double.Epsilon);
}
private static double CalcCasingDownSpeed(IEnumerable<WellOperation> operationsProps)
{
var ops = operationsProps.Where(o => o.IdCategory == WellOperationCategory.IdCasingDown);
var depth = 0d;
var dHours = 0d;
foreach (var operation in ops)
{
depth += operation.DepthStart;
dHours += operation.DurationHours;
}
return depth / (dHours + double.Epsilon);
}
private static IEnumerable<Race> GetCompleteRaces(IEnumerable<WellOperation> operations, double timezoneOffsetH)
{
var races = new List<Race>();
var iterator = operations
.OrderBy(o => o.DateStart)
.GetEnumerator();
while (iterator.MoveNext())
{
if (iterator.Current.IdCategory == WellOperationCategory.IdBhaAssembly)
{
var race = new Race
{
StartDate = iterator.Current.DateStart.ToRemoteDateTime(timezoneOffsetH).AddHours(iterator.Current.DurationHours),
StartWellDepth = iterator.Current.DepthStart,
Operations = new List<WellOperation>(10),
};
while (iterator.MoveNext())
{
if (iterator.Current.IdCategory == WellOperationCategory.IdEquipmentRepair)
race.RepairHours += iterator.Current.DurationHours;
if (WellOperationCategory.NonProductiveTimeSubIds.Contains(iterator.Current.IdCategory))
race.NonProductiveHours += iterator.Current.DurationHours;
if (iterator.Current.IdCategory == WellOperationCategory.IdBhaDisassembly)
{
race.EndDate = iterator.Current.DateStart.ToRemoteDateTime(timezoneOffsetH);
race.EndWellDepth = iterator.Current.DepthStart;
races.Add(race);
break;
}
race.Operations.Add(iterator.Current);
}
}
}
return races;
}
private static double CalcAvgRaceSpeed(IEnumerable<Race> races)
{
var dDepth = 0d;
var dHours = 0d;
foreach (var race in races)
{
dHours += race.DeltaHours - race.NonProductiveHours - race.RepairHours;
dDepth += race.DeltaDepth;
}
return dDepth / (dHours + double.Epsilon);
}
private static double CalcBhaDownSpeed(IEnumerable<Race> races)
{
var dDepth = 0d;
var dHours = 0d;
foreach (Race race in races)
{
dDepth += race.StartWellDepth;
for (var i = 0; i < race.Operations.Count; i++)
{
if (race.Operations[i].IdCategory == WellOperationCategory.IdBhaDown)
dHours += race.Operations[i].DurationHours;
if (WellOperationCategory.MechanicalDrillingSubIds.Contains(race.Operations[i].IdCategory))
break;
}
}
return dDepth / (dHours + double.Epsilon);
}
private static double CalcBhaUpSpeed(IEnumerable<Race> races)
{
var dDepth = 0d;
var dHours = 0d;
foreach (var race in races)
{
dDepth += race.EndWellDepth;
for (var i = race.Operations.Count - 1; i > 0; i--)
{
if (race.Operations[i].IdCategory == WellOperationCategory.IdBhaUp)
dHours += race.Operations[i].DurationHours;
if (WellOperationCategory.MechanicalDrillingSubIds.Contains(race.Operations[i].IdCategory))
break;
}
}
return dDepth / (dHours + double.Epsilon);
}
private async Task<IEnumerable<(WellOperation? Plan, WellOperation? Fact)>> GetOperationsAsync(int idWell, CancellationToken token)
{
var query = db.WellOperations
.Include(o => o.OperationCategory)
.Include(o => o.WellSectionType)
.Where(o => o.IdWell == idWell)
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthEnd);
var operationsFactWithNoPlan = await query.Where(o => o.IdPlan == null && o.IdType == WellOperation.IdOperationTypeFact)
.AsNoTracking()
.ToArrayAsync(token);
var operationsFactWithPlan = await query.Where(o => o.IdPlan != null && o.IdType == WellOperation.IdOperationTypeFact)
.Include(o => o.OperationPlan)
.ThenInclude(o => o!.WellSectionType)
.Include(o => o.OperationPlan)
.ThenInclude(o => o!.OperationCategory)
.AsNoTracking()
.ToArrayAsync(token);
var idsPlanWithFact = operationsFactWithPlan.Select(o => o.IdPlan).Distinct();
var operationsPlanWithNoFact = await query
.Where(o => o.IdType == WellOperation.IdOperationTypePlan && !idsPlanWithFact.Contains(o.IdPlan)).ToArrayAsync(token);
var capacity = operationsFactWithNoPlan.Length + operationsFactWithPlan.Length + operationsPlanWithNoFact.Length;
var result = new List<(WellOperation? Plan, WellOperation? Fact)>(capacity);
foreach (var operation in operationsFactWithPlan)
result.Add((operation.OperationPlan, operation));
foreach (var operation in operationsFactWithNoPlan)
result.Add((null, operation));
foreach (var operation in operationsPlanWithNoFact)
result.Add((operation, null));
return result
.OrderBy(x => x.Plan?.DateStart)
.ThenBy(x => x.Fact?.DateStart);
}
private static WellOperationDto Convert(WellOperation source, double tzOffsetHours)
{
var destination = source.Adapt<WellOperationDto>();
destination.OperationCategoryName = source.OperationCategory.Name;
destination.WellSectionTypeCaption = source.WellSectionType.Caption;
destination.DateStart = source.DateStart.ToRemoteDateTime(tzOffsetHours);
return destination;
}
}