From ee22408225ae6408d970348d3a4c282e3a4e522f Mon Sep 17 00:00:00 2001 From: Frolov-Nikita Date: Wed, 4 Oct 2023 16:41:19 +0500 Subject: [PATCH] =?UTF-8?q?#20644813=20=D1=80=D0=BA=D1=84=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20SubsystemOperationTimeSe?= =?UTF-8?q?rvice=20#20369896=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=81=D0=B2?= =?UTF-8?q?=D0=BE=D0=B9=D1=81=D1=82=D0=B2=D0=B0=20=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B8=20=D0=BF=D0=BE=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetectedOperation/OperationsSummaryDto.cs | 32 + .../Data/Subsystems/SubsystemStatDto.cs | 15 +- .../DetectedOperationSummaryRequest.cs | 53 ++ .../Requests/SubsystemOperationTimeRequest.cs | 3 +- .../Services/IDetectedOperationService.cs | 6 +- .../ISubsystemOperationTimeService.cs | 4 +- .../DetectedOperationService.cs | 69 +- .../SubsystemOperationTimeService.cs | 750 +++++++++--------- 8 files changed, 524 insertions(+), 408 deletions(-) create mode 100644 AsbCloudApp/Data/DetectedOperation/OperationsSummaryDto.cs create mode 100644 AsbCloudApp/Requests/DetectedOperationSummaryRequest.cs diff --git a/AsbCloudApp/Data/DetectedOperation/OperationsSummaryDto.cs b/AsbCloudApp/Data/DetectedOperation/OperationsSummaryDto.cs new file mode 100644 index 00000000..74f755cc --- /dev/null +++ b/AsbCloudApp/Data/DetectedOperation/OperationsSummaryDto.cs @@ -0,0 +1,32 @@ +namespace AsbCloudApp.Data.DetectedOperation; + +/// +/// Статистика по операциям +/// +public class OperationsSummaryDto +{ + /// + /// Id телеметрии + /// + public int IdTelemetry { get; set; } + + /// + /// Id названия/описания операции + /// + public int IdCategory { get; set; } + + /// + /// Количество операций + /// + public int Count { get; set; } + + /// + /// Cумма проходок операций + /// + public double SumDepthIntervals { get; set; } + + /// + /// Cумма продолжительностей операций + /// + public double SumDurationHours { get; set; } +} diff --git a/AsbCloudApp/Data/Subsystems/SubsystemStatDto.cs b/AsbCloudApp/Data/Subsystems/SubsystemStatDto.cs index aec6c6b8..da9c98cb 100644 --- a/AsbCloudApp/Data/Subsystems/SubsystemStatDto.cs +++ b/AsbCloudApp/Data/Subsystems/SubsystemStatDto.cs @@ -23,13 +23,20 @@ namespace AsbCloudApp.Data.Subsystems /// public double KUsage { get; set; } /// - /// сумма изменения глубин + /// сумма изменения глубин при включеной подсистеме /// public double SumDepthInterval { get; set; } /// - /// количество операций + /// сумма проходок автоопределенных операций выполняемых подсистемой /// - public int OperationCount { get; set; } - + public double SumOperationDepthInterval { get; set; } + /// + /// сумма продолжительности автоопределенных операций выполняемых подсистемой + /// + public double SumOperationDurationHours { get; set; } + /// + /// количество включений подсистемы + /// + public int OperationCount { get; set; } } } diff --git a/AsbCloudApp/Requests/DetectedOperationSummaryRequest.cs b/AsbCloudApp/Requests/DetectedOperationSummaryRequest.cs new file mode 100644 index 00000000..2b362f28 --- /dev/null +++ b/AsbCloudApp/Requests/DetectedOperationSummaryRequest.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudApp.Requests; + +/// +/// Запрос на получение обобщенных данных по операцим +/// +public class DetectedOperationSummaryRequest +{ + /// + /// Список id телеметрий + /// пустой список - нет фильтрации + /// + public IEnumerable IdsTelemetries { get;set;} = Enumerable.Empty(); + + /// + /// Список id категорий операций + /// пустой список - нет фильтрации + /// + public IEnumerable IdsOperationCategories { get; set; } = Enumerable.Empty(); + + /// + /// Больше или равно даты начала операции + /// + public DateTimeOffset? GeDateStart {get;set;} + + /// + /// Меньше или равно даты начала операции + /// + public DateTimeOffset? LeDateStart { get; set; } + + /// + /// Меньше или равно даты окончания операции + /// + public DateTimeOffset? LeDateEnd { get; set; } + + /// + /// Больше или равно глубины начала операции + /// + public double? GeDepthStart { get; set; } + + /// + /// Меньше или равно глубины начала операции + /// + public double? LeDepthStart { get; set; } + + /// + /// Меньше или равно глубины окончания операции + /// + public double? LeDepthEnd { get; set; } +} diff --git a/AsbCloudApp/Requests/SubsystemOperationTimeRequest.cs b/AsbCloudApp/Requests/SubsystemOperationTimeRequest.cs index 9080b596..02db1042 100644 --- a/AsbCloudApp/Requests/SubsystemOperationTimeRequest.cs +++ b/AsbCloudApp/Requests/SubsystemOperationTimeRequest.cs @@ -26,7 +26,7 @@ namespace AsbCloudApp.Requests /// /// Больше или равно дате /// - public DateTime? GtDate { get; set; } + public DateTime? GtDate { get; set; }//TODO: its Ge* /// /// Меньше или равно дате @@ -43,6 +43,7 @@ namespace AsbCloudApp.Requests /// public double? LtDepth { get; set; } + //TODO: Replace modes by DateTimeOffset LeDateStart, LeDateEnd /// /// информация попадает в выборку, если интервал выборки частично или полностью пересекается с запрашиваемым интервалом /// diff --git a/AsbCloudApp/Services/IDetectedOperationService.cs b/AsbCloudApp/Services/IDetectedOperationService.cs index 901f7b3f..120cfef8 100644 --- a/AsbCloudApp/Services/IDetectedOperationService.cs +++ b/AsbCloudApp/Services/IDetectedOperationService.cs @@ -42,12 +42,10 @@ namespace AsbCloudApp.Services /// /// Получить интервалы глубин по всем скважинам /// - /// список ИД телеметрий активных скважин - /// - /// + /// /// /// кортеж - ид телеметрии, интервалы глубины забоя (ротор,слайд) - Task> GetDepthIntervalAllOperationsAsync(IEnumerable telemetryIds,DateTimeOffset gtDate, DateTimeOffset ltDate, CancellationToken token); + Task> GetOperationSummaryAsync(DetectedOperationSummaryRequest request, CancellationToken token); /// /// Удалить операции diff --git a/AsbCloudApp/Services/Subsystems/ISubsystemOperationTimeService.cs b/AsbCloudApp/Services/Subsystems/ISubsystemOperationTimeService.cs index 774b8754..67b29673 100644 --- a/AsbCloudApp/Services/Subsystems/ISubsystemOperationTimeService.cs +++ b/AsbCloudApp/Services/Subsystems/ISubsystemOperationTimeService.cs @@ -19,7 +19,7 @@ namespace AsbCloudApp.Services.Subsystems /// /// /// - Task?> GetStatAsync(SubsystemOperationTimeRequest request, CancellationToken token); + Task> GetStatAsync(SubsystemOperationTimeRequest request, CancellationToken token); /// /// Удаление наработки по подсистемам. @@ -37,7 +37,7 @@ namespace AsbCloudApp.Services.Subsystems /// /// /// - Task?> GetOperationTimeAsync(SubsystemOperationTimeRequest request, CancellationToken token); + Task> GetOperationTimeAsync(SubsystemOperationTimeRequest request, CancellationToken token); /// /// Временной диапазон за который есть статистика работы подсистем diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs index 701cfd08..c3246e05 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs @@ -67,27 +67,58 @@ namespace AsbCloudInfrastructure.Services.DetectOperations return dtos; } - public async Task> GetDepthIntervalAllOperationsAsync(IEnumerable telemetryIds, DateTimeOffset gtDate, DateTimeOffset ltDate, CancellationToken token) + public async Task> GetOperationSummaryAsync(DetectedOperationSummaryRequest request, CancellationToken token) { var query = db.Set() - .Include(o => o.OperationCategory) - .Where(o => o.DateStart >= gtDate) - .Where(o => o.DateEnd <= ltDate) - .Where(o => telemetryIds.Contains(o.IdTelemetry)) - .GroupBy(g => g.IdTelemetry) - .Select(g => new - { - IdTelemetry = g.Key, - RotorDepthInterval = g.Where(o => o.IdCategory == WellOperationCategory.IdRotor).Sum(o => o.DepthEnd - o.DepthStart), - SlideDepthInterval = g.Where(o => o.IdCategory == WellOperationCategory.IdSlide).Sum(o => o.DepthEnd - o.DepthStart) - }); - var data = await query.ToArrayAsync(token); - var result = data.Select(g => - ( - g.IdTelemetry, - g.RotorDepthInterval, - g.SlideDepthInterval - )); + .AsNoTracking(); + + if (request.IdsTelemetries.Any()) + query = query.Where(operation => request.IdsTelemetries.Contains(operation.IdTelemetry)); + + if (request.IdsOperationCategories.Any()) + query = query.Where(operation => request.IdsOperationCategories.Contains(operation.IdCategory)); + + if (request.GeDateStart.HasValue) + { + var geDateStart = request.GeDateStart.Value.ToUniversalTime(); + query = query.Where(operation => operation.DateStart >= geDateStart); + } + + if (request.LeDateStart.HasValue) + { + var leDateStart = request.LeDateStart.Value.ToUniversalTime(); + query = query.Where(operation => operation.DateStart <= leDateStart); + } + + if (request.LeDateEnd.HasValue) + { + var leDateEnd = request.LeDateEnd.Value.ToUniversalTime(); + query = query.Where(operation => operation.DateEnd <= leDateEnd); + } + + if (request.GeDepthStart.HasValue) + query = query.Where(operation => operation.DepthStart >= request.GeDepthStart.Value); + + if (request.LeDepthStart.HasValue) + query = query.Where(operation => operation.DepthStart <= request.LeDepthStart.Value); + + if (request.LeDepthEnd.HasValue) + query = query.Where(operation => operation.DepthEnd <= request.LeDepthEnd.Value); + + var queryGroup = query + .GroupBy(operation => new { operation.IdTelemetry, operation.IdCategory }) + .Select(group => new OperationsSummaryDto + { + IdTelemetry = group.Key.IdTelemetry, + IdCategory = group.Key.IdCategory, + Count = group.Count(), + SumDepthIntervals = group.Sum(operation => operation.DepthEnd - operation.DepthStart), + SumDurationHours = group.Sum(operation => (operation.DateEnd - operation.DateStart).TotalHours) + }) + .OrderBy(summ => summ.IdTelemetry) + .ThenBy(summ => summ.IdCategory); + + var result = await queryGroup.ToArrayAsync(token); return result; } diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeService.cs index f3b079eb..e40c1a4d 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeService.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeService.cs @@ -16,385 +16,379 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Services.Subsystems +namespace AsbCloudInfrastructure.Services.Subsystems; + +internal class SubsystemOperationTimeService : ISubsystemOperationTimeService { - internal class SubsystemOperationTimeService : ISubsystemOperationTimeService + private readonly IAsbCloudDbContext db; + private readonly IWellService wellService; + private readonly ICrudRepository subsystemService; + private readonly IDetectedOperationService detectedOperationService; + public const int IdSubsystemAKB = 1; + public const int IdSubsystemAKBRotor = 11; + public const int IdSubsystemAKBSlide = 12; + public const int IdSubsystemMSE = 2; + public const int IdSubsystemSpin = 65536; + public const int IdSubsystemTorque = 65537; + + public SubsystemOperationTimeService(IAsbCloudDbContext db, IWellService wellService, ICrudRepository subsystemService, IDetectedOperationService detectedOperationService) { - private readonly IAsbCloudDbContext db; - private readonly IWellService wellService; - private readonly ICrudRepository subsystemService; - private readonly IDetectedOperationService detectedOperationService; - public const int IdSubsystemAKB = 1; - public const int IdSubsystemAKBRotor = 11; - public const int IdSubsystemAKBSlide = 12; - public const int IdSubsystemMSE = 2; - public const int IdSubsystemSpin = 65536; - public const int IdSubsystemTorque = 65537; - public SubsystemOperationTimeService(IAsbCloudDbContext db, IWellService wellService, ICrudRepository subsystemService, IDetectedOperationService detectedOperationService) - { - this.db = db; - this.wellService = wellService; - this.subsystemService = subsystemService; - this.detectedOperationService = detectedOperationService; - } - - /// - public async Task DeleteAsync(SubsystemOperationTimeRequest request, CancellationToken token) - { - var well = await wellService.GetOrDefaultAsync(request.IdWell, token); - if (well?.IdTelemetry is null || well.Timezone is null) - return 0; - var query = BuildQuery(request); - if (query is null) - return 0; - db.SubsystemOperationTimes.RemoveRange(query); - return await db.SaveChangesAsync(token); - } - - /// - public async Task?> GetOperationTimeAsync(SubsystemOperationTimeRequest request, CancellationToken token) - { - var well = await wellService.GetOrDefaultAsync(request.IdWell, token); - if (well?.IdTelemetry is null || well.Timezone is null) - return null; - - var query = BuildQuery(request); - - if (query is null) - return null; - - IEnumerable data = await query.ToListAsync(token); - - if (request.SelectMode == SubsystemOperationTimeRequest.SelectModeInner) - { - if (request.GtDate is not null) - data = data.Where(o => o.DateStart >= request.GtDate.Value); - - if (request.LtDate is not null) - data = data.Where(o => o.DateEnd <= request.LtDate.Value); - } - else if (request.SelectMode == SubsystemOperationTimeRequest.SelectModeTrim) - { - var begin = request.GtDate?.ToUtcDateTimeOffset(well.Timezone.Hours); - var end = request.LtDate?.ToUtcDateTimeOffset(well.Timezone.Hours); - data = TrimOperation(data, begin, end); - } - - var dtos = data.Select(o => Convert(o, well.Timezone.Hours)); - return dtos; - } - - /// - public async Task?> GetStatAsync(SubsystemOperationTimeRequest request, CancellationToken token) - { - request.SelectMode = SubsystemOperationTimeRequest.SelectModeTrim; - var data = await GetOperationTimeAsync(request, token); - if (data is null) - return null; - - var detectedOperationsRequest = new DetectedOperationRequest() - { - IdWell = request.IdWell, - IdsCategories = new int[] { - WellOperationCategory.IdRotor, WellOperationCategory.IdSlide, - }, - LtDate = request.LtDate, - GtDate = request.GtDate, - }; - var detectedOperations = await detectedOperationService.GetOperationsAsync(detectedOperationsRequest, token); - if(detectedOperations?.Any() != true) - return null; - var depthInterval = GetDepthInterval(detectedOperations); - - var statList = CalcStat(data,depthInterval); - return statList; - } - - private static IEnumerable TrimOperation(IEnumerable data, DateTimeOffset? gtDate, DateTimeOffset? ltDate) - { - if (!ltDate.HasValue && !gtDate.HasValue) - return data.Select(d => d.Adapt()); - - var items = data.Select((item) => - { - var operationTime = item.Adapt(); - if (!(item.DepthStart.HasValue && item.DepthEnd.HasValue)) - return operationTime; - - var dateDiff = (item.DateEnd - item.DateStart).TotalSeconds; - var depthDiff = item.DepthEnd.Value - item.DepthStart.Value; - var a = depthDiff / dateDiff; - var b = item.DepthStart.Value; - - if (gtDate.HasValue && item.DateStart < gtDate.Value) - { - operationTime.DateStart = gtDate.Value; - var x = (gtDate.Value - item.DateStart).TotalSeconds; - operationTime.DepthStart = (float)(a * x + b); - } - if (ltDate.HasValue && item.DateEnd > ltDate.Value) - { - operationTime.DateEnd = ltDate.Value; - var x = (ltDate.Value - item.DateStart).TotalSeconds; - operationTime.DepthEnd = (float)(a * x + b); - } - return operationTime; - }); - - return items; - } - - private IEnumerable CalcStat( - IEnumerable dtos, - (double depthIntervalRotor, double depthIntervalSlide) depthInterval) - { - var groupedDataSubsystems = dtos - .OrderBy(o => o.Id) - .GroupBy(o => o.IdSubsystem); - var periodGroupTotal = dtos.Sum(o => (o.DateEnd - o.DateStart).TotalHours); - - var result = groupedDataSubsystems.Select(g => - { - var depthIntervalSubsystem = GetDepthIntervalSubsystem(g.Key, depthInterval); - var periodGroup = g.Sum(o => (o.DateEnd - o.DateStart).TotalHours); - var periodGroupDepth = g.Sum(o => o.DepthEnd - o.DepthStart); - var subsystemStat = new SubsystemStatDto() - { - IdSubsystem = g.Key, - SubsystemName = subsystemService.GetOrDefault(g.Key)?.Name ?? "unknown", - UsedTimeHours = periodGroup, - //% использования = суммарная проходка АПД в слайде - /// суммарную проходку автоопределенных операций в слайде. - KUsage = periodGroupDepth / depthIntervalSubsystem, - SumDepthInterval = periodGroupDepth, - OperationCount = g.Count(), - }; - if (subsystemStat.KUsage > 1) - subsystemStat.KUsage = 1; - return subsystemStat; - }); - - var apdParts = result.Where(x => x.IdSubsystem == 11 || x.IdSubsystem == 12); - if (apdParts.Any()) - { - var apdSum = new SubsystemStatDto() - { - IdSubsystem = IdSubsystemAKB, - SubsystemName = "АПД", - UsedTimeHours = apdParts.Sum(part => part.UsedTimeHours), - KUsage = apdParts.Sum(part => part.SumDepthInterval) / GetDepthIntervalSubsystem(IdSubsystemAKB, depthInterval), - SumDepthInterval = apdParts.Sum(part => part.SumDepthInterval), - OperationCount = apdParts.Sum(part => part.OperationCount), - }; - result = result.Append(apdSum).OrderBy(m => m.IdSubsystem); - } - - return result; - } - - private static (double depthIntervalRotor, double depthIntervalSlide) GetDepthInterval (IEnumerable detectedOperations) - { - var depthIntervalRotor = detectedOperations.Where(o => o.IdCategory == WellOperationCategory.IdRotor) - .Sum(o => o.DepthEnd - o.DepthStart); - var depthIntervalSlide = detectedOperations.Where(o => o.IdCategory == WellOperationCategory.IdSlide) - .Sum(o => o.DepthEnd - o.DepthStart); - var depthInterval = (depthIntervalRotor, depthIntervalSlide); - - return depthInterval; - } - - private static double GetDepthIntervalSubsystem(int idSubsystem, (double depthIntervalRotor, double depthIntervalSlide) depthInterval) - { - var depthIntervalSubsystem = 0d; - //AKB - MSE - if (idSubsystem == IdSubsystemAKB || idSubsystem == IdSubsystemMSE) - { - depthIntervalSubsystem = depthInterval.depthIntervalRotor + depthInterval.depthIntervalSlide; - } - //AKB - Rotor - if (idSubsystem == IdSubsystemAKBRotor) - { - depthIntervalSubsystem = depthInterval.depthIntervalRotor; - } - //AKB - Slide - if (idSubsystem == IdSubsystemAKBSlide) - { - depthIntervalSubsystem = depthInterval.depthIntervalSlide; - } - //Spin - if (idSubsystem == IdSubsystemSpin) - { - depthIntervalSubsystem = depthInterval.depthIntervalSlide; - } - //Torque - if (idSubsystem == IdSubsystemTorque) - { - depthIntervalSubsystem = depthInterval.depthIntervalRotor; - } - return depthIntervalSubsystem; - - } - - /// - public async Task> GetStatByActiveWells(int idCompany, DateTime? gtDate, DateTime? ltDate, CancellationToken token) - { - var activeWells = await wellService.GetAsync(new() { IdCompany = idCompany, IdState = 1 }, token); - var result = await GetStatAsync(activeWells, gtDate, ltDate, token); - return result; - } - - /// - public async Task> GetStatByActiveWells(IEnumerable wellIds, CancellationToken token) - { - var activeWells = await wellService.GetAsync(new() { Ids = wellIds, IdState = 1 }, token); - var result = await GetStatAsync(activeWells, null, null, token); - return result; - } - - private async Task> GetStatAsync(IEnumerable wells, DateTime? gtDate, DateTime? ltDate, CancellationToken token) - { - if (!wells.Any()) - return Enumerable.Empty(); - - var hoursOffset = wells - .FirstOrDefault(well => well.Timezone is not null) - ?.Timezone.Hours - ?? 5d; - - var beginUTC = gtDate.HasValue - ? gtDate.Value.ToUtcDateTimeOffset(hoursOffset) - : db.SubsystemOperationTimes.Min(s => s.DateStart) - .DateTime - .ToUtcDateTimeOffset(hoursOffset); - - var endUTC = ltDate.HasValue - ? ltDate.Value.ToUtcDateTimeOffset(hoursOffset) - : db.SubsystemOperationTimes.Max(s => s.DateEnd) - .DateTime - .ToUtcDateTimeOffset(hoursOffset); - - var telemetryIds = wells - .Where(w => w.IdTelemetry is not null) - .Select(w => w.IdTelemetry) - .Distinct(); - - var query = db.SubsystemOperationTimes - .Where(o => telemetryIds.Contains(o.IdTelemetry) && - o.DateStart >= beginUTC && - o.DateEnd <= endUTC) - .AsNoTracking(); - - var subsystemsOperationTime = await query.ToListAsync(token); - - var depthIntervals = await detectedOperationService - .GetDepthIntervalAllOperationsAsync(telemetryIds, beginUTC, endUTC, token); - - var result = wells - .Select(well => { - var dtos = subsystemsOperationTime - .Where(s => s.IdTelemetry == well.IdTelemetry) - .Select(s => Convert(s, well.Timezone.Hours)); - - var (idTelemetry, depthIntervalRotor, depthIntervalSlide) = depthIntervals - .FirstOrDefault(i => i.idTelemetry == well.IdTelemetry); - - var subsystemStat = idTelemetry > 0 && dtos.Any() - ? CalcStat(dtos, (depthIntervalRotor, depthIntervalSlide)) - : Enumerable.Empty(); - - return new SubsystemActiveWellStatDto - { - Well = well, - SubsystemAKB = subsystemStat.FirstOrDefault(s => s.IdSubsystem == IdSubsystemAKB), - SubsystemMSE = subsystemStat.FirstOrDefault(s => s.IdSubsystem == IdSubsystemMSE), - SubsystemSpinMaster = subsystemStat.FirstOrDefault(s => s.IdSubsystem == IdSubsystemSpin), - SubsystemTorqueMaster = subsystemStat.FirstOrDefault(s => s.IdSubsystem == IdSubsystemTorque), - }; - }); - - return result; - } - - /// - public async Task GetDateRangeOperationTimeAsync(SubsystemOperationTimeRequest request, CancellationToken token) - { - var query = BuildQuery(request); - if (query is null) - { - return null; - } - var result = await query - .GroupBy(o => o.IdTelemetry) - .Select(g => new DatesRangeDto - { - From = g.Min(o => o.DateStart).DateTime, - To = g.Max(o => o.DateEnd).DateTime - }) - .FirstOrDefaultAsync(token); - return result; - } - - private IQueryable BuildQuery(SubsystemOperationTimeRequest request) - { - var well = wellService.GetOrDefault(request.IdWell) - ?? throw new ArgumentInvalidException(nameof(request.IdWell), $"Not valid IdWell = {request.IdWell}"); - - var query = db.SubsystemOperationTimes - .Include(o => o.Subsystem) - .Where(o => o.IdTelemetry == well.IdTelemetry) - .AsNoTracking(); - - if (request.IdsSubsystems.Any()) - query = query.Where(o => request.IdsSubsystems.Contains(o.IdSubsystem)); - - // # Dates range condition - // [GtDate LtDate] - // [DateStart DateEnd] [DateStart DateEnd] - if (request.GtDate.HasValue) - { - DateTimeOffset gtDate = request.GtDate.Value.ToUtcDateTimeOffset(well.Timezone.Hours); - query = query.Where(o => o.DateEnd >= gtDate); - } - - if (request.LtDate.HasValue) - { - DateTimeOffset ltDate = request.LtDate.Value.ToUtcDateTimeOffset(well.Timezone.Hours); - query = query.Where(o => o.DateStart <= ltDate); - } - - if (request.GtDepth.HasValue) - query = query.Where(o => o.DepthEnd >= request.GtDepth.Value); - - if (request.LtDepth.HasValue) - query = query.Where(o => o.DepthStart <= request.LtDepth.Value); - - if (request?.SortFields?.Any() == true) - { - query = query.SortBy(request.SortFields); - } - else - { - query = query - .OrderBy(o => o.DateStart) - .ThenBy(o => o.DepthStart); - } - - if (request?.Skip > 0) - query = query.Skip((int)request.Skip); - - if (request?.Take > 0) - query = query.Take((int)request.Take); - - return query; - } - - private static SubsystemOperationTimeDto Convert(SubsystemOperationTime operationTime, double? timezoneHours = null) - { - var dto = operationTime.Adapt(); - var hours = timezoneHours ?? operationTime.Telemetry.TimeZone.Hours; - dto.DateStart = operationTime.DateStart.ToRemoteDateTime(hours); - dto.DateEnd = operationTime.DateEnd.ToRemoteDateTime(hours); - return dto; - } + this.db = db; + this.wellService = wellService; + this.subsystemService = subsystemService; + this.detectedOperationService = detectedOperationService; } + + /// + public async Task DeleteAsync(SubsystemOperationTimeRequest request, CancellationToken token) + { + var well = await wellService.GetOrDefaultAsync(request.IdWell, token) + ?? throw new ArgumentInvalidException(nameof(request.IdWell), $"Well Id: {request.IdWell} does not exist"); + + var query = BuildQuery(request, well); + db.SubsystemOperationTimes.RemoveRange(query); + return await db.SaveChangesAsync(token); + } + + /// + public async Task> GetOperationTimeAsync(SubsystemOperationTimeRequest request, CancellationToken token) + { + var well = await wellService.GetOrDefaultAsync(request.IdWell, token) + ?? throw new ArgumentInvalidException(nameof(request.IdWell), $"Well Id: {request.IdWell} does not exist"); + + var dtos = await GetOperationTimeAsync(request, well, token); + return dtos; + } + + private async Task> GetOperationTimeAsync(SubsystemOperationTimeRequest request, WellDto well, CancellationToken token) + { + var query = BuildQuery(request, well); + IEnumerable data = await query.ToListAsync(token); + + if (request.SelectMode == SubsystemOperationTimeRequest.SelectModeInner) + { + if (request.GtDate is not null) + data = data.Where(o => o.DateStart >= request.GtDate.Value); + + if (request.LtDate is not null) + data = data.Where(o => o.DateEnd <= request.LtDate.Value); + } + else if (request.SelectMode == SubsystemOperationTimeRequest.SelectModeTrim) + { + var begin = request.GtDate?.ToUtcDateTimeOffset(well.Timezone.Hours); + var end = request.LtDate?.ToUtcDateTimeOffset(well.Timezone.Hours); + data = TrimOperation(data, begin, end); + } + + var dtos = data.Select(o => Convert(o, well.Timezone.Hours)); + return dtos; + } + + /// + public async Task> GetStatAsync(SubsystemOperationTimeRequest request, CancellationToken token) + { + var well = await wellService.GetOrDefaultAsync(request.IdWell, token) + ?? throw new ArgumentInvalidException(nameof(request.IdWell), $"Well Id: {request.IdWell} does not exist"); + + request.SelectMode = SubsystemOperationTimeRequest.SelectModeTrim; + var subsystemsTimes = await GetOperationTimeAsync(request, well, token); + if (subsystemsTimes is null) + return Enumerable.Empty(); + + var detectedOperationSummaryRequest = new DetectedOperationSummaryRequest() + { + IdsTelemetries = new[] {well.IdTelemetry!.Value}, + IdsOperationCategories = WellOperationCategory.MechanicalDrillingSubIds, + + GeDateStart = request.GtDate, + LeDateStart = request.LtDate, + + GeDepthStart = request.GtDepth, + LeDepthStart = request.LtDepth, + }; + var operationsSummaries = await detectedOperationService.GetOperationSummaryAsync(detectedOperationSummaryRequest, token); + if(!operationsSummaries.Any()) + return Enumerable.Empty(); + + var statList = CalcStat(subsystemsTimes, operationsSummaries); + return statList; + } + + private static IEnumerable TrimOperation(IEnumerable data, DateTimeOffset? gtDate, DateTimeOffset? ltDate) + { + if (!ltDate.HasValue && !gtDate.HasValue) + return data.Select(d => d.Adapt()); + + var items = data.Select((item) => + { + var operationTime = item.Adapt(); + if (!(item.DepthStart.HasValue && item.DepthEnd.HasValue)) + return operationTime; + + var dateDiff = (item.DateEnd - item.DateStart).TotalSeconds; + var depthDiff = item.DepthEnd.Value - item.DepthStart.Value; + var a = depthDiff / dateDiff; + var b = item.DepthStart.Value; + + if (gtDate.HasValue && item.DateStart < gtDate.Value) + { + operationTime.DateStart = gtDate.Value; + var x = (gtDate.Value - item.DateStart).TotalSeconds; + operationTime.DepthStart = (float)(a * x + b); + } + if (ltDate.HasValue && item.DateEnd > ltDate.Value) + { + operationTime.DateEnd = ltDate.Value; + var x = (ltDate.Value - item.DateStart).TotalSeconds; + operationTime.DepthEnd = (float)(a * x + b); + } + return operationTime; + }); + + return items; + } + + private IEnumerable CalcStat( + IEnumerable subsystemsTimes, + IEnumerable operationsSummaries) + { + var groupedSubsystemsTimes = subsystemsTimes + .OrderBy(o => o.Id) + .GroupBy(o => o.IdSubsystem); + + var periodGroupTotal = subsystemsTimes.Sum(o => (o.DateEnd - o.DateStart).TotalHours); + + var result = groupedSubsystemsTimes.Select(g => + { + var periodGroup = g.Sum(o => (o.DateEnd - o.DateStart).TotalHours); + var periodGroupDepth = g.Sum(o => o.DepthEnd - o.DepthStart); + var (sumOprationsDepth, sumOprationsDurationHours) = AggregateOperationsSummaries(g.Key, operationsSummaries); + var subsystemStat = new SubsystemStatDto() + { + IdSubsystem = g.Key, + SubsystemName = subsystemService.GetOrDefault(g.Key)?.Name ?? "unknown", + UsedTimeHours = periodGroup, + SumOperationDepthInterval = sumOprationsDepth, + SumOperationDurationHours = sumOprationsDurationHours, + SumDepthInterval = periodGroupDepth, + KUsage = periodGroupDepth / sumOprationsDepth, + OperationCount = g.Count(), + }; + if (subsystemStat.KUsage > 1) + subsystemStat.KUsage = 1; + return subsystemStat; + }); + + var apdParts = result.Where(x => x.IdSubsystem == 11 || x.IdSubsystem == 12); + if (apdParts.Any()) + { + var apdSum = new SubsystemStatDto() + { + IdSubsystem = IdSubsystemAKB, + SubsystemName = "АПД", + UsedTimeHours = apdParts.Sum(part => part.UsedTimeHours), + SumOperationDepthInterval = apdParts.Sum(part => part.SumOperationDepthInterval), + SumOperationDurationHours = apdParts.Sum(part => part.SumOperationDurationHours), + SumDepthInterval = apdParts.Sum(part => part.SumDepthInterval), + OperationCount = apdParts.Sum(part => part.OperationCount), + }; + apdSum.KUsage = apdSum.SumDepthInterval / apdSum.SumOperationDepthInterval; + if (apdSum.KUsage > 1) + apdSum.KUsage = 1; + result = result.Append(apdSum).OrderBy(m => m.IdSubsystem); + } + + return result; + } + + private static (double SumDepth, double SumDurationHours) AggregateOperationsSummaries(int idSubsystem, IEnumerable operationsSummaries) + => idSubsystem switch + { + IdSubsystemAKBRotor or IdSubsystemTorque => CalcOperationSummariesByCategories(operationsSummaries, WellOperationCategory.IdRotor), + IdSubsystemAKBSlide or IdSubsystemSpin => CalcOperationSummariesByCategories(operationsSummaries, WellOperationCategory.IdSlide), + IdSubsystemAKB or IdSubsystemMSE => CalcOperationSummariesByCategories(operationsSummaries, WellOperationCategory.IdRotor, WellOperationCategory.IdSlide), + _ => throw new ArgumentException($"idSubsystem: {idSubsystem} does not supported in this method", nameof(idSubsystem)), + }; + + private static (double SumDepth, double SumDurationHours) CalcOperationSummariesByCategories( + IEnumerable operationsSummaries, + params int[] idsOperationCategories) + { + var filtered = operationsSummaries.Where(sum => idsOperationCategories.Contains(sum.IdCategory)); + var sumDepth = filtered.Sum(summ => summ.SumDepthIntervals); + var sumDurationHours = filtered.Sum(summ => summ.SumDurationHours); + return (sumDepth, sumDurationHours); + } + + /// + public async Task> GetStatByActiveWells(int idCompany, DateTime? gtDate, DateTime? ltDate, CancellationToken token) + { + var activeWells = await wellService.GetAsync(new() { IdCompany = idCompany, IdState = 1 }, token); + var result = await GetStatAsync(activeWells, gtDate, ltDate, token); + return result; + } + + /// + public async Task> GetStatByActiveWells(IEnumerable wellIds, CancellationToken token) + { + var activeWells = await wellService.GetAsync(new() { Ids = wellIds, IdState = 1 }, token); + var result = await GetStatAsync(activeWells, null, null, token); + return result; + } + + private async Task> GetStatAsync(IEnumerable wells, DateTime? gtDate, DateTime? ltDate, CancellationToken token) + { + if (!wells.Any()) + return Enumerable.Empty(); + + var hoursOffset = wells + .FirstOrDefault(well => well.Timezone is not null) + ?.Timezone.Hours + ?? 5d; + + var beginUTC = gtDate.HasValue + ? gtDate.Value.ToUtcDateTimeOffset(hoursOffset) + : db.SubsystemOperationTimes.Min(s => s.DateStart) + .DateTime + .ToUtcDateTimeOffset(hoursOffset); + + var endUTC = ltDate.HasValue + ? ltDate.Value.ToUtcDateTimeOffset(hoursOffset) + : db.SubsystemOperationTimes.Max(s => s.DateEnd) + .DateTime + .ToUtcDateTimeOffset(hoursOffset); + + IEnumerable idsTelemetries = wells + .Where(w => w.IdTelemetry is not null) + .Select(w => w.IdTelemetry!.Value) + .Distinct(); + + var query = db.SubsystemOperationTimes + .Where(o => idsTelemetries.Contains(o.IdTelemetry) && + o.DateStart >= beginUTC && + o.DateEnd <= endUTC) + .AsNoTracking(); + + var subsystemsOperationTime = await query.ToArrayAsync(token); + + var operationSummaries = await detectedOperationService + .GetOperationSummaryAsync(new () + { + IdsTelemetries = idsTelemetries, + IdsOperationCategories = WellOperationCategory.MechanicalDrillingSubIds, + GeDateStart = beginUTC, + LeDateEnd = endUTC, + }, token); + + var result = wells + .Select(well => { + var dtos = subsystemsOperationTime + .Where(s => s.IdTelemetry == well.IdTelemetry) + .Select(s => Convert(s, well.Timezone.Hours)); + + var wellStat = new SubsystemActiveWellStatDto{ Well = well }; + + var telemetryOperationSummaries = operationSummaries.Where(summ => summ.IdTelemetry == well.IdTelemetry); + if (telemetryOperationSummaries.Any()) + { + var subsystemStat = CalcStat(dtos, telemetryOperationSummaries); + if (subsystemStat.Any()) + { + wellStat.SubsystemAKB = subsystemStat.FirstOrDefault(s => s.IdSubsystem == IdSubsystemAKB); + wellStat.SubsystemMSE = subsystemStat.FirstOrDefault(s => s.IdSubsystem == IdSubsystemMSE); + wellStat.SubsystemSpinMaster = subsystemStat.FirstOrDefault(s => s.IdSubsystem == IdSubsystemSpin); + wellStat.SubsystemTorqueMaster = subsystemStat.FirstOrDefault(s => s.IdSubsystem == IdSubsystemTorque); + } + } + + return wellStat; + }); + + return result; + } + + /// + public async Task GetDateRangeOperationTimeAsync(SubsystemOperationTimeRequest request, CancellationToken token) + { + var well = await wellService.GetOrDefaultAsync(request.IdWell, token) + ?? throw new ArgumentInvalidException(nameof(request.IdWell), $"Well Id: {request.IdWell} does not exist"); + + var query = BuildQuery(request, well); + if (query is null) + { + return null; + } + var result = await query + .GroupBy(o => o.IdTelemetry) + .Select(g => new DatesRangeDto + { + From = g.Min(o => o.DateStart).DateTime, + To = g.Max(o => o.DateEnd).DateTime + }) + .FirstOrDefaultAsync(token); + return result; + } + + private IQueryable BuildQuery(SubsystemOperationTimeRequest request, WellDto well) + { + var idTelemetry = well.IdTelemetry + ?? throw new ArgumentInvalidException(nameof(request.IdWell), $"Well Id: {request.IdWell} has no telemetry"); + + var query = db.SubsystemOperationTimes + .Include(o => o.Subsystem) + .Where(o => o.IdTelemetry == idTelemetry) + .AsNoTracking(); + + if (request.IdsSubsystems.Any()) + query = query.Where(o => request.IdsSubsystems.Contains(o.IdSubsystem)); + + // # Dates range condition + // [GtDate LtDate] + // [DateStart DateEnd] [DateStart DateEnd] + if (request.GtDate.HasValue) + { + DateTimeOffset gtDate = request.GtDate.Value.ToUtcDateTimeOffset(well.Timezone.Hours); + query = query.Where(o => o.DateEnd >= gtDate); + } + + if (request.LtDate.HasValue) + { + DateTimeOffset ltDate = request.LtDate.Value.ToUtcDateTimeOffset(well.Timezone.Hours); + query = query.Where(o => o.DateStart <= ltDate); + } + + if (request.GtDepth.HasValue) + query = query.Where(o => o.DepthEnd >= request.GtDepth.Value); + + if (request.LtDepth.HasValue) + query = query.Where(o => o.DepthStart <= request.LtDepth.Value); + + if (request?.SortFields?.Any() == true) + { + query = query.SortBy(request.SortFields); + } + else + { + query = query + .OrderBy(o => o.DateStart) + .ThenBy(o => o.DepthStart); + } + + if (request?.Skip > 0) + query = query.Skip((int)request.Skip); + + if (request?.Take > 0) + query = query.Take((int)request.Take); + + return query; + } + + private static SubsystemOperationTimeDto Convert(SubsystemOperationTime operationTime, double? timezoneHours = null) + { + var dto = operationTime.Adapt(); + var hours = timezoneHours ?? operationTime.Telemetry.TimeZone.Hours; + dto.DateStart = operationTime.DateStart.ToRemoteDateTime(hours); + dto.DateEnd = operationTime.DateEnd.ToRemoteDateTime(hours); + return dto; + } }