forked from ddrilling/AsbCloudServer
224 lines
7.5 KiB
C#
224 lines
7.5 KiB
C#
using AsbCloudDb.Model;
|
||
using ClosedXML.Excel;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
|
||
|
||
namespace AsbCloudInfrastructure.Services.DetectOperations;
|
||
|
||
public class DetectedOperationExportService
|
||
{
|
||
private readonly DetectorAbstract[] detectors = { new DetectorDrilling(), new DetectorSlipsTime() };
|
||
|
||
private readonly IDictionary<int, string> domains = new Dictionary<int, string>
|
||
{
|
||
{ 1, "https://cloud.digitaldrilling.ru" },
|
||
{ 2, "https://cloud.autodrilling.ru" }
|
||
};
|
||
|
||
private const int headerRowsCount = 1;
|
||
|
||
private const string cellDepositName = "B1";
|
||
private const string cellClusterName = "B2";
|
||
private const string cellWellName = "B3";
|
||
private const string cellDeltaDate = "H2";
|
||
|
||
private const int columnOperationName = 1;
|
||
private const int columnDateStart = 2;
|
||
private const int columnDateEnd = 3;
|
||
private const int columnDuration = 4;
|
||
private const int columnDepthStart = 5;
|
||
private const int columnDepthEnd = 6;
|
||
private const int columnDeltaDepth = 7;
|
||
private const int columnDepth = 8;
|
||
private const int columnIdReasonOfEnd = 9;
|
||
|
||
private readonly IAsbCloudDbContext dbContext;
|
||
|
||
public DetectedOperationExportService(IAsbCloudDbContext dbContext)
|
||
{
|
||
this.dbContext = dbContext;
|
||
}
|
||
|
||
public async Task<Stream> ExportAsync(int idWell, int idDomain, CancellationToken cancellationToken)
|
||
{
|
||
var well = await dbContext.Wells
|
||
.Include(w => w.Cluster)
|
||
.ThenInclude(c => c.Deposit)
|
||
.SingleOrDefaultAsync(w => w.Id == idWell, cancellationToken);
|
||
|
||
if (well is null)
|
||
throw new ArgumentNullException(nameof(well));
|
||
|
||
if (!well.IdTelemetry.HasValue)
|
||
throw new ArgumentNullException(nameof(well));
|
||
|
||
var operations = await DetectOperationsAsync(well.IdTelemetry.Value, new DateTime(2023, 10, 14)
|
||
.ToUtcDateTimeOffset(well.Timezone.Hours), cancellationToken);
|
||
|
||
return await GenerateExcelFileStreamAsync(well, idDomain, operations, cancellationToken);
|
||
}
|
||
|
||
private async Task<Stream> GenerateExcelFileStreamAsync(Well well, int idDomain, IEnumerable<DetectedOperation> detectedOperations,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
|
||
|
||
using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled);
|
||
|
||
await AddToWorkbookAsync(workbook, well, idDomain, detectedOperations, cancellationToken);
|
||
|
||
MemoryStream memoryStream = new MemoryStream();
|
||
workbook.SaveAs(memoryStream, new SaveOptions { });
|
||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||
return memoryStream;
|
||
}
|
||
|
||
private async Task AddToWorkbookAsync(XLWorkbook workbook, Well well, int idDomain, IEnumerable<DetectedOperation> detectedOperations,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
const string sheetName = "Операции";
|
||
|
||
if (!detectedOperations.Any())
|
||
return;
|
||
|
||
var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetName)
|
||
?? throw new FileFormatException($"Книга excel не содержит листа {sheetName}.");
|
||
|
||
await AddToSheetAsync(sheet, well, idDomain, detectedOperations.OrderBy(x => x.DateStart).ThenBy(x => x.DepthStart).ToArray(),
|
||
cancellationToken);
|
||
}
|
||
|
||
private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, int idDomain, IList<DetectedOperation> detectedOperations,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var wellOperationCategories = await dbContext.WellOperationCategories.ToListAsync(cancellationToken);
|
||
|
||
sheet.Cell(cellDepositName).Value = well.Cluster.Deposit.Caption;
|
||
sheet.Cell(cellClusterName).Value = well.Cluster.Caption;
|
||
sheet.Cell(cellWellName).Value = well.Caption;
|
||
sheet.Cell(cellDeltaDate).Value = detectedOperations.Max(o => o.DateEnd) - detectedOperations.Min(o => o.DateStart);
|
||
|
||
var timeZoneWell = TimeSpan.FromHours(well.Timezone.Hours);
|
||
|
||
for (int i = 0; i < detectedOperations.Count; i++)
|
||
{
|
||
var dateStart = detectedOperations[i].DateStart.ToOffset(timeZoneWell);
|
||
var dateEnd = detectedOperations[i].DateEnd.ToOffset(timeZoneWell);
|
||
|
||
var row = sheet.Row(5 + i + headerRowsCount);
|
||
|
||
row.Cell(columnOperationName).Value = detectedOperations[i].IdCategory == 12000
|
||
? "Бурение в слайде с осцилляцией"
|
||
: wellOperationCategories.Single(o => o.Id == detectedOperations[i].IdCategory).Name;
|
||
row.Cell(columnDateEnd).Value = dateEnd;
|
||
row.Cell(columnDuration).Value = (dateEnd - dateStart).TotalMinutes;
|
||
row.Cell(columnDepthStart).Value = detectedOperations[i].DepthStart;
|
||
row.Cell(columnDepthEnd).Value = detectedOperations[i].DepthEnd;
|
||
row.Cell(columnDepth).Value = detectedOperations[i].DepthEnd - detectedOperations[i].DepthStart;
|
||
row.Cell(columnIdReasonOfEnd).Value = detectedOperations[i].IdReasonOfEnd;
|
||
|
||
var link =
|
||
$"{domains[idDomain]}/well/{well.Id}/telemetry/monitoring?end={Uri.EscapeDataString(dateStart.AddSeconds(3544).ToString("yyyy-MM-ddTHH:mm:ss.fff"))}&range=3600";
|
||
|
||
row.Cell(columnDateStart).Value = dateStart;
|
||
row.Cell(columnDateStart).SetHyperlink(new XLHyperlink(link));
|
||
|
||
row.Cell(columnDeltaDepth).Value = i > 0 && i + 1 < detectedOperations.Count
|
||
? detectedOperations[i].DepthStart - detectedOperations[i - 1].DepthEnd
|
||
: 0;
|
||
}
|
||
}
|
||
|
||
private async Task<Stream> GetExcelTemplateStreamAsync(CancellationToken cancellationToken)
|
||
{
|
||
string resourceName = Assembly.GetExecutingAssembly()
|
||
.GetManifestResourceNames()
|
||
.FirstOrDefault(n => n.EndsWith("DetectOperations.xlsx"))!;
|
||
|
||
using var stream = Assembly.GetExecutingAssembly()
|
||
.GetManifestResourceStream(resourceName)!;
|
||
|
||
var memoryStream = new MemoryStream();
|
||
await stream.CopyToAsync(memoryStream, cancellationToken);
|
||
memoryStream.Position = 0;
|
||
|
||
return memoryStream;
|
||
}
|
||
|
||
private async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin,
|
||
CancellationToken token)
|
||
{
|
||
var query = dbContext.TelemetryDataSaub
|
||
.AsNoTracking()
|
||
.Where(d => d.IdTelemetry == idTelemetry)
|
||
.Where(d => d.BlockPosition >= 0)
|
||
.Select(d => new DetectableTelemetry
|
||
{
|
||
DateTime = d.DateTime,
|
||
IdUser = d.IdUser,
|
||
WellDepth = d.WellDepth,
|
||
Pressure = d.Pressure,
|
||
HookWeight = d.HookWeight,
|
||
BlockPosition = d.BlockPosition,
|
||
BitDepth = d.BitDepth,
|
||
RotorSpeed = d.RotorSpeed,
|
||
})
|
||
.OrderBy(d => d.DateTime);
|
||
|
||
var startDate = begin;
|
||
var detectedOperations = new List<DetectedOperation>(8);
|
||
DetectedOperation? lastDetectedOperation = null;
|
||
const int minOperationLength = 5;
|
||
const int maxDetectorsInterpolationFrameLength = 30;
|
||
const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;
|
||
|
||
while (true)
|
||
{
|
||
var data = await query
|
||
.Where(d => d.DateTime > startDate)
|
||
.ToArrayAsync(token);
|
||
|
||
if (data.Length < gap)
|
||
break;
|
||
|
||
var isDetected = false;
|
||
var positionBegin = 0;
|
||
var positionEnd = data.Length - gap;
|
||
var step = 10;
|
||
while (positionEnd > positionBegin)
|
||
{
|
||
step++;
|
||
foreach (var detector in detectors)
|
||
{
|
||
if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result))
|
||
continue;
|
||
|
||
detectedOperations.Add(result!.Operation);
|
||
lastDetectedOperation = result.Operation;
|
||
isDetected = true;
|
||
step = 1;
|
||
positionBegin = result.TelemetryEnd;
|
||
break;
|
||
}
|
||
|
||
if (step > 20)
|
||
step = 10;
|
||
positionBegin += step;
|
||
}
|
||
|
||
if (isDetected)
|
||
startDate = lastDetectedOperation!.DateEnd;
|
||
else
|
||
startDate = data[positionEnd].DateTime;
|
||
}
|
||
|
||
return detectedOperations;
|
||
}
|
||
} |