From 20d306a24c09df7e4d1ae3035bd1257a25df680f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Wed, 2 Aug 2023 11:34:42 +0500 Subject: [PATCH 1/3] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=80=D0=B0=D1=81=D1=81=D1=87=D1=91=D1=82=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BA=D0=BB=D0=BE=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D0=A2=D0=92=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudApp/Data/StatWellDto.cs | 4 +- AsbCloudApp/Data/WellMapInfoDto.cs | 5 +- AsbCloudApp/Data/WellOperationDto.cs | 5 ++ .../Services/WellInfoService.cs | 5 +- .../OperationsStatService.cs | 71 ++++++++++--------- 5 files changed, 48 insertions(+), 42 deletions(-) diff --git a/AsbCloudApp/Data/StatWellDto.cs b/AsbCloudApp/Data/StatWellDto.cs index 92df13ef..59ffd3d7 100644 --- a/AsbCloudApp/Data/StatWellDto.cs +++ b/AsbCloudApp/Data/StatWellDto.cs @@ -53,8 +53,8 @@ namespace AsbCloudApp.Data public IEnumerable Companies { get; set; } = Enumerable.Empty(); /// - /// Отставание от ГГД, дней + /// Отставание от ГГД, проценты /// - public double TvdLagDays { get; set; } = 0; + public double? TvdLagPercent { get; set; } } } diff --git a/AsbCloudApp/Data/WellMapInfoDto.cs b/AsbCloudApp/Data/WellMapInfoDto.cs index 42a48534..242ecb6c 100644 --- a/AsbCloudApp/Data/WellMapInfoDto.cs +++ b/AsbCloudApp/Data/WellMapInfoDto.cs @@ -95,9 +95,8 @@ namespace AsbCloudApp.Data public PlanFactDto WellDepth { get; set; } = null!; /// - /// Отставание от ГГД, дни + /// Отставание от ГГД, проценты /// - public double TvdLagDays { get; set; } - + public double TvdLagPercent { get; set; } } } diff --git a/AsbCloudApp/Data/WellOperationDto.cs b/AsbCloudApp/Data/WellOperationDto.cs index 31b50243..b3dc537c 100644 --- a/AsbCloudApp/Data/WellOperationDto.cs +++ b/AsbCloudApp/Data/WellOperationDto.cs @@ -89,6 +89,11 @@ namespace AsbCloudApp.Data [DateValidation(GtDate = "2010-01-01T00:00:00")] public DateTime DateStart { get; set; } + /// + /// Дата окончания операции + /// + public DateTime DateEnd => DateStart.AddHours(DurationHours); + /// /// Продолжительность, часы /// diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs index 4f92afe9..7066ef22 100644 --- a/AsbCloudInfrastructure/Services/WellInfoService.cs +++ b/AsbCloudInfrastructure/Services/WellInfoService.cs @@ -138,8 +138,7 @@ namespace AsbCloudInfrastructure.Services wellMapInfo.FirstFactOperationDateStart = wellOperationsStat?.Total.Fact?.Start ?? wellOperationsStat?.Total.Plan?.Start; - wellMapInfo.LastPredictOperationDateEnd = wellOperationsStat?.Total.Plan?.End? - .AddDays(wellOperationsStat?.TvdLagDays ?? 0d); + wellMapInfo.LastPredictOperationDateEnd = wellOperationsStat?.Total.Plan?.End; wellMapInfo.WellDepth = new() { @@ -163,7 +162,7 @@ namespace AsbCloudInfrastructure.Services wellMapInfo.SaubUsage = wellSubsystemStat?.SubsystemAKB?.KUsage ?? 0d; wellMapInfo.SpinUsage = wellSubsystemStat?.SubsystemSpinMaster?.KUsage ?? 0d; wellMapInfo.TorqueKUsage = wellSubsystemStat?.SubsystemTorqueMaster?.KUsage ?? 0d; - wellMapInfo.TvdLagDays = wellOperationsStat?.TvdLagDays ?? 0d; + wellMapInfo.TvdLagPercent = wellOperationsStat?.TvdLagPercent ?? 0d; wellMapInfo.IdsCompanies = well.Companies.Select(c => c.Id); return wellMapInfo; diff --git a/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs b/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs index 2757dbfd..465f98f9 100644 --- a/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs @@ -9,6 +9,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data.SAUB; +using AsbCloudInfrastructure.Services.SAUB; namespace AsbCloudInfrastructure.Services.WellOperationService { @@ -18,13 +20,16 @@ namespace AsbCloudInfrastructure.Services.WellOperationService private readonly IAsbCloudDbContext db; private readonly IMemoryCache memoryCache; private readonly IWellService wellService; + private readonly TelemetryDataCache telemetryDataCache; - public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService) + public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService, + TelemetryDataCache telemetryDataCache) { this.db = db; this.memoryCache = memoryCache; this.wellService = wellService; - } + this.telemetryDataCache = telemetryDataCache; + } public async Task GetOrDefaultStatClusterAsync(int idCluster, int idCompany, CancellationToken token) { @@ -168,45 +173,43 @@ namespace AsbCloudInfrastructure.Services.WellOperationService var timezoneOffsetH = wellService.GetTimezone(well.Id).Hours; statWellDto.Sections = CalcSectionsStats(wellOperations, timezoneOffsetH); statWellDto.Total = GetStatTotal(wellOperations, well.IdState, timezoneOffsetH); - statWellDto.TvdLagDays = CalcTvdLagDays(wellOperations); + statWellDto.TvdLagPercent = CalcTvdLagPercent(well.IdTelemetry, wellOperations + .Select(x => x.Adapt())); return statWellDto; } - private static double CalcTvdLagDays(IOrderedEnumerable wellOperations) - { - var operationsOrdered = wellOperations - .OrderBy(o => o.DateStart); + private double? CalcTvdLagPercent(int? idTelemetry, IEnumerable wellOperations) + { + if (!idTelemetry.HasValue) + return null; - var factOperations = operationsOrdered - .Where(o => o.IdType == WellOperation.IdOperationTypeFact); + var currentDate = DateTime.UtcNow; - var lastCorrespondingFactOperation = factOperations - .LastOrDefault(o => o.IdPlan is not null); + var lastFactOperation = telemetryDataCache.GetLastOrDefault(idTelemetry.Value); + + wellOperations = wellOperations + .Where(o => o.IdType == WellOperation.IdOperationTypePlan) + .OrderBy(o => o.DateEnd); + + var wellOperationFrom = wellOperations + .LastOrDefault(o => o.DateEnd <= currentDate); + + var wellOperationTo = wellOperations + .FirstOrDefault(o => o.DateEnd >= currentDate); + + var wellOperationDepthFrom = wellOperationFrom?.DepthEnd; + var wellOperationDepthTo = wellOperationTo?.DepthStart ?? wellOperationDepthFrom; + + var wellOperationDateFrom = wellOperationFrom?.DateEnd; + var wellOperationDateTo = wellOperationTo?.DateStart ?? wellOperationDateFrom; - if (lastCorrespondingFactOperation is null) - return 0d; - - var lastCorrespondingPlanOperation = wellOperations - .FirstOrDefault(o => o.Id == lastCorrespondingFactOperation.IdPlan); + var planDepth = (wellOperationDateTo - wellOperationDateFrom)?.TotalHours * + (wellOperationDepthTo - wellOperationDepthFrom) / + (currentDate - wellOperationDateFrom)?.TotalHours + wellOperationDepthFrom; - if (lastCorrespondingPlanOperation is null) - return 0d; - - var lastFactOperation = factOperations.Last(); - - var remainingPlanOperations = operationsOrdered - .Where(o => o.IdType == WellOperation.IdOperationTypePlan) - .Where(o => o.DateStart > lastCorrespondingPlanOperation.DateStart); - - var durationRemain = remainingPlanOperations.Sum(o => o.DurationHours); - - var factEnd = lastFactOperation.DateStart.AddHours(durationRemain + lastFactOperation.DurationHours); - var planEnd = lastCorrespondingFactOperation.DateStart.AddHours(durationRemain + lastCorrespondingFactOperation.DurationHours); - var lagDays = (planEnd - factEnd).TotalDays; - - return lagDays; - } + return (1 - lastFactOperation?.WellDepth / planDepth) * 100; + } private IEnumerable CalcSectionsStats(IEnumerable operations, double timezoneOffsetH) { @@ -544,4 +547,4 @@ namespace AsbCloudInfrastructure.Services.WellOperationService } } -} +} \ No newline at end of file From 5b06b9d55707d60f5d6010b93749b82f6f836d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Fri, 4 Aug 2023 14:18:10 +0500 Subject: [PATCH 2/3] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudApp/Data/WellOperationDto.cs | 5 -- .../OperationsStatService.cs | 54 ++++++++++--------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/AsbCloudApp/Data/WellOperationDto.cs b/AsbCloudApp/Data/WellOperationDto.cs index b3dc537c..31b50243 100644 --- a/AsbCloudApp/Data/WellOperationDto.cs +++ b/AsbCloudApp/Data/WellOperationDto.cs @@ -89,11 +89,6 @@ namespace AsbCloudApp.Data [DateValidation(GtDate = "2010-01-01T00:00:00")] public DateTime DateStart { get; set; } - /// - /// Дата окончания операции - /// - public DateTime DateEnd => DateStart.AddHours(DurationHours); - /// /// Продолжительность, часы /// diff --git a/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs b/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs index 465f98f9..8be58bc6 100644 --- a/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs @@ -173,43 +173,49 @@ namespace AsbCloudInfrastructure.Services.WellOperationService var timezoneOffsetH = wellService.GetTimezone(well.Id).Hours; statWellDto.Sections = CalcSectionsStats(wellOperations, timezoneOffsetH); statWellDto.Total = GetStatTotal(wellOperations, well.IdState, timezoneOffsetH); - statWellDto.TvdLagPercent = CalcTvdLagPercent(well.IdTelemetry, wellOperations - .Select(x => x.Adapt())); + statWellDto.TvdLagPercent = CalcTvdLagPercent(well.IdTelemetry, wellOperations); return statWellDto; } - private double? CalcTvdLagPercent(int? idTelemetry, IEnumerable wellOperations) - { - if (!idTelemetry.HasValue) - return null; - + private double? CalcTvdLagPercent(int? idTelemetry, IOrderedEnumerable wellOperations) + { var currentDate = DateTime.UtcNow; + + var wellDepth = wellOperations + .LastOrDefault(o => o.IdType == WellOperation.IdOperationTypeFact)?.DepthEnd; + + if (idTelemetry.HasValue) + wellDepth = telemetryDataCache.GetLastOrDefault(idTelemetry.Value)?.WellDepth; - var lastFactOperation = telemetryDataCache.GetLastOrDefault(idTelemetry.Value); - - wellOperations = wellOperations - .Where(o => o.IdType == WellOperation.IdOperationTypePlan) - .OrderBy(o => o.DateEnd); + var planOperations = wellOperations + .Where(o => o.IdType == WellOperation.IdOperationTypePlan) + .OrderBy(o => o.DateStart.AddHours(o.DurationHours)); - var wellOperationFrom = wellOperations - .LastOrDefault(o => o.DateEnd <= currentDate); + var wellOperationFrom = planOperations + .LastOrDefault(o => o.DateStart.AddHours(o.DurationHours) <= currentDate); - var wellOperationTo = wellOperations - .FirstOrDefault(o => o.DateEnd >= currentDate); + var wellOperationTo = planOperations + .FirstOrDefault(o => o.DateStart >= currentDate); var wellOperationDepthFrom = wellOperationFrom?.DepthEnd; - var wellOperationDepthTo = wellOperationTo?.DepthStart ?? wellOperationDepthFrom; + var wellOperationDepthTo = wellOperationTo?.DepthStart ?? wellOperationDepthFrom; - var wellOperationDateFrom = wellOperationFrom?.DateEnd; - var wellOperationDateTo = wellOperationTo?.DateStart ?? wellOperationDateFrom; + var wellOperationDateFrom = wellOperationFrom?.DateStart.AddHours(wellOperationFrom.DurationHours); + var wellOperationDateTo = wellOperationTo?.DateStart ?? currentDate; - var planDepth = (wellOperationDateTo - wellOperationDateFrom)?.TotalHours * - (wellOperationDepthTo - wellOperationDepthFrom) / - (currentDate - wellOperationDateFrom)?.TotalHours + wellOperationDepthFrom; + if (wellOperationDateTo <= wellOperationDateFrom || + currentDate <= wellOperationDateFrom || + (wellOperationDateTo - wellOperationDateFrom)?.TotalHours is null or 0) + return null; - return (1 - lastFactOperation?.WellDepth / planDepth) * 100; - } + var planDepth = (currentDate - wellOperationDateFrom)?.TotalHours * + (wellOperationDepthTo - wellOperationDepthFrom) / + (wellOperationDateTo - wellOperationDateFrom)?.TotalHours + + wellOperationDepthFrom; + + return (1 - wellDepth / planDepth) * 100; + } private IEnumerable CalcSectionsStats(IEnumerable operations, double timezoneOffsetH) { From 05e373e16bfc758333f6c45d8def977d3079e736 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 4 Aug 2023 15:47:56 +0500 Subject: [PATCH 3/3] =?UTF-8?q?OperationsStatService.=20=D0=98=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D1=80=D0=B0=D1=81=D1=87?= =?UTF-8?q?=D0=B5=D1=82=20=D0=BF=D0=BB=D0=B0=D0=BD=D0=BE=D0=B2=D0=BE=D0=B9?= =?UTF-8?q?=20=D0=B3=D0=BB=D1=83=D0=B1=D0=B8=D0=BD=D1=8B,=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BE=D1=82=D1=81=D1=82=D0=B0=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BE=D1=82=20=D0=93=D0=93=D0=94.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OperationsStatService.cs | 1088 +++++++++-------- 1 file changed, 558 insertions(+), 530 deletions(-) diff --git a/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs b/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs index 8be58bc6..e3c63e04 100644 --- a/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs @@ -12,545 +12,573 @@ using System.Threading.Tasks; using AsbCloudApp.Data.SAUB; using AsbCloudInfrastructure.Services.SAUB; -namespace AsbCloudInfrastructure.Services.WellOperationService -{ +namespace AsbCloudInfrastructure.Services.WellOperationService; - public class OperationsStatService : IOperationsStatService - { - private readonly IAsbCloudDbContext db; - private readonly IMemoryCache memoryCache; - private readonly IWellService wellService; +public class OperationsStatService : IOperationsStatService +{ + private readonly IAsbCloudDbContext db; + private readonly IMemoryCache memoryCache; + private readonly IWellService wellService; private readonly TelemetryDataCache telemetryDataCache; public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService, TelemetryDataCache telemetryDataCache) - { - this.db = db; - this.memoryCache = memoryCache; - this.wellService = wellService; + { + this.db = db; + this.memoryCache = memoryCache; + this.wellService = wellService; this.telemetryDataCache = telemetryDataCache; } - public async Task GetOrDefaultStatClusterAsync(int idCluster, int idCompany, CancellationToken token) + public async Task GetOrDefaultStatClusterAsync(int idCluster, int idCompany, CancellationToken token) + { + var cluster = (await memoryCache + .GetOrCreateBasicAsync(db.Set(), 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 { - var cluster = (await memoryCache - .GetOrCreateBasicAsync(db.Set(), 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> GetWellsStatAsync(IEnumerable 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(wells.Count); - - foreach (var well in wells) - { - var statWellDto = await CalcWellStatAsync(well, token); - statsWells.Add(statWellDto); - } - return statsWells; - } - - public async Task 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 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(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 CalcWellStatAsync(Well well, CancellationToken token) - { - var wellType = (await memoryCache - .GetOrCreateBasicAsync(db.Set(), 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; - - var timezoneOffsetH = wellService.GetTimezone(well.Id).Hours; - statWellDto.Sections = CalcSectionsStats(wellOperations, timezoneOffsetH); - statWellDto.Total = GetStatTotal(wellOperations, well.IdState, timezoneOffsetH); - statWellDto.TvdLagPercent = CalcTvdLagPercent(well.IdTelemetry, wellOperations); - - return statWellDto; - } - - private double? CalcTvdLagPercent(int? idTelemetry, IOrderedEnumerable wellOperations) - { - var currentDate = DateTime.UtcNow; - - var wellDepth = wellOperations - .LastOrDefault(o => o.IdType == WellOperation.IdOperationTypeFact)?.DepthEnd; - - if (idTelemetry.HasValue) - wellDepth = telemetryDataCache.GetLastOrDefault(idTelemetry.Value)?.WellDepth; - - var planOperations = wellOperations - .Where(o => o.IdType == WellOperation.IdOperationTypePlan) - .OrderBy(o => o.DateStart.AddHours(o.DurationHours)); - - var wellOperationFrom = planOperations - .LastOrDefault(o => o.DateStart.AddHours(o.DurationHours) <= currentDate); - - var wellOperationTo = planOperations - .FirstOrDefault(o => o.DateStart >= currentDate); - - var wellOperationDepthFrom = wellOperationFrom?.DepthEnd; - var wellOperationDepthTo = wellOperationTo?.DepthStart ?? wellOperationDepthFrom; - - var wellOperationDateFrom = wellOperationFrom?.DateStart.AddHours(wellOperationFrom.DurationHours); - var wellOperationDateTo = wellOperationTo?.DateStart ?? currentDate; - - if (wellOperationDateTo <= wellOperationDateFrom || - currentDate <= wellOperationDateFrom || - (wellOperationDateTo - wellOperationDateFrom)?.TotalHours is null or 0) - return null; - - var planDepth = (currentDate - wellOperationDateFrom)?.TotalHours * - (wellOperationDepthTo - wellOperationDepthFrom) / - (wellOperationDateTo - wellOperationDateFrom)?.TotalHours + - wellOperationDepthFrom; - - return (1 - wellDepth / planDepth) * 100; - } - - private IEnumerable CalcSectionsStats(IEnumerable operations, double timezoneOffsetH) - { - var sectionTypeIds = operations - .Select(o => o.IdWellSectionType) - .Distinct(); - - var sectionTypes = memoryCache - .GetOrCreateBasic(db.Set()) - .Where(s => sectionTypeIds.Contains(s.Id)) - .ToDictionary(s => s.Id); - - var sections = new List(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 GetStatTotal(IEnumerable 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 - { - Plan = CalcStat(operationsPlan, timezoneOffsetH), - Fact = factEnd, - }; - return section; - } - - private static StatOperationsDto? CalcSectionStat(IEnumerable 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 operations, double timezoneOffsetHours) - { - if (!operations.Any()) - return null; - - var races = GetCompleteRaces(operations, timezoneOffsetHours); - - var section = new StatOperationsDto - { - Start = operations.FirstOrDefault()?.DateStart.ToRemoteDateTime(timezoneOffsetHours), - End = operations.Max(o => o.DateStart.ToRemoteDateTime(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 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 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 GetCompleteRaces(IEnumerable operations, double timezoneOffsetH) - { - var races = new List(); - 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(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 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 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 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); - } - - public async Task>> GetTvdAsync(int idWell, CancellationToken token) - { - var wellOperations = await db.WellOperations - .Include(o => o.OperationCategory) - .Include(o => o.WellSectionType) - .Include(o => o.OperationPlan) - .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 == WellOperation.IdOperationTypePlan) - .OrderBy(o => o.DateStart) - .ThenBy(o => o.DepthEnd); - - var wellOperationsFact = wellOperations - .Where(o => o.IdType == WellOperation.IdOperationTypeFact) - .OrderBy(o => o.DateStart) - .ThenBy(o => o.DepthEnd); - - var sectionsIds = wellOperations - .Select(o => o.IdWellSectionType) - .Distinct(); - - var tzOffsetHours = wellService.GetTimezone(idWell).Hours; - - var merged = MergeArraysBySections(sectionsIds, wellOperationsPlan, wellOperationsFact).ToList(); - if (merged.Count ==0) - return Enumerable.Empty>(); - - var tvd = new List>(merged.Count); - var (Plan, Fact) = merged.FirstOrDefault(); - var dateStart = Plan?.DateStart ?? Fact!.DateStart; - int? iLastMatch = null; - int iLastFact = 0; - var nptHours = 0d; - for (int i = 0; i < merged.Count; i++) - { - var item = merged[i]; - var plan = item.Plan; - var fact = item.Fact; - - var planFactPredict = new PlanFactPredictBase(); - if (plan is not null) - { - planFactPredict.Plan = Convert(plan, tzOffsetHours); - planFactPredict.Plan.Day = (planFactPredict.Plan.DateStart - dateStart).TotalDays; - if (fact is not null) - iLastMatch = i; - } - - if (fact is not null) - { - if(WellOperationCategory.NonProductiveTimeSubIds.Contains(fact.IdCategory)) - nptHours += fact.DurationHours; - - planFactPredict.Fact = Convert(fact, tzOffsetHours); - planFactPredict.Fact.Day = (planFactPredict.Fact.DateStart - dateStart).TotalDays; - planFactPredict.Fact.NptHours = nptHours; - iLastFact = i; - } - - tvd.Add(planFactPredict); - } - - if (iLastMatch is null || iLastMatch == merged.Count - 1) - return tvd; - - var lastMatchPlan = merged[iLastMatch.Value].Plan!; - var lastMatchPlanOperationEnd = lastMatchPlan.DateStart.AddHours(lastMatchPlan.DurationHours); - var lastFact = merged[iLastFact].Fact!; - var lastFactDateEnd = lastFact.DateStart.AddHours(lastFact.DurationHours); - var startOffset = lastFactDateEnd - lastMatchPlanOperationEnd; - - for (int i = iLastMatch.Value + 1; i < merged.Count; i++) - { - if (merged[i].Plan is null) - continue; - var predict = Convert(merged[i].Plan!, tzOffsetHours); - predict.IdType = 2; - predict.DateStart = predict.DateStart + startOffset; - predict.Day = (predict.DateStart - dateStart).TotalDays; - tvd[i].Predict = predict; - } - - return tvd; - } - - private static IEnumerable<(WellOperation? Plan, WellOperation? Fact)> MergeArraysBySections( - IEnumerable sectionsIds, - IOrderedEnumerable wellOperationsPlan, - IOrderedEnumerable wellOperationsFact) - { - var merged = new List<(WellOperation? Plan, WellOperation? Fact)>(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 IEnumerable<(WellOperation? Plan, WellOperation? Fact)> MergeArrays(IEnumerable operationsPlan, IEnumerable operationsFact) - { - var operationsFactWithNoPlan = operationsFact.Where(x => x.IdPlan == null).ToArray(); - var operationsFactWithPlan = operationsFact.Where(x => x.IdPlan != null).ToArray(); - - var idsPlanWithFact = operationsFact.Where(x => x.IdPlan is not null).Select(x => x.IdPlan).Distinct(); - var operationsPlanWithNoFact = operationsPlan.Where(x => !idsPlanWithFact.Contains(x.IdPlan)).ToArray(); - - var result = new List<(WellOperation? Plan, WellOperation? Fact)>(operationsFactWithNoPlan.Length + operationsFactWithPlan.Length + operationsPlanWithNoFact.Length); - - 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(); - destination.CategoryName = source.OperationCategory?.Name; - destination.WellSectionTypeName = source.WellSectionType?.Caption; - destination.DateStart = source.DateStart.ToRemoteDateTime(tzOffsetHours); - return destination; - } + Id = idCluster, + Caption = cluster.Caption, + StatsWells = statsWells, + }; + return statClusterDto; } + public async Task> GetWellsStatAsync(IEnumerable 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(wells.Count); + + foreach (var well in wells) + { + var statWellDto = await CalcWellStatAsync(well, token); + statsWells.Add(statWellDto); + } + return statsWells; + } + + public async Task 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 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(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 CalcWellStatAsync(Well well, CancellationToken token) + { + var wellType = (await memoryCache + .GetOrCreateBasicAsync(db.Set(), 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; + + var timezoneOffsetH = wellService.GetTimezone(well.Id).Hours; + statWellDto.Sections = CalcSectionsStats(wellOperations, timezoneOffsetH); + statWellDto.Total = GetStatTotal(wellOperations, well.IdState, timezoneOffsetH); + statWellDto.TvdLagPercent = CalcTvdLagPercent(well.IdTelemetry, wellOperations); + + return statWellDto; + } + + private double? CalcTvdLagPercent(int? idTelemetry, IOrderedEnumerable wellOperations) + { + var currentDate = DateTimeOffset.UtcNow; + + var wellDepth = wellOperations + .LastOrDefault(o => o.IdType == WellOperation.IdOperationTypeFact)?.DepthEnd; + + if (idTelemetry.HasValue) + wellDepth = telemetryDataCache.GetLastOrDefault(idTelemetry.Value)?.WellDepth; + + if (wellDepth is null) + return null; + + var planOperations = wellOperations + .Where(o => o.IdType == WellOperation.IdOperationTypePlan) + .OrderBy(o => o.DateStart.AddHours(o.DurationHours)); + + if (!planOperations.Any()) + return null; + + var planDepth = CalcPlanDepth(planOperations, currentDate); + + if (planDepth is null) + return null; + + if (planDepth == 0d) + return 0d; + + return (1 - wellDepth / planDepth) * 100; + } + + private static double? CalcPlanDepth(IOrderedEnumerable planOperations, DateTimeOffset currentDate) + { + var operationIn = planOperations + .FirstOrDefault(o => o.DateStart <= currentDate && o.DateStart.AddHours(o.DurationHours) >= currentDate); + + if (operationIn is not null) + return Interpolate( + operationIn.DepthStart, + operationIn.DepthEnd, + operationIn.DateStart, + operationIn.DateStart.AddHours(operationIn.DurationHours), + currentDate); + + var operationFrom = planOperations + .LastOrDefault(o => o.DateStart.AddHours(o.DurationHours) <= currentDate); + + var operationTo = planOperations + .FirstOrDefault(o => o.DateStart >= currentDate); + + if (operationFrom is null && operationTo is not null) + return 0d; + else if (operationFrom is not null && operationTo is not null) + { + return Interpolate( + operationFrom.DepthEnd, + operationTo.DepthStart, + operationFrom.DateStart.AddHours(operationTo.DurationHours), + operationTo.DateStart, + currentDate); + } + else if (operationFrom is not null && operationTo is null) + return operationFrom.DepthEnd; + + return null; + } + + private static double Interpolate(double y0, double y1, DateTimeOffset x0, DateTimeOffset x1, DateTimeOffset x) + => y0 + (y1 - y0) * (x - x0).TotalMinutes / (x1 - x0).TotalMinutes; + + private IEnumerable CalcSectionsStats(IEnumerable operations, double timezoneOffsetH) + { + var sectionTypeIds = operations + .Select(o => o.IdWellSectionType) + .Distinct(); + + var sectionTypes = memoryCache + .GetOrCreateBasic(db.Set()) + .Where(s => sectionTypeIds.Contains(s.Id)) + .ToDictionary(s => s.Id); + + var sections = new List(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 GetStatTotal(IEnumerable 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 + { + Plan = CalcStat(operationsPlan, timezoneOffsetH), + Fact = factEnd, + }; + return section; + } + + private static StatOperationsDto? CalcSectionStat(IEnumerable 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 operations, double timezoneOffsetHours) + { + if (!operations.Any()) + return null; + + var races = GetCompleteRaces(operations, timezoneOffsetHours); + + var section = new StatOperationsDto + { + Start = operations.FirstOrDefault()?.DateStart.ToRemoteDateTime(timezoneOffsetHours), + End = operations.Max(o => o.DateStart.ToRemoteDateTime(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 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 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 GetCompleteRaces(IEnumerable operations, double timezoneOffsetH) + { + var races = new List(); + 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(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 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 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 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); + } + + public async Task>> GetTvdAsync(int idWell, CancellationToken token) + { + var wellOperations = await db.WellOperations + .Include(o => o.OperationCategory) + .Include(o => o.WellSectionType) + .Include(o => o.OperationPlan) + .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 == WellOperation.IdOperationTypePlan) + .OrderBy(o => o.DateStart) + .ThenBy(o => o.DepthEnd); + + var wellOperationsFact = wellOperations + .Where(o => o.IdType == WellOperation.IdOperationTypeFact) + .OrderBy(o => o.DateStart) + .ThenBy(o => o.DepthEnd); + + var sectionsIds = wellOperations + .Select(o => o.IdWellSectionType) + .Distinct(); + + var tzOffsetHours = wellService.GetTimezone(idWell).Hours; + + var merged = MergeArraysBySections(sectionsIds, wellOperationsPlan, wellOperationsFact).ToList(); + if (merged.Count ==0) + return Enumerable.Empty>(); + + var tvd = new List>(merged.Count); + var (Plan, Fact) = merged.FirstOrDefault(); + var dateStart = Plan?.DateStart ?? Fact!.DateStart; + int? iLastMatch = null; + int iLastFact = 0; + var nptHours = 0d; + for (int i = 0; i < merged.Count; i++) + { + var item = merged[i]; + var plan = item.Plan; + var fact = item.Fact; + + var planFactPredict = new PlanFactPredictBase(); + if (plan is not null) + { + planFactPredict.Plan = Convert(plan, tzOffsetHours); + planFactPredict.Plan.Day = (planFactPredict.Plan.DateStart - dateStart).TotalDays; + if (fact is not null) + iLastMatch = i; + } + + if (fact is not null) + { + if(WellOperationCategory.NonProductiveTimeSubIds.Contains(fact.IdCategory)) + nptHours += fact.DurationHours; + + planFactPredict.Fact = Convert(fact, tzOffsetHours); + planFactPredict.Fact.Day = (planFactPredict.Fact.DateStart - dateStart).TotalDays; + planFactPredict.Fact.NptHours = nptHours; + iLastFact = i; + } + + tvd.Add(planFactPredict); + } + + if (iLastMatch is null || iLastMatch == merged.Count - 1) + return tvd; + + var lastMatchPlan = merged[iLastMatch.Value].Plan!; + var lastMatchPlanOperationEnd = lastMatchPlan.DateStart.AddHours(lastMatchPlan.DurationHours); + var lastFact = merged[iLastFact].Fact!; + var lastFactDateEnd = lastFact.DateStart.AddHours(lastFact.DurationHours); + var startOffset = lastFactDateEnd - lastMatchPlanOperationEnd; + + for (int i = iLastMatch.Value + 1; i < merged.Count; i++) + { + if (merged[i].Plan is null) + continue; + var predict = Convert(merged[i].Plan!, tzOffsetHours); + predict.IdType = 2; + predict.DateStart = predict.DateStart + startOffset; + predict.Day = (predict.DateStart - dateStart).TotalDays; + tvd[i].Predict = predict; + } + + return tvd; + } + + private static IEnumerable<(WellOperation? Plan, WellOperation? Fact)> MergeArraysBySections( + IEnumerable sectionsIds, + IOrderedEnumerable wellOperationsPlan, + IOrderedEnumerable wellOperationsFact) + { + var merged = new List<(WellOperation? Plan, WellOperation? Fact)>(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 IEnumerable<(WellOperation? Plan, WellOperation? Fact)> MergeArrays(IEnumerable operationsPlan, IEnumerable operationsFact) + { + var operationsFactWithNoPlan = operationsFact.Where(x => x.IdPlan == null).ToArray(); + var operationsFactWithPlan = operationsFact.Where(x => x.IdPlan != null).ToArray(); + + var idsPlanWithFact = operationsFact.Where(x => x.IdPlan is not null).Select(x => x.IdPlan).Distinct(); + var operationsPlanWithNoFact = operationsPlan.Where(x => !idsPlanWithFact.Contains(x.IdPlan)).ToArray(); + + var result = new List<(WellOperation? Plan, WellOperation? Fact)>(operationsFactWithNoPlan.Length + operationsFactWithPlan.Length + operationsPlanWithNoFact.Length); + + 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(); + destination.CategoryName = source.OperationCategory?.Name; + destination.WellSectionTypeName = source.WellSectionType?.Caption; + destination.DateStart = source.DateStart.ToRemoteDateTime(tzOffsetHours); + return destination; + } } \ No newline at end of file