using AsbCloudApp.Data; using AsbCloudApp.Data.DetectedOperation; using AsbCloudApp.Data.Subsystems; using AsbCloudApp.Exceptions; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudApp.Services.Subsystems; using AsbCloudDb; using AsbCloudDb.Model; using AsbCloudDb.Model.Subsystems; using Mapster; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.Subsystems; 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) { 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; } }