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

549 lines
21 KiB
C#

using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.WellOperationService
{
public class OperationsStatService : IOperationsStatService
{
private readonly IAsbCloudDbContext db;
private readonly IWellService wellService;
private readonly ITelemetryService telemetryService;
private readonly CacheTable<WellSectionType> cacheSectionsTypes;
private readonly CacheTable<WellType> cacheWellType;
private readonly CacheTable<Cluster> cacheCluster;
private const int idOperationBhaAssembly = 1025;
private const int idOperationBhaDisassembly = 1026;
private const int idOperationNonProductiveTime = 1043;
private const int idOperationDrilling = 1001;
private const int idOperationBhaDown = 1046;
private const int idOperationBhaUp = 1047;
private const int idOperationCasingDown = 1048;
private const int idOperationTypePlan = 0;
private const int idOperationTypeFact = 1;
public OperationsStatService(IAsbCloudDbContext db, CacheDb cache, IWellService wellService,
ITelemetryService telemetryService)
{
this.db = db;
this.wellService = wellService;
this.telemetryService = telemetryService;
cacheSectionsTypes = cache.GetCachedTable<WellSectionType>((DbContext)db);
cacheWellType = cache.GetCachedTable<WellType>((DbContext)db);
cacheCluster = cache.GetCachedTable<Cluster>((DbContext)db);
}
public async Task<ClusterRopStatDto> GetRopStatByIdWellAsync(int idWell,
CancellationToken token)
{
return await GetRopStatAsync(idWell, token).ConfigureAwait(false);
}
public async Task<ClusterRopStatDto> GetRopStatByUidAsync(string uid,
CancellationToken token)
{
var idWell = telemetryService.GetidWellByTelemetryUid(uid);
if (idWell is null)
return null;
return await GetRopStatAsync((int)idWell, token).ConfigureAwait(false);
}
public async Task<StatClusterDto> GetStatClusterAsync(int idCluster, int idCompany, CancellationToken token = default)
{
var allWellsByCompany = await wellService.GetWellsByCompanyAsync(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 cluster = await cacheCluster.FirstOrDefaultAsync(c => c.Id == idCluster, token);
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 CalcStatWellAsync(well, token);
statsWells.Add(statWellDto);
}
return statsWells;
}
public async Task<StatWellDto> GetStatWellAsync(int idWell,
CancellationToken token = default)
{
var well = await db.Wells
.Include(w => w.WellOperations)
.FirstOrDefaultAsync(w => w.Id == idWell, token)
.ConfigureAwait(false);
var statWellDto = await CalcStatWellAsync(well, token);
return statWellDto;
}
private async Task<ClusterRopStatDto> GetRopStatAsync(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);
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 = clusterWellsIds.Select(clusterWellId =>
{
var currentWellOps = operations.Where(o => o.IdWell == clusterWellId);
var stat = CalcStat(currentWellOps);
return stat;
}).Where(c => c is not null);
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> CalcStatWellAsync(Well well, CancellationToken token = default)
{
var wellType = await cacheWellType.FirstOrDefaultAsync(t => t.Id == well.IdWellType, token);
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),
};
statWellDto.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);
statWellDto.Total = GetStatTotal(wellOperations);
return statWellDto;
}
private IEnumerable<StatSectionDto> CalcSectionsStats(IEnumerable<WellOperation> operations)
{
var sectionTypeIds = operations
.Select(o => o.IdWellSectionType)
.Distinct();
var sectionTypes = cacheSectionsTypes
.Where(s => sectionTypeIds.Contains(s.Id))
.ToDictionary(s => s.Id);
var sections = new List<StatSectionDto>(sectionTypes.Count);
var operationsPlan = operations.Where(o => o.IdType == idOperationTypePlan);
var operationsFact = operations.Where(o => o.IdType == idOperationTypeFact);
foreach ((var id, var sectionType) in sectionTypes)
{
var section = new StatSectionDto
{
Id = id,
Caption = sectionType.Caption,
Plan = CalcSectionStat(operationsPlan, id),
Fact = CalcSectionStat(operationsFact, id),
};
sections.Add(section);
}
return sections;
}
private static PlanFactBase<StatOperationsDto> GetStatTotal(IEnumerable<WellOperation> operations)
{
var operationsPlan = operations.Where(o => o.IdType == idOperationTypePlan);
var operationsFact = operations.Where(o => o.IdType == idOperationTypeFact);
var section = new PlanFactBase<StatOperationsDto>
{
Plan = CalcStat(operationsPlan),
Fact = CalcStat(operationsFact),
};
return section;
}
private static StatOperationsDto CalcSectionStat(IEnumerable<WellOperation> operations, int idSectionType)
{
var sectionOperations = operations
.Where(o => o.IdWellSectionType == idSectionType)
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthStart);
return CalcStat(sectionOperations);
}
private static StatOperationsDto CalcStat(IEnumerable<WellOperation> operations)
{
if (!operations.Any())
return null;
var races = GetCompleteRaces(operations);
var section = new StatOperationsDto
{
Start = operations.First().DateStart,
End = operations.Max(o => (o.DateStart.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 => o.IdCategory == idOperationNonProductiveTime)
.Sum(o => o.DurationHours),
};
return section;
}
private static double CalcROP(IEnumerable<WellOperation> operationsProps)
{
var drillingOperations = operationsProps.Where(o => o.IdCategory == idOperationDrilling);
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 == idOperationCasingDown);
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)
{
var races = new List<Race>();
var iterator = operations
.OrderBy(o => o.DateStart)
.GetEnumerator();
while (iterator.MoveNext())
{
if (iterator.Current.IdCategory == idOperationBhaAssembly)
{
var race = new Race
{
StartDate = iterator.Current.DateStart.AddHours(iterator.Current.DurationHours),
StartWellDepth = iterator.Current.DepthStart,
Operations = new List<WellOperation>(10),
};
while (iterator.MoveNext())
{
if (iterator.Current.IdCategory == idOperationNonProductiveTime)
{
race.NonProductiveHours += iterator.Current.DurationHours;
}
if (iterator.Current.IdCategory == idOperationBhaDisassembly)
{
race.EndDate = iterator.Current.DateStart;
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.DeltaHoursTimeNoNpt;
dDepth += race.DeltaDepth;
}
return dDepth / (dHours + double.Epsilon);
}
private static double CalcBhaDownSpeed(IEnumerable<Race> races)
{
var dDepth = 0d;
var dHours = 0d;
foreach (var race in races)
{
dDepth += race.StartWellDepth;
for (var i = 0; i < race.Operations.Count; i++)
{
if (race.Operations[i].IdCategory == idOperationBhaDown)
dHours += race.Operations[i].DurationHours;
if (race.Operations[i].IdCategory == idOperationDrilling)
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 == idOperationBhaUp)
dHours += race.Operations[i].DurationHours;
if (race.Operations[i].IdCategory == idOperationDrilling)
break;
}
}
return dDepth / (dHours + double.Epsilon);
}
public async Task<IEnumerable<PlanFactPredictBase<WellOperationDto>>> GetTvdAsync(int idWell, CancellationToken token)
{
var wellOperations = await db.WellOperations
.Include(o => o.OperationCategory)
.Include(o => o.WellSectionType)
.Where(o => o.IdWell == idWell)
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthEnd)
.AsNoTracking()
.ToListAsync(token)
.ConfigureAwait(false);
var wellOperationsPlan = wellOperations
.Where(o => o.IdType == idOperationTypePlan)
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthEnd);
var wellOperationsFact = wellOperations
.Where(o => o.IdType == idOperationTypeFact)
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthEnd);
var sectionsIds = wellOperations
.Select(o => o.IdWellSectionType)
.Distinct();
if (!wellOperationsPlan.Any())
return null;
var merged = MergeArraysBySections(sectionsIds, wellOperationsPlan, wellOperationsFact);
var tvd = new List<PlanFactPredictBase<WellOperationDto>>(merged.Count);
int iLastMatch = 0;
int iLastFact = 0;
for (int i = 0; i < merged.Count; i++)
{
var item = merged[i];
var planFactPredict = new PlanFactPredictBase<WellOperationDto>
{
Plan = item.Item1?.Adapt(WellOperationDtoMutation),
Fact = item.Item2?.Adapt(WellOperationDtoMutation),
Predict = null,
};
tvd.Add(planFactPredict);
if ((item.Item1 is not null) && (item.Item2 is not null))
iLastMatch = i;
if (item.Item2 is not null)
iLastFact = i;
}
if (iLastMatch == 0 || iLastMatch == merged.Count - 1)
return tvd;
var lastMatchPlan = merged[iLastMatch].Item1;
var lastMatchPlanOperationEnd = lastMatchPlan.DateStart.AddHours(lastMatchPlan.DurationHours);
//var lastMatchFact = merged[iLastMatch].Item2;
//var lastMatchFactDateEnd = lastMatchFact.DateStart.AddHours(lastMatchFact.DurationHours);
var lastFact = merged[iLastFact].Item2;
var lastFactDateEnd = lastFact.DateStart.AddHours(lastFact.DurationHours);
var startOffset = lastFactDateEnd - lastMatchPlanOperationEnd;
for (int i = iLastMatch + 1; i < merged.Count; i++)
{
if (merged[i].Item1 is null)
continue;
tvd[i].Predict = merged[i].Item1.Adapt<WellOperationDto>();
tvd[i].Predict.IdType = 2;
tvd[i].Predict.DateStart = tvd[i].Predict.DateStart + startOffset;
}
return tvd;
}
private static List<Tuple<WellOperation, WellOperation>> MergeArraysBySections(
IEnumerable<int> sectionsIds,
IOrderedEnumerable<WellOperation> wellOperationsPlan,
IOrderedEnumerable<WellOperation> wellOperationsFact)
{
var merged = new List<Tuple<WellOperation, WellOperation>>(wellOperationsPlan.Count());
foreach (var sectionId in sectionsIds)
{
var sectionOperationsPlan = wellOperationsPlan
.Where(o => o.IdWellSectionType == sectionId);
var sectionOperationsFact = wellOperationsFact
.Where(o => o.IdWellSectionType == sectionId);
var sectionMerged = MergeArrays(sectionOperationsPlan, sectionOperationsFact);
merged.AddRange(sectionMerged);
}
return merged;
}
private static List<Tuple<WellOperation, WellOperation>> MergeArrays(IEnumerable<WellOperation> array1, IEnumerable<WellOperation> array2)
{
var a1 = array1.ToArray();
var a2 = array2.ToArray();
var m = new List<Tuple<WellOperation, WellOperation>>(a1.Length);
void Add(WellOperation item1, WellOperation item2) =>
m.Add(new Tuple<WellOperation, WellOperation>(item1, item2));
static bool Compare(WellOperation item1, WellOperation item2) =>
item1.IdCategory == item2.IdCategory && Math.Abs(item1.DepthEnd - item2.DepthEnd) < (30d + 0.005d * (item1.DepthEnd + item2.DepthEnd));
int i1 = 0;
int i2 = 0;
while (true)
{
var is1 = a1.Length > i1;
var is2 = a2.Length > i2;
if (!(is1 || is2))
break;
if (is1 && is2)
{
if (Compare(a1[i1], a2[i2]))
Add(a1[i1++], a2[i2++]);
else
{
int nextI1 = Array.FindIndex(a1, i1, (item) => Compare(item, a2[i2]));
int nextI2 = Array.FindIndex(a2, i2, (item) => Compare(item, a1[i1]));
bool deltaI1_Lt_deltaI2 = (nextI1 - i1) < (nextI2 - i2);
if (nextI1 == -1 && nextI2 == -1)
{
if (a1[i1].DepthEnd < a2[i2].DepthEnd)
{
Add(a1[i1++], null);
}
else
{
Add(null, a2[i2++]);
}
}
else if (nextI1 > -1 && nextI2 == -1)
{
Add(a1[i1++], null);
}
else if (nextI1 == -1 && nextI2 > -1)
{
Add(null, a2[i2++]);
}
else if (deltaI1_Lt_deltaI2)
{
Add(a1[i1++], null);
}
else if (!deltaI1_Lt_deltaI2)
{
Add(null, a2[i2++]);
}
}
}
else if (is1)
{
Add(a1[i1++], null);
}
else if (is2)
{
Add(null, a2[i2++]);
}
}
return m;
}
private static readonly Action<WellOperationDto, WellOperation> WellOperationDtoMutation = (WellOperationDto dest, WellOperation source) =>
{
dest.CategoryName = source.OperationCategory?.Name;
dest.WellSectionTypeName = source.WellSectionType?.Caption;
};
}
}