using AsbCloudApp.Data; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.Cache; using Mapster; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { public class TelemetryAnalyticsService : ITelemetryAnalyticsService { private readonly IAsbCloudDbContext db; private readonly ITelemetryService telemetryService; private readonly ISaubDataCache saubDataCache; private readonly CacheTable cacheOperations; private readonly TelemetryOperationDetectorService operationDetectorService; private readonly IEnumerable operations; public TelemetryAnalyticsService(IAsbCloudDbContext db, ITelemetryService telemetryService, ISaubDataCache saubDataCache, CacheDb cacheDb) { this.db = db; this.telemetryService = telemetryService; this.saubDataCache = saubDataCache; cacheOperations = cacheDb.GetCachedTable((AsbCloudDbContext)db); operations = cacheOperations.Where(); operationDetectorService = new TelemetryOperationDetectorService(operations); } public async Task> GetWellDepthToDayAsync(int idWell, CancellationToken token = default) { var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell); if (telemetryId is null) return null; var depthToTimeData = (from d in db.DataSaubBases where d.IdTelemetry == telemetryId select new { d.Id, d.WellDepth, d.BitDepth, d.Date }); var m = (int)Math.Round(1d * depthToTimeData.Count() / 2048); if (m > 1) depthToTimeData = depthToTimeData.Where(d => d.Id % m == 0); return await depthToTimeData.Select(d => new WellDepthToDayDto { WellDepth = d.WellDepth ?? 0.0, BitDepth = d.BitDepth ?? 0.0, Date = d.Date }).AsNoTracking().ToListAsync(token).ConfigureAwait(false); } public async Task> GetWellDepthToIntervalAsync(int idWell, int intervalSeconds, int workBeginSeconds, CancellationToken token = default) { intervalSeconds = intervalSeconds == 0 ? 86400 : intervalSeconds; var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell); if (telemetryId is null) return null; var timezoneOffset = telemetryService.GetTimezoneOffsetByTelemetryId((int)telemetryId); var drillingPeriodsInfo = await db.GetDepthToIntervalAsync((int)telemetryId, intervalSeconds, workBeginSeconds, timezoneOffset, token).ConfigureAwait(false); var wellDepthToIntervalData = drillingPeriodsInfo.Select(d => new WellDepthToIntervalDto { IntervalStartDate = d.BeginPeriodDate, IntervalDepthProgress = (d.MaxDepth - d.MinDepth) ?? 0.0 / intervalSeconds }).OrderBy(d => d.IntervalStartDate).ToList(); return wellDepthToIntervalData; } public async Task> GetOperationsByWellAsync(int idWell, IEnumerable categoryIds = default, DateTime begin = default, DateTime end = default, int skip = 0, int take = 32, CancellationToken token = default) { var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell); if (telemetryId is null) return null; var operations = from a in db.TelemetryAnalysis.Include(t => t.Operation) where a.IdTelemetry == telemetryId select a; if ((categoryIds != default) && (categoryIds.Any())) operations = operations.Where(o => categoryIds.Contains(o.IdOperation)); var result = new PaginationContainer { Skip = skip, Take = take }; operations = operations.OrderBy(o => o.UnixDate); if (begin != default) { var unixBegin = (begin - new DateTime(1970, 1, 1)).TotalSeconds; operations = operations.Where(o => o.UnixDate >= unixBegin); } if (end != default) { var unixEnd = (end - new DateTime(1970, 1, 1)).TotalSeconds; operations = operations.Where(m => (m.UnixDate + m.DurationSec) <= unixEnd); } result.Count = await operations.CountAsync(token).ConfigureAwait(false); if (skip > 0) operations = operations.Skip(skip); var operationsList = await operations.Take(take) .AsNoTracking() .ToListAsync(token) .ConfigureAwait(false); if (operationsList.Count == 0) return result; foreach (var operation in operations) { var operationDto = new TelemetryOperationDto { Id = operation.Id, Name = operation.Operation.Name, BeginDate = DateTimeOffset.FromUnixTimeSeconds(operation.UnixDate).DateTime, EndDate = DateTimeOffset.FromUnixTimeSeconds(operation.UnixDate + operation.DurationSec).DateTime, StartWellDepth = operation.OperationStartDepth ?? 0.0, EndWellDepth = operation.OperationEndDepth ?? 0.0 }; result.Items.Add(operationDto); } return result; } public async Task> GetOperationsSummaryAsync(int idWell, DateTime begin = default, DateTime end = default, CancellationToken token = default) { var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell); if (telemetryId is null) return null; var unixBegin = (begin - new DateTime(1970, 1, 1)).TotalSeconds; var unixEnd = (end - new DateTime(1970, 1, 1)).TotalSeconds; return await (from a in db.TelemetryAnalysis where a.IdTelemetry == telemetryId && a.UnixDate > unixBegin && a.UnixDate < unixEnd join o in db.TelemetryOperations on a.IdOperation equals o.Id group a by new { a.IdOperation, o.Name } into g select new TelemetryOperationDurationDto { OperationName = g.Key.Name, Duration = g.Where(g => g.DurationSec > 0) .Sum(a => a.DurationSec) }).AsNoTracking().ToListAsync(token) .ConfigureAwait(false); } // This method is not finished (only half done). It returns not correct Dtos. public async Task> GetOperationsToIntervalAsync(int idWell, int intervalSeconds, int workBeginSeconds, CancellationToken token = default) { intervalSeconds = intervalSeconds == 0 ? 86400 : intervalSeconds; var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell); if (telemetryId is null) return null; var timezoneOffset = telemetryService.GetTimezoneOffsetByTelemetryId((int)telemetryId); // Get'n'Group all operations only by start date and by name (if there were several operations in interval). // Without dividing these operations duration by given interval var ops = await (from a in db.TelemetryAnalysis where a.IdTelemetry == telemetryId join o in db.TelemetryOperations on a.IdOperation equals o.Id group a by new { Interval = Math.Floor((a.UnixDate - workBeginSeconds + timezoneOffset) / intervalSeconds), o.Name } into g select new { IntervalStart = g.Min(d => d.UnixDate), OperationName = g.Key.Name, OperationDuration = g.Sum(an => an.DurationSec) }).AsNoTracking() .OrderBy(op => op.IntervalStart) .ToListAsync(token) .ConfigureAwait(false); var groupedOperationsList = new List(); if (operations is not null && operations.Any()) { var operations = ops.Select(o => (o.IntervalStart, o.OperationName, o.OperationDuration)); var splittedOperationsByInterval = DivideOperationsByIntervalLength(operations, intervalSeconds); // divides good groupedOperationsList = UniteOperationsInDto(splittedOperationsByInterval, intervalSeconds).ToList(); // unites not good } return groupedOperationsList; } public void SaveAnalytics(DataSaubBaseDto dataSaubDto) { saubDataCache.AddData(dataSaubDto); if (saubDataCache.GetOrCreateCache(dataSaubDto.IdTelemetry).Count() > 1) { var dataSaubs = saubDataCache.GetOrCreateCache(dataSaubDto.IdTelemetry) .OrderBy(d => d.Date); var telemetryAnalysisDto = GetDrillingAnalysis(dataSaubs); if (saubDataCache.CurrentAnalysis is null) saubDataCache.CurrentAnalysis = telemetryAnalysisDto; if (saubDataCache.CurrentAnalysis.IdOperation == telemetryAnalysisDto.IdOperation) { saubDataCache.CurrentAnalysis.DurationSec += telemetryAnalysisDto.DurationSec; saubDataCache.CurrentAnalysis.OperationEndDepth = dataSaubDto.WellDepth; } else { db.TelemetryAnalysis.Add(saubDataCache.CurrentAnalysis.Adapt()); saubDataCache.CurrentAnalysis = telemetryAnalysisDto; saubDataCache.CurrentAnalysis.OperationStartDepth = dataSaubDto.WellDepth; } } } private static IEnumerable<(long IntervalStart, string OperationName, int OperationDuration)> DivideOperationsByIntervalLength( IEnumerable<(long IntervalStart, string OperationName, int OperationDuration)> operations, int intervalSeconds) { var splittedOperationsByInterval = new List<(long IntervalStart, string OperationName, int OperationDuration)>(); var operationDurationTimeCounter = 0; foreach (var op in operations) { if (op.OperationDuration < (intervalSeconds - operationDurationTimeCounter)) { splittedOperationsByInterval.Add((op.IntervalStart, op.OperationName, op.OperationDuration)); operationDurationTimeCounter += op.OperationDuration; } else { // if operation duration overflows current interval it shoud be divided into 2 or more parts for this and next intervals var remainingIntervalTime = intervalSeconds - operationDurationTimeCounter; splittedOperationsByInterval.Add((op.IntervalStart, op.OperationName, remainingIntervalTime)); // first part of long operation var operationDurationAfterDividing = op.OperationDuration - remainingIntervalTime; // second part of long operation. Can be less or more than interval // If operation duration even after dividing is still more than interval, // it should be divided several times to several intervals. if (operationDurationAfterDividing > intervalSeconds) { var counter = 0; var updatedIntervalStartTime = op.IntervalStart + remainingIntervalTime; while (operationDurationAfterDividing > intervalSeconds) { splittedOperationsByInterval.Add((updatedIntervalStartTime + intervalSeconds * counter, op.OperationName, intervalSeconds)); operationDurationAfterDividing -= intervalSeconds; counter++; } splittedOperationsByInterval.Add((updatedIntervalStartTime + operationDurationAfterDividing, op.OperationName, operationDurationAfterDividing)); operationDurationTimeCounter = operationDurationAfterDividing; } else { splittedOperationsByInterval.Add((op.IntervalStart, op.OperationName, operationDurationAfterDividing)); operationDurationTimeCounter = operationDurationAfterDividing; } } } return splittedOperationsByInterval; } private static IEnumerable UniteOperationsInDto( IEnumerable<(long IntervalStart, string OperationName, int OperationDuration)> operations, int intervalSeconds) { var groupedOperationsList = new List(); var groupedOperationsObj = new TelemetryOperationInfoDto { IntervalBegin = DateTimeOffset.FromUnixTimeSeconds(operations.First().IntervalStart), Operations = new List() }; var intervalEndDate = operations.First().IntervalStart + intervalSeconds; foreach (var op in operations) { if (op.IntervalStart < intervalEndDate) { groupedOperationsObj.Operations.Add(new TelemetryOperationDetailsDto { OperationName = op.OperationName, DurationSec = op.OperationDuration }); } else { groupedOperationsList.Add(groupedOperationsObj); intervalEndDate = op.IntervalStart + intervalSeconds; groupedOperationsObj = new TelemetryOperationInfoDto { IntervalBegin = DateTimeOffset.FromUnixTimeSeconds(op.IntervalStart), Operations = new List() }; groupedOperationsObj.Operations.Add(new TelemetryOperationDetailsDto { OperationName = op.OperationName, DurationSec = op.OperationDuration }); } } groupedOperationsList.Add(groupedOperationsObj); return groupedOperationsList; } private TelemetryAnalysisDto GetDrillingAnalysis(IEnumerable dataSaubBases) { var lastSaubDate = dataSaubBases.Last().Date; var saubWellDepths = dataSaubBases.Where(sw => sw.WellDepth is not null) .Select(s => (Value: (double)s.WellDepth, (s.Date - dataSaubBases.First().Date).TotalSeconds)); var saubBitDepths = dataSaubBases.Where(sw => sw.BitDepth is not null) .Select(s => (Value: (double)s.BitDepth, (s.Date - dataSaubBases.First().Date).TotalSeconds)); var saubBlockPositions = dataSaubBases.Where(sw => sw.BlockPosition is not null) .Select(s => (Value: (double)s.BlockPosition, (s.Date - dataSaubBases.First().Date).TotalSeconds)); var saubRotorSpeeds = dataSaubBases.Where(sw => sw.RotorSpeed is not null) .Select(s => (Value: (double)s.RotorSpeed, (s.Date - dataSaubBases.First().Date).TotalSeconds)); var saubPressures = dataSaubBases.Where(sw => sw.Pressure is not null) .Select(s => (Value: (double)s.Pressure, (s.Date - dataSaubBases.First().Date).TotalSeconds)); var saubHookWeights = dataSaubBases.Where(sw => sw.HookWeight is not null) .Select(s => (Value: (double)s.HookWeight, (s.Date - dataSaubBases.First().Date).TotalSeconds)); var wellDepthChangingIndex = new InterpolationLine(saubWellDepths).GetAForLinearFormula(); var bitPositionChangingIndex = new InterpolationLine(saubBitDepths).GetAForLinearFormula(); var blockPositionChangingIndex = new InterpolationLine(saubBlockPositions).GetAForLinearFormula(); var rotorSpeedChangingIndex = new InterpolationLine(saubRotorSpeeds).GetAForLinearFormula(); var pressureChangingIndex = new InterpolationLine(saubPressures).GetAForLinearFormula(); var hookWeightChangingIndex = new InterpolationLine(saubHookWeights).GetAForLinearFormula(); var IsBlockRising = InterpolationLine.IsValueDecreases(blockPositionChangingIndex, -0.0001); var IsBlockGoesDown = InterpolationLine.IsValueIncreases(blockPositionChangingIndex, 0.0001); var IsBlockStandsStill = InterpolationLine.IsValueNotChanges(blockPositionChangingIndex, (0.0001, -0.0001)); var drillingAnalysis = new TelemetryAnalysisDto { IdTelemetry = dataSaubBases.First().IdTelemetry, UnixDate = (long)(lastSaubDate - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds, DurationSec = (int)(dataSaubBases.Last().Date - dataSaubBases.ElementAt(dataSaubBases.Count() - 2).Date).TotalSeconds, OperationStartDepth = null, OperationEndDepth = null, IsWellDepthDecreasing = InterpolationLine.IsValueDecreases(wellDepthChangingIndex, -0.0001), IsWellDepthIncreasing = InterpolationLine.IsValueIncreases(wellDepthChangingIndex, 0.0001), IsBitPositionDecreasing = InterpolationLine.IsValueDecreases(bitPositionChangingIndex, -0.0001), IsBitPositionIncreasing = InterpolationLine.IsValueIncreases(bitPositionChangingIndex, 0.0001), IsBitPositionLt20 = InterpolationLine.IsAverageLessThanBound(saubBitDepths, 20), IsBlockPositionDecreasing = InterpolationLine.IsValueDecreases(blockPositionChangingIndex, -0.0001), IsBlockPositionIncreasing = InterpolationLine.IsValueIncreases(blockPositionChangingIndex, 0.0001), IsRotorSpeedLt3 = InterpolationLine.IsAverageLessThanBound(saubRotorSpeeds, 3), IsRotorSpeedGt3 = InterpolationLine.IsAverageMoreThanBound(saubRotorSpeeds, 3), IsPressureLt20 = InterpolationLine.IsAverageLessThanBound(saubPressures, 20), IsPressureGt20 = InterpolationLine.IsAverageMoreThanBound(saubPressures, 20), IsHookWeightNotChanges = InterpolationLine.IsValueNotChanges(hookWeightChangingIndex, (0.0001, -0.0001)), IsHookWeightLt3 = InterpolationLine.IsAverageLessThanBound(saubHookWeights, 3), IdOperation = 1 }; drillingAnalysis.IdOperation = operationDetectorService.DetectOperation(drillingAnalysis).Id; return drillingAnalysis; } } }