using AsbCloudApp.Data; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.Cache; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Mapster; namespace AsbCloudInfrastructure.Services { class OperationParams { public OperationParams() { } public OperationParams(WellOperation operation) { Id = operation.Id; IdWellSectionType = operation.IdWellSectionType; IdCategory = operation.IdCategory; Start = operation.StartDate; WellDepth = operation.WellDepth; DeltaDepth = 0; Hours = operation.DurationHours; } public int IdWellSectionType { get; } public int IdCategory { get; } public int Id { get; } public DateTime Start { get; } public double WellDepth { get; set; } public double DeltaDepth { get; set; } public double Hours { get; set; } } class Race { public DateTime StartDate { get; set; } public double StartWellDepth { get; set; } public DateTime EndDate { get; set; } public double EndWellDepth { get; set; } public double DrillingTime { get; set; } public double NonProductiveHours { get; set; } public double DeltaDepth => EndWellDepth - StartWellDepth; public double DeltaHoursTimeNoNpt => (EndDate - StartDate).TotalHours - NonProductiveHours; public double Speed => DeltaDepth / (DeltaHoursTimeNoNpt + double.Epsilon); public List Operations { get; internal set; } } public class WellOperationsStatService : IWellOperationsStatService { private readonly IAsbCloudDbContext db; private readonly IWellService wellService; private readonly CacheTable cacheSectionsTypes; private readonly CacheTable cacheWell; private readonly CacheTable cacheWellType; private readonly CacheTable 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 WellOperationsStatService(IAsbCloudDbContext db, CacheDb cache, IWellService wellService) { this.db = db; this.wellService = wellService; cacheSectionsTypes = cache.GetCachedTable((DbContext)db); cacheWell = cache.GetCachedTable((DbContext)db); cacheWellType = cache.GetCachedTable((DbContext)db); cacheCluster = cache.GetCachedTable((DbContext)db); } public async Task GetStatClusterAsync(int idCluster, CancellationToken token = default) { var wells = await db.Wells .Include(w => w.WellOperations) .Where(o => o.IdCluster == idCluster) .AsNoTracking() .ToListAsync(token); var operations = wells.SelectMany(w => w.WellOperations).OrderBy(o => o.StartDate); var cluster = await cacheCluster.FirstOrDefaultAsync(c => c.Id == idCluster, token); var wellsIds = wells.Select(o => o.Id).Distinct(); var statsWells = new List(wellsIds.Count()); foreach (var idWell in wellsIds) { var statWellDto = await CalcStatWell(operations, idWell, token); statsWells.Add(statWellDto); } var statClusterDto = new StatClusterDto { Id = idCluster, Caption = cluster.Caption, StatsWells = statsWells, }; return statClusterDto; } public async Task GetStatWellAsync(int idWell, CancellationToken token = default) { var operations = await db.WellOperations .Where(o => o.IdWell == idWell) .OrderBy(o => o.StartDate) // ускорит дальнейшие сортировки .AsNoTracking() .ToListAsync(token); if (!operations.Any()) return null; var statWellDto = await CalcStatWell(operations, idWell, token); return statWellDto; } private async Task CalcStatWell(IEnumerable operations, int idWell, CancellationToken token = default) { var wellOperations = operations .Where(o => o.IdWell == idWell); var well = await cacheWell.FirstOrDefaultAsync(w => w.Id == idWell, token); var wellType = await cacheWellType.FirstOrDefaultAsync(t => t.Id == well.IdWellType, token); if (!wellOperations.Any()) return new StatWellDto() { Caption = well.Caption, WellType = wellType.Caption }; var statWellDto = new StatWellDto { Id = idWell, Caption = well.Caption, WellType = wellType?.Caption, Companies = await wellService.GetCompaniesAsync(idWell, token), Sections = CalcSectionsStats(wellOperations), Total = GetStat(wellOperations), }; return statWellDto; } private IEnumerable CalcSectionsStats(IEnumerable 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(sectionTypes.Count); var operationsPlan = MakeOperationsExt(operations.Where(o => o.IdType == idOperationTypePlan)); var operationsFact = MakeOperationsExt(operations.Where(o => o.IdType == idOperationTypeFact)); foreach ((var id, var sectionType) in sectionTypes) { StatSectionDto section = new StatSectionDto { Id = id, Caption = sectionType.Caption, Plan = CalcSectionStat(operationsPlan, id), Fact = CalcSectionStat(operationsFact, id), }; sections.Add(section); } return sections; } private static PlanFactBase GetStat(IEnumerable operations) { var operationsPlan = MakeOperationsExt(operations.Where(o => o.IdType == idOperationTypePlan)); var operationsFact = MakeOperationsExt(operations.Where(o => o.IdType == idOperationTypeFact)); var section = new PlanFactBase { Plan = CalcStat(operationsPlan), Fact = CalcStat(operationsFact), }; return section; } private static StatOperationsDto CalcSectionStat(IEnumerable operations, int idSectionType) { var sectionOperations = operations .Where(o => o.IdWellSectionType == idSectionType) .OrderBy(o => o.Start) .ThenBy(o => o.WellDepth); return CalcStat(sectionOperations); } private static StatOperationsDto CalcStat(IEnumerable operations) { if (!operations.Any()) return null; var races = GetCompleteRaces(operations); var section = new StatOperationsDto { Start = operations.First().Start, End = operations.Max(o => (o.Start.AddHours(o.Hours))), WellDepthStart = operations.Min(o => o.WellDepth), WellDepthEnd = operations.Max(o => o.WellDepth), 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.Hours), }; return section; } private static double CalcROP(IEnumerable operationsProps) { var drillingOperations = operationsProps.Where(o => o.IdCategory == idOperationDrilling); var dDepth = 0d; var dHours = 0d; foreach (var operation in drillingOperations) { dDepth += operation.DeltaDepth; dHours += operation.Hours; } return dDepth / (dHours + double.Epsilon); } private static double CalcCasingDownSpeed(IEnumerable operationsProps) { var ops = operationsProps.Where(o => o.IdCategory == idOperationCasingDown); var depth = 0d; var dHours = 0d; foreach (var operation in ops) { depth += operation.WellDepth; dHours += operation.Hours; } return depth / (dHours + double.Epsilon); } private static IEnumerable GetCompleteRaces(IEnumerable operations) { var races = new List(); var iterator = operations .OrderBy(o => o.Start) .GetEnumerator(); while (iterator.MoveNext()) { if (iterator.Current.IdCategory == idOperationBhaAssembly) { var race = new Race { StartDate = iterator.Current.Start.AddHours(iterator.Current.Hours), StartWellDepth = iterator.Current.WellDepth, Operations = new List(4), }; while (iterator.MoveNext()) { if (iterator.Current.IdCategory == idOperationNonProductiveTime) { race.NonProductiveHours += iterator.Current.Hours; } if (iterator.Current.IdCategory == idOperationBhaDisassembly) { race.EndDate = iterator.Current.Start; race.EndWellDepth = iterator.Current.WellDepth; races.Add(race); break; } race.Operations.Add(iterator.Current); } } } return races; } private static double CalcAvgRaceSpeed(IEnumerable 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 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].Hours; if (race.Operations[i].IdCategory == idOperationDrilling) break; } } return dDepth / (dHours + double.Epsilon); } private static double CalcBhaUpSpeed(IEnumerable 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].Hours; if (race.Operations[i].IdCategory == idOperationDrilling) break; } } return dDepth / (dHours + double.Epsilon); } public async Task>> 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.StartDate) .AsNoTracking() .ToListAsync(token) .ConfigureAwait(false); var wellOperationsPlan = wellOperations .Where(o => o.IdType == idOperationTypePlan) .OrderBy(o => o.StartDate); var wellOperationsFact = wellOperations .Where(o => o.IdType == idOperationTypeFact) .OrderBy(o => o.StartDate); if (!wellOperationsPlan.Any()) return null; var merged = MergeArrays(wellOperationsPlan, wellOperationsFact); var tvd = new List>(merged.Count); int iLastMatch = 0; int iLastFact = 0; for (int i = 0; i < merged.Count; i++) { var item = merged[i]; var planFactPredict = new PlanFactPredictBase { 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.StartDate.AddHours(lastMatchPlan.DurationHours); var lastMatchFact = merged[iLastMatch].Item2; var startOffset = lastMatchFact.StartDate.AddHours(lastMatchFact.DurationHours) - lastMatchPlanOperationEnd; for (int i = iLastMatch + 1; i < merged.Count; i++) { if (merged[i].Item1 is null) continue; tvd[i].Predict = merged[i].Item1.Adapt(); tvd[i].Predict.IdType = 2; tvd[i].Predict.StartDate = tvd[i].Predict.StartDate + startOffset; } return tvd; } public static List> MergeArrays(IEnumerable array1, IEnumerable array2) { var a1 = array1.ToArray(); var a2 = array2.ToArray(); var m = new List>(a1.Length); void Add(WellOperation item1, WellOperation item2) => m.Add(new Tuple(item1, item2)); bool Compare(WellOperation item1, WellOperation item2) => item1.IdCategory == item2.IdCategory && Math.Abs(item1.WellDepth - item2.WellDepth) < 250; 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])); if ((nextI1 > -1) && ((nextI2 == -1) || ((nextI1 - i1) < (nextI2 - i2)))) { for (; i1 < nextI1; i1++) Add(a1[i1], null); } if ((nextI2 > -1) && ((nextI1 == -1) || ((nextI1 - i1) > (nextI2 - i2)))) { for (; i2 < nextI2; i2++) Add(null, a2[i2]); } if ((nextI1 == -1) && (nextI2 == -1)) { for (; i2 < a2.Length; i2++) Add(null, a2[i2]); for (; i1 < a1.Length; i1++) Add(a1[i1], null); } } } else if (is1) { Add(a1[i1++], null); } else if (is2) { Add(null, a2[i2++]); } } return m; } private static readonly Action WellOperationDtoMutation = (WellOperationDto dest, WellOperation source) => { dest.CategoryName = source.OperationCategory?.Name; dest.WellSectionTypeName = source.WellSectionType?.Caption; }; private static IEnumerable MakeOperationsExt(IEnumerable operations) { var ops = new List(); if (operations.Any()) { var sortedOperations = operations.OrderBy(o => o.StartDate); var count = operations.Count(); ops = new List(count); var item = operations.ElementAt(0); var wellDepth = item.WellDepth; var pre = new OperationParams(item); var current = new OperationParams(item); for (int i = 1; i < count; i++) { item = operations.ElementAt(i); current = new OperationParams(item) { WellDepth = Helper.Max(wellDepth, item.WellDepth) }; pre.DeltaDepth = current.WellDepth - wellDepth; wellDepth = current.WellDepth; pre.Hours = (current.Start - pre.Start).TotalHours; ops.Add(pre); pre = current; } ops.Add(current); } return ops; } } }