2023-11-15 09:33:26 +05:00
|
|
|
|
using AsbCloudDb.Model;
|
2022-08-04 15:06:17 +05:00
|
|
|
|
using ClosedXML.Excel;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
2023-11-15 09:33:26 +05:00
|
|
|
|
using System.Reflection;
|
2022-08-04 15:06:17 +05:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2023-12-18 13:51:40 +05:00
|
|
|
|
using AsbCloudApp.Data.DetectedOperation;
|
2023-11-15 09:33:26 +05:00
|
|
|
|
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
|
2023-12-04 17:36:00 +05:00
|
|
|
|
using AsbCloudApp.Repositories;
|
2023-12-18 15:56:24 +05:00
|
|
|
|
using Microsoft.AspNetCore.Http.Extensions;
|
2023-12-26 14:03:31 +05:00
|
|
|
|
using AsbCloudApp.Exceptions;
|
2022-08-04 15:06:17 +05:00
|
|
|
|
|
2023-11-15 09:33:26 +05:00
|
|
|
|
namespace AsbCloudInfrastructure.Services.DetectOperations;
|
|
|
|
|
|
|
|
|
|
public class DetectedOperationExportService
|
2022-08-04 15:06:17 +05:00
|
|
|
|
{
|
2023-11-28 11:23:30 +05:00
|
|
|
|
private readonly DetectorAbstract[] detectors =
|
|
|
|
|
{
|
|
|
|
|
new DetectorDrilling(),
|
|
|
|
|
new DetectorSlipsTime()
|
|
|
|
|
};
|
2023-04-18 16:22:53 +05:00
|
|
|
|
|
2023-11-15 09:33:26 +05:00
|
|
|
|
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;
|
2023-11-28 11:23:30 +05:00
|
|
|
|
private const int columnComment = 10;
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2024-02-07 11:33:00 +05:00
|
|
|
|
//TODO: удалить неиспользуемую зависимость
|
2023-11-15 09:33:26 +05:00
|
|
|
|
private readonly IAsbCloudDbContext dbContext;
|
2024-02-07 11:33:00 +05:00
|
|
|
|
private readonly IWellOperationRepository wellOperationRepository;
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-12-04 17:36:00 +05:00
|
|
|
|
public DetectedOperationExportService(IAsbCloudDbContext dbContext, IWellOperationRepository wellOperationRepository)
|
2023-11-15 09:33:26 +05:00
|
|
|
|
{
|
|
|
|
|
this.dbContext = dbContext;
|
2023-12-04 17:36:00 +05:00
|
|
|
|
this.wellOperationRepository = wellOperationRepository;
|
|
|
|
|
}
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Экспорт excel файла с операциями по скважине
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="idWell">ключ скважины</param>
|
|
|
|
|
/// <param name="host">хост</param>
|
|
|
|
|
/// <param name="cancellationToken"></param>
|
|
|
|
|
/// <returns></returns>
|
2023-12-26 14:03:31 +05:00
|
|
|
|
/// <exception cref="ArgumentInvalidException"></exception>
|
2023-12-18 15:56:24 +05:00
|
|
|
|
public async Task<Stream> ExportAsync(int idWell, string host, CancellationToken cancellationToken)
|
2023-11-15 09:33:26 +05:00
|
|
|
|
{
|
2023-12-26 14:03:31 +05:00
|
|
|
|
var well = await dbContext.Set<Well>()
|
2023-11-15 09:33:26 +05:00
|
|
|
|
.Include(w => w.Cluster)
|
|
|
|
|
.ThenInclude(c => c.Deposit)
|
2023-12-26 14:03:31 +05:00
|
|
|
|
.FirstOrDefaultAsync(w => w.Id == idWell, cancellationToken);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
|
|
|
|
if (well is null)
|
2023-12-26 14:03:31 +05:00
|
|
|
|
throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} does not exist");
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
|
|
|
|
if (!well.IdTelemetry.HasValue)
|
2023-12-26 14:03:31 +05:00
|
|
|
|
throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} has no telemetry");
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-12-26 14:03:31 +05:00
|
|
|
|
var operations = await DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, cancellationToken);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
return await GenerateExcelFileStreamAsync(well, host, operations, cancellationToken);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
private async Task<Stream> GenerateExcelFileStreamAsync(Well well, string host, IEnumerable<OperationDetectorResult> operationDetectorResults,
|
2023-11-15 09:33:26 +05:00
|
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
|
|
|
|
|
|
2024-02-07 11:33:00 +05:00
|
|
|
|
using var workbook = new XLWorkbook(excelTemplateStream);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
await AddToWorkbookAsync(workbook, well, host, operationDetectorResults, cancellationToken);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
|
|
|
|
MemoryStream memoryStream = new MemoryStream();
|
|
|
|
|
workbook.SaveAs(memoryStream, new SaveOptions { });
|
|
|
|
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
return memoryStream;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
private async Task AddToWorkbookAsync(XLWorkbook workbook, Well well, string host, IEnumerable<OperationDetectorResult> operationDetectorResults,
|
2023-11-15 09:33:26 +05:00
|
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
const string sheetName = "Операции";
|
|
|
|
|
|
2023-11-28 11:23:30 +05:00
|
|
|
|
if (!operationDetectorResults.Any())
|
2023-11-15 09:33:26 +05:00
|
|
|
|
return;
|
|
|
|
|
|
2024-02-07 11:33:00 +05:00
|
|
|
|
var sheet = workbook.GetWorksheet(sheetName);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
await AddToSheetAsync(sheet, well, host, operationDetectorResults
|
2023-11-28 11:23:30 +05:00
|
|
|
|
.OrderBy(x => x.Operation.DateStart).ThenBy(x => x.Operation.DepthStart).ToArray(),
|
2023-11-15 09:33:26 +05:00
|
|
|
|
cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, string host, IList<OperationDetectorResult> operationDetectorResults,
|
2023-11-15 09:33:26 +05:00
|
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var wellOperationCategories = await dbContext.WellOperationCategories.ToListAsync(cancellationToken);
|
|
|
|
|
|
2024-02-07 11:33:00 +05:00
|
|
|
|
sheet.Cell(cellDepositName).SetCellValue(well.Cluster.Deposit.Caption);
|
|
|
|
|
sheet.Cell(cellClusterName).SetCellValue(well.Cluster.Caption);
|
|
|
|
|
sheet.Cell(cellWellName).SetCellValue(well.Caption);
|
|
|
|
|
sheet.Cell(cellDeltaDate).SetCellValue(operationDetectorResults.Max(o => o.Operation.DateEnd) - operationDetectorResults.Min(o => o.Operation.DateStart));
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-12-04 17:36:00 +05:00
|
|
|
|
var detectedOperations = operationDetectorResults.Select(o => o.Operation).ToArray();
|
|
|
|
|
|
2023-11-28 11:23:30 +05:00
|
|
|
|
for (int i = 0; i < operationDetectorResults.Count; i++)
|
2023-11-15 09:33:26 +05:00
|
|
|
|
{
|
2023-12-04 17:36:00 +05:00
|
|
|
|
var current = detectedOperations[i];
|
|
|
|
|
var dateStart = current.DateStart.ToRemoteDateTime(well.Timezone.Hours);
|
|
|
|
|
var dateEnd = current.DateEnd.ToRemoteDateTime(well.Timezone.Hours);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
|
|
|
|
var row = sheet.Row(5 + i + headerRowsCount);
|
|
|
|
|
|
2024-02-07 11:33:00 +05:00
|
|
|
|
var categoryName = GetCategoryName(wellOperationCategories, current);
|
|
|
|
|
|
|
|
|
|
row.Cell(columnOperationName).SetCellValue(categoryName);
|
|
|
|
|
row.Cell(columnDateEnd).SetCellValue(dateEnd);
|
|
|
|
|
row.Cell(columnDuration).SetCellValue((dateEnd - dateStart).TotalMinutes);
|
|
|
|
|
row.Cell(columnDepthStart).SetCellValue(current.DepthStart);
|
|
|
|
|
row.Cell(columnDepthEnd).SetCellValue(current.DepthEnd);
|
|
|
|
|
row.Cell(columnDepth).SetCellValue(current.DepthEnd - current.DepthStart);
|
2023-12-04 17:36:00 +05:00
|
|
|
|
|
2024-02-07 11:33:00 +05:00
|
|
|
|
if (current.ExtraData.TryGetValue("IdReasonOfEnd", out object? idReasonOfEndObject)
|
2023-12-04 17:36:00 +05:00
|
|
|
|
&& idReasonOfEndObject is int idReasonOfEnd)
|
2024-02-07 11:33:00 +05:00
|
|
|
|
{
|
|
|
|
|
var reasonOfEnd = GetIdReasonOfEnd(idReasonOfEnd);
|
|
|
|
|
row.Cell(columnIdReasonOfEnd).SetCellValue(reasonOfEnd);
|
|
|
|
|
}
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
var query = new QueryBuilder();
|
|
|
|
|
query.Add("end", dateStart.AddSeconds(1800 * 0.9).ToString("yyyy-MM-ddTHH:mm:ss.fff"));
|
|
|
|
|
query.Add("range", "1800");
|
|
|
|
|
|
2024-02-07 11:33:00 +05:00
|
|
|
|
row.Cell(columnDateStart).SetCellValue(dateStart);
|
|
|
|
|
|
2023-12-18 15:56:24 +05:00
|
|
|
|
var link = $"{host}/well/{well.Id}/telemetry/monitoring{query}";
|
2024-02-07 11:33:00 +05:00
|
|
|
|
row.Cell(columnDateStart).SetHyperlink(link);
|
|
|
|
|
|
|
|
|
|
var deltaDepth = i > 0 && i + 1 < detectedOperations.Length
|
2023-12-04 17:36:00 +05:00
|
|
|
|
? current.DepthStart - detectedOperations[i - 1].DepthEnd
|
2023-11-15 09:33:26 +05:00
|
|
|
|
: 0;
|
2024-02-07 11:33:00 +05:00
|
|
|
|
row.Cell(columnDeltaDepth).SetCellValue(deltaDepth);
|
2023-11-28 11:23:30 +05:00
|
|
|
|
|
2024-02-07 11:33:00 +05:00
|
|
|
|
var comment = CreateComment(operationDetectorResults[i]);
|
|
|
|
|
row.Cell(columnComment).SetCellValue(comment);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-04 17:36:00 +05:00
|
|
|
|
|
|
|
|
|
private static string GetCategoryName(IEnumerable<WellOperationCategory> wellOperationCategories, DetectedOperation current)
|
|
|
|
|
{
|
|
|
|
|
var idCategory = current.IdCategory;
|
2023-12-18 13:51:40 +05:00
|
|
|
|
if (idCategory == WellOperationCategory.IdSlide &&
|
|
|
|
|
EnabledSubsystemsFlags.AutoOscillation.HasEnabledSubsystems(current.EnabledSubsystems))
|
2023-12-04 17:36:00 +05:00
|
|
|
|
return "Бурение в слайде с осцилляцией";
|
|
|
|
|
|
|
|
|
|
var category = wellOperationCategories.FirstOrDefault(o => o.Id == current.IdCategory);
|
|
|
|
|
|
|
|
|
|
if(category is not null)
|
|
|
|
|
return category.Name;
|
|
|
|
|
|
|
|
|
|
return $"Операция №{idCategory}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetIdReasonOfEnd(int idReasonOfEnd)
|
|
|
|
|
=> idReasonOfEnd switch {
|
|
|
|
|
0 => "Не определена",
|
|
|
|
|
1 => "Не определено начало операции",
|
|
|
|
|
101 => "Разница глубин забоя и положением долота",
|
|
|
|
|
300 => "Низкое давление",
|
|
|
|
|
_ => idReasonOfEnd.ToString($"Причина № {idReasonOfEnd}"),
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private async Task<Stream> GetExcelTemplateStreamAsync(CancellationToken cancellationToken)
|
2023-11-15 09:33:26 +05:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
2023-11-28 11:23:30 +05:00
|
|
|
|
|
2023-12-05 10:56:49 +05:00
|
|
|
|
private static string CreateComment(OperationDetectorResult operationDetectorResult)
|
2023-11-28 11:23:30 +05:00
|
|
|
|
{
|
2023-12-04 17:36:00 +05:00
|
|
|
|
var operation = operationDetectorResult.Operation;
|
|
|
|
|
switch (operation.IdCategory)
|
2023-11-28 11:23:30 +05:00
|
|
|
|
{
|
2023-12-04 17:36:00 +05:00
|
|
|
|
case WellOperationCategory.IdRotor:
|
|
|
|
|
case WellOperationCategory.IdSlide:
|
2023-12-05 12:10:30 +05:00
|
|
|
|
var comment = "";
|
|
|
|
|
if (operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyAvgRotorSpeed, out object? oAvgRotorSpeed))
|
|
|
|
|
comment += $"Средняя скорость оборотов ротора: {oAvgRotorSpeed}\r\n";
|
|
|
|
|
|
|
|
|
|
if (operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyDispersionOfNormalizedRotorSpeed, out object? oDispersionOfNormalizedRotorSpeed))
|
|
|
|
|
comment += $"Дисперсия нормированных оборотов ротора: {oDispersionOfNormalizedRotorSpeed}";
|
|
|
|
|
|
2023-12-04 17:36:00 +05:00
|
|
|
|
return comment;
|
2023-12-05 12:10:30 +05:00
|
|
|
|
|
2023-11-28 11:23:30 +05:00
|
|
|
|
default:
|
2023-12-05 10:56:49 +05:00
|
|
|
|
return string.Empty;
|
2023-11-28 11:23:30 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-15 09:33:26 +05:00
|
|
|
|
|
2023-11-28 11:23:30 +05:00
|
|
|
|
private async Task<IEnumerable<OperationDetectorResult>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin,
|
2023-11-15 09:33:26 +05:00
|
|
|
|
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,
|
2023-11-15 09:43:13 +05:00
|
|
|
|
WellDepth = d.WellDepth,
|
|
|
|
|
Pressure = d.Pressure,
|
|
|
|
|
HookWeight = d.HookWeight,
|
|
|
|
|
BlockPosition = d.BlockPosition,
|
|
|
|
|
BitDepth = d.BitDepth,
|
|
|
|
|
RotorSpeed = d.RotorSpeed,
|
2023-11-15 09:33:26 +05:00
|
|
|
|
})
|
|
|
|
|
.OrderBy(d => d.DateTime);
|
|
|
|
|
|
|
|
|
|
var startDate = begin;
|
2023-11-28 11:23:30 +05:00
|
|
|
|
var detectedOperationResults = new List<OperationDetectorResult>(8);
|
2023-11-15 09:33:26 +05:00
|
|
|
|
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;
|
|
|
|
|
|
2023-11-28 11:23:30 +05:00
|
|
|
|
detectedOperationResults.Add(result!);
|
|
|
|
|
lastDetectedOperation = result!.Operation;
|
2023-11-15 09:33:26 +05:00
|
|
|
|
isDetected = true;
|
|
|
|
|
positionBegin = result.TelemetryEnd;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-11-22 14:47:17 +05:00
|
|
|
|
|
|
|
|
|
positionBegin += 1;
|
2023-11-15 09:33:26 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isDetected)
|
|
|
|
|
startDate = lastDetectedOperation!.DateEnd;
|
|
|
|
|
else
|
|
|
|
|
startDate = data[positionEnd].DateTime;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-28 11:23:30 +05:00
|
|
|
|
return detectedOperationResults;
|
2023-11-15 09:33:26 +05:00
|
|
|
|
}
|
|
|
|
|
}
|