forked from ddrilling/AsbCloudServer
8bbaca0d0c
1. Поправел excel шаблон 2. Доработаны алгоритмы определения операций бурения 3. Небольшой рефакторинг DetectorAbstract, добавил метод для валидации 4. Закомментированы неиспользуемые детекторы. 5. Обновлена спецификация определения операций бурения 6. Добавлены тесты для определения операций бурения
226 lines
7.7 KiB
C#
226 lines
7.7 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() };
|
||
|
||
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 switch
|
||
{
|
||
0 => "Не определена",
|
||
1 => "Не определено начало операции",
|
||
101 => "Разница глубин забоя и положением долота",
|
||
300 => "Низкое давление",
|
||
_ => detectedOperations[i].IdReasonOfEnd
|
||
};
|
||
|
||
var link =
|
||
$"{domains[idDomain]}/well/{well.Id}/telemetry/monitoring?end={Uri.EscapeDataString(dateStart.AddSeconds(1800 * 0.9).ToString("yyyy-MM-ddTHH:mm:ss.fff"))}&range=1800";
|
||
|
||
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;
|
||
while (positionEnd > positionBegin)
|
||
{
|
||
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;
|
||
positionBegin = result.TelemetryEnd;
|
||
break;
|
||
}
|
||
|
||
positionBegin += 1;
|
||
}
|
||
|
||
if (isDetected)
|
||
startDate = lastDetectedOperation!.DateEnd;
|
||
else
|
||
startDate = data[positionEnd].DateTime;
|
||
}
|
||
|
||
return detectedOperations;
|
||
}
|
||
} |