DD.WellWorkover.Cloud/AsbCloudInfrastructure/Services/TelemetryAnalyticsService.cs

539 lines
24 KiB
C#
Raw Normal View History

2021-07-21 15:29:19 +05:00
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
2021-07-21 15:29:19 +05:00
using AsbCloudInfrastructure.Services.Cache;
using Microsoft.EntityFrameworkCore;
2021-07-21 15:29:19 +05:00
using System;
using System.Collections.Generic;
2021-07-21 15:29:19 +05:00
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 CacheTable<WellOperationCategory> cacheOperations;
2021-07-27 13:32:00 +05:00
private readonly TelemetryOperationDetectorService operationDetectorService;
private readonly IEnumerable<WellOperationCategory> operations;
private const int intervalHours = 12;
public TelemetryAnalyticsService(IAsbCloudDbContext db, ITelemetryService telemetryService,
CacheDb cacheDb)
{
this.db = db;
this.telemetryService = telemetryService;
cacheOperations = cacheDb.GetCachedTable<WellOperationCategory>((AsbCloudDbContext)db);
operations = cacheOperations.Where();
2021-07-27 13:32:00 +05:00
operationDetectorService = new TelemetryOperationDetectorService(operations);
}
public async Task<IEnumerable<WellDepthToDayDto>> GetWellDepthToDayAsync(int idWell, CancellationToken token = default)
{
var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell);
if (telemetryId is null)
return null;
var depthToTimeData = (from d in db.TelemetryDataSaub
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<IEnumerable<WellDepthToIntervalDto>> 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<PaginationContainer<TelemetryOperationDto>> GetOperationsByWellAsync(int idWell,
IEnumerable<int> 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));
2021-07-27 13:32:00 +05:00
var result = new PaginationContainer<TelemetryOperationDto>
{
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)
{
2021-07-27 13:32:00 +05:00
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<IEnumerable<TelemetryOperationDurationDto>> 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 == default
? 0
: (begin - new DateTime(1970, 1, 1)).TotalSeconds;
var unixEnd = end == default
? (DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds
: (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<IEnumerable<TelemetryOperationInfoDto>> 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<TelemetryOperationInfoDto>();
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 CalculateAnalytics()
2021-07-20 11:24:57 +05:00
{
var allTelemetryIds = (from telemetry in db.TelemetryDataSaub
select telemetry.IdTelemetry)
.Distinct()
.ToList();
2021-07-20 11:24:57 +05:00
foreach(var idTelemetry in allTelemetryIds)
2021-07-20 11:24:57 +05:00
{
var analyzeStartDate = GetAnalyzeStartDate(idTelemetry);
2021-07-20 11:24:57 +05:00
if (analyzeStartDate == default)
continue;
TelemetryAnalysis currentAnalysis = null;
while (TryGetDataSaubPart(idTelemetry, analyzeStartDate,
out var dataSaubPart))
2021-07-20 11:24:57 +05:00
{
analyzeStartDate = dataSaubPart.Last().Date;
var count = dataSaubPart.Count();
var skip = 0;
var step = 10;
var take = step * 2;
for(;skip < count; skip += step)
{
var analyzingSaubs = dataSaubPart.Skip(skip).Take(take);
if (analyzingSaubs.Count() <= 1)
{
continue;
}
var dataSaub = analyzingSaubs.First();
var telemetryAnalysis = new TelemetryAnalysis();
telemetryAnalysis = GetDrillingAnalysis(analyzingSaubs);
if (currentAnalysis is null)
{
currentAnalysis = telemetryAnalysis;
currentAnalysis.OperationStartDepth = dataSaub.WellDepth;
}
if (currentAnalysis.IdOperation == telemetryAnalysis.IdOperation)
{
currentAnalysis.DurationSec += telemetryAnalysis.DurationSec;
currentAnalysis.OperationEndDepth = dataSaub.WellDepth;
}
else
{
db.TelemetryAnalysis.Add(currentAnalysis);
currentAnalysis = telemetryAnalysis;
currentAnalysis.OperationStartDepth = dataSaub.WellDepth;
}
}
db.SaveChanges();
GC.Collect();
}
2021-07-20 11:24:57 +05:00
}
}
public async Task<DatesRangeDto> GetOperationsDateRangeAsync(int idWell,
CancellationToken token = default)
{
var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell);
if (telemetryId is null)
return null;
var datesRange = await (from d in db.TelemetryAnalysis
where d.IdTelemetry == telemetryId
select d.UnixDate).DefaultIfEmpty()
.GroupBy(g => true)
.AsNoTracking()
.Select(g => new
{
From = g.Min(),
To = g.Max()
}).OrderBy(gr => gr.From)
.FirstOrDefaultAsync(token)
.ConfigureAwait(false);
return new DatesRangeDto
{
From = DateTimeOffset.FromUnixTimeSeconds(datesRange.From).DateTime,
To = datesRange.To == default
? DateTime.MaxValue
: DateTimeOffset.FromUnixTimeSeconds(datesRange.To).DateTime
};
}
private DateTime GetAnalyzeStartDate(int idTelemetry)
{
var lastAnalysisInDb = (from analysis in db.TelemetryAnalysis
where analysis.IdTelemetry == idTelemetry
orderby analysis.UnixDate
select analysis)
.DefaultIfEmpty()
.Last();
var lastAnalysisUnixDate = lastAnalysisInDb?.UnixDate ?? default;
var analyzeStartDate = lastAnalysisUnixDate == default
? DateTimeOffset.MinValue
: DateTimeOffset.FromUnixTimeSeconds(lastAnalysisUnixDate);
var firstDataSaub = GetFirstSaub(analyzeStartDate.DateTime, idTelemetry);
var firstSaubUtcTime = firstDataSaub?.Date.ToUniversalTime() ?? default;
return firstSaubUtcTime;
}
private bool TryGetDataSaubPart(int idTelemetry, DateTime analyzeStartDate,
out IEnumerable<TelemetryDataSaub> dataSaubPart)
{
var query = (from ds in db.TelemetryDataSaub
where ds.IdTelemetry == idTelemetry
&& ds.Date > analyzeStartDate
orderby ds.Date
select ds)
.Take(12 * 60* 60)
.AsNoTracking();
dataSaubPart = query.ToList();
return dataSaubPart.Any();
}
private TelemetryDataSaub GetFirstSaub(DateTime analyzeStartDate, int idTelemetry)
{
return (from ds in db.TelemetryDataSaub
where ds.IdTelemetry == idTelemetry
&& ds.Date > analyzeStartDate.AddHours(intervalHours)
orderby ds.Date
select ds).FirstOrDefault();
}
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 (IntervalStart, OperationName, OperationDuration) in operations)
{
if (OperationDuration < (intervalSeconds - operationDurationTimeCounter))
{
splittedOperationsByInterval.Add((IntervalStart, OperationName, OperationDuration));
operationDurationTimeCounter += 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((IntervalStart, OperationName, remainingIntervalTime)); // first part of long operation
var operationDurationAfterDividing = 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 = IntervalStart + remainingIntervalTime;
while (operationDurationAfterDividing > intervalSeconds)
{
splittedOperationsByInterval.Add((updatedIntervalStartTime + intervalSeconds * counter, OperationName, intervalSeconds));
operationDurationAfterDividing -= intervalSeconds;
counter++;
}
splittedOperationsByInterval.Add((updatedIntervalStartTime + operationDurationAfterDividing, OperationName, operationDurationAfterDividing));
operationDurationTimeCounter = operationDurationAfterDividing;
}
else
{
splittedOperationsByInterval.Add((IntervalStart, OperationName, operationDurationAfterDividing));
operationDurationTimeCounter = operationDurationAfterDividing;
}
}
}
return splittedOperationsByInterval;
}
private static IEnumerable<TelemetryOperationInfoDto> UniteOperationsInDto(
IEnumerable<(long IntervalStart, string OperationName, int OperationDuration)> operations, int intervalSeconds)
{
var groupedOperationsList = new List<TelemetryOperationInfoDto>();
var groupedOperationsObj = new TelemetryOperationInfoDto
{
IntervalBegin = DateTimeOffset.FromUnixTimeSeconds(operations.First().IntervalStart),
Operations = new List<TelemetryOperationDetailsDto>()
};
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<TelemetryOperationDetailsDto>()
};
groupedOperationsObj.Operations.Add(new TelemetryOperationDetailsDto
{
OperationName = op.OperationName,
DurationSec = op.OperationDuration
});
}
}
groupedOperationsList.Add(groupedOperationsObj);
return groupedOperationsList;
}
private TelemetryAnalysis GetDrillingAnalysis(IEnumerable<TelemetryDataSaub> dataSaubBases)
{
var lastSaubDate = dataSaubBases.Last().Date;
var saubWellDepths = dataSaubBases.Where(sw => sw.WellDepth is not null)
.Select(s => (Y: (double)s.WellDepth,
X: (s.Date - dataSaubBases.First().Date).TotalSeconds));
var saubBitDepths = dataSaubBases.Where(sw => sw.BitDepth is not null)
.Select(s => (Y: (double)s.BitDepth,
X: (s.Date - dataSaubBases.First().Date).TotalSeconds));
var saubBlockPositions = dataSaubBases.Where(sw => sw.BlockPosition is not null)
.Select(s => (Y: (double)s.BlockPosition,
X: (s.Date - dataSaubBases.First().Date).TotalSeconds));
var saubRotorSpeeds = dataSaubBases.Where(sw => sw.RotorSpeed is not null)
.Select(s => (Y: (double)s.RotorSpeed,
X: (s.Date - dataSaubBases.First().Date).TotalSeconds));
var saubPressures = dataSaubBases.Where(sw => sw.Pressure is not null)
.Select(s => (Y: (double)s.Pressure,
X: (s.Date - dataSaubBases.First().Date).TotalSeconds));
var saubHookWeights = dataSaubBases.Where(sw => sw.HookWeight is not null)
.Select(s => (Y: (double)s.HookWeight,
X: (s.Date - dataSaubBases.First().Date).TotalSeconds));
var wellDepthLine = new InterpolationLine(saubWellDepths);
var bitPositionLine = new InterpolationLine(saubBitDepths);
var blockPositionLine = new InterpolationLine(saubBlockPositions);
var rotorSpeedLine = new InterpolationLine(saubRotorSpeeds);
var pressureLine = new InterpolationLine(saubPressures);
var hookWeightLine = new InterpolationLine(saubHookWeights);
var IsBlockRising = blockPositionLine.IsYDecreases(-0.0001);
var IsBlockGoesDown = blockPositionLine.IsYIncreases(0.0001);
var IsBlockStandsStill = blockPositionLine.IsYNotChanges(0.0001, -0.0001);
var drillingAnalysis = new TelemetryAnalysis
{
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 = wellDepthLine.IsYDecreases(-0.0001),
IsWellDepthIncreasing = wellDepthLine.IsYIncreases( 0.0001),
IsBitPositionDecreasing = bitPositionLine.IsYDecreases(-0.0001),
IsBitPositionIncreasing = bitPositionLine.IsYIncreases(0.0001),
IsBitPositionLt20 = bitPositionLine.IsAverageYLessThanBound(20),
IsBlockPositionDecreasing = blockPositionLine.IsYDecreases(-0.0001),
IsBlockPositionIncreasing = blockPositionLine.IsYIncreases(0.0001),
IsRotorSpeedLt3 = rotorSpeedLine.IsAverageYLessThanBound(3),
IsRotorSpeedGt3 = rotorSpeedLine.IsAverageYMoreThanBound(3),
IsPressureLt20 = pressureLine.IsAverageYLessThanBound(20),
IsPressureGt20 = pressureLine.IsAverageYMoreThanBound(20),
IsHookWeightNotChanges = hookWeightLine.IsYNotChanges(0.0001, -0.0001),
IsHookWeightLt3 = hookWeightLine.IsAverageYLessThanBound(3),
IdOperation = 1
};
drillingAnalysis.IdOperation =
operationDetectorService.DetectOperation(drillingAnalysis).Id;
return drillingAnalysis;
}
}
}