From 081c0c50700f6ea824856f1be7f995b88d0d98f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 15 Nov 2023 09:33:26 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20+=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D1=91=D1=81=20=D1=8D=D0=BA=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D1=80=D1=82=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BE=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D1=91=D0=BD=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B9=20=D0=B2=20?= =?UTF-8?q?=D0=B0=D0=BF=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/IDetectedOperationService.cs | 10 - .../AsbCloudInfrastructure.csproj | 1 + AsbCloudInfrastructure/DependencyInjection.cs | 2 + .../DetectOperations/DetectOperations.xlsx | Bin .../DetectedOperationExportService.cs | 288 ++++++++++++------ .../DetectedOperationService.cs | 9 - .../Detectors/DetectorAbstract.cs | 2 +- .../Detectors/DetectorDrilling.cs | 2 +- .../Detectors/DetectorSlipsTime.cs | 2 +- .../SAUB/DetectedOperationController.cs | 44 +-- .../DetectedOperationExportService.cs | 227 -------------- 11 files changed, 216 insertions(+), 371 deletions(-) rename ConsoleApp1/DetectedOperations/Files/Operations.xlsx => AsbCloudInfrastructure/Services/DetectOperations/DetectOperations.xlsx (100%) delete mode 100644 ConsoleApp1/DetectedOperations/DetectedOperationExportService.cs diff --git a/AsbCloudApp/Services/IDetectedOperationService.cs b/AsbCloudApp/Services/IDetectedOperationService.cs index 120cfef8..1117d8e3 100644 --- a/AsbCloudApp/Services/IDetectedOperationService.cs +++ b/AsbCloudApp/Services/IDetectedOperationService.cs @@ -1,9 +1,7 @@ using AsbCloudApp.Data; using AsbCloudApp.Data.DetectedOperation; using AsbCloudApp.Requests; -using System; using System.Collections.Generic; -using System.IO; using System.Threading; using System.Threading.Tasks; @@ -62,13 +60,5 @@ namespace AsbCloudApp.Services /// /// Task?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token); - - /// - /// Выгрузка в Excel - /// - /// - /// - /// - Task ExportAsync(IEnumerable idsWells, CancellationToken token); } } diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index bc825b4b..0ed4b266 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -29,6 +29,7 @@ + diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index ef248c13..3c4d8045 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -289,6 +289,8 @@ namespace AsbCloudInfrastructure services.AddTransient, WellOperationDefaultExcelParser>(); services.AddTransient, WellOperationGazpromKhantosExcelParser>(); + services.AddTransient(); + return services; } diff --git a/ConsoleApp1/DetectedOperations/Files/Operations.xlsx b/AsbCloudInfrastructure/Services/DetectOperations/DetectOperations.xlsx similarity index 100% rename from ConsoleApp1/DetectedOperations/Files/Operations.xlsx rename to AsbCloudInfrastructure/Services/DetectOperations/DetectOperations.xlsx diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs index f8f0296d..83955b10 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs @@ -1,120 +1,224 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Services; -using AsbCloudDb.Model; +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 +namespace AsbCloudInfrastructure.Services.DetectOperations; + +public class DetectedOperationExportService { + private readonly DetectorAbstract[] detectors = { new DetectorDrilling(), new DetectorSlipsTime() }; - internal class DetectedOperationExportService - { - private readonly IAsbCloudDbContext db; - private readonly IWellService wellService; + private readonly IDictionary domains = new Dictionary + { + { 1, "https://cloud.digitaldrilling.ru" }, + { 2, "https://cloud.autodrilling.ru" } + }; - public DetectedOperationExportService(IAsbCloudDbContext db, IWellService wellService) - { - this.db = db; - this.wellService = wellService; - } + private const int headerRowsCount = 1; - public async Task ExportAsync(IEnumerable idsWells, CancellationToken token) - { - using var workbook = new XLWorkbook(XLEventTracking.Disabled); + private const string cellDepositName = "B1"; + private const string cellClusterName = "B2"; + private const string cellWellName = "B3"; + private const string cellDeltaDate = "H2"; - await AddSheetsAsync(workbook, idsWells, token); + 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; - MemoryStream memoryStream = new MemoryStream(); - workbook.SaveAs(memoryStream, new SaveOptions { }); - memoryStream.Seek(0, SeekOrigin.Begin); - return memoryStream; - } + private readonly IAsbCloudDbContext dbContext; - private async Task AddSheetsAsync(XLWorkbook workbook, IEnumerable idsWells, CancellationToken token) - { - if(!idsWells.Any()) - return; + public DetectedOperationExportService(IAsbCloudDbContext dbContext) + { + this.dbContext = dbContext; + } - var wells = idsWells.Select(i => wellService.GetOrDefault(i)) - .Where(w => w is not null && w.IdTelemetry is not null) - .Select(w => w!); + public async Task 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 (!wells.Any()) - return; + if (well is null) + throw new ArgumentNullException(nameof(well)); - var idsTelemetries = wells.Select(w => w.IdTelemetry); - if (!idsTelemetries.Any()) - return; + if (!well.IdTelemetry.HasValue) + throw new ArgumentNullException(nameof(well)); - var operations = await db.DetectedOperations - .Include(o => o.OperationCategory) - .AsNoTracking() - .Where(o => idsTelemetries.Contains(o.IdTelemetry)) - .OrderBy(o => o.IdTelemetry) - .ThenBy(o => o.DateStart) - .ToListAsync(token); + var operations = await DetectOperationsAsync(well.IdTelemetry.Value, new DateTime(2023, 10, 14) + .ToUtcDateTimeOffset(well.Timezone.Hours), cancellationToken); - var groups = operations.GroupBy(o => o.IdTelemetry); + return await GenerateExcelFileStreamAsync(well, idDomain, operations, cancellationToken); + } - foreach (var well in wells) - { - var ops = groups.FirstOrDefault(g => g.Key == well.IdTelemetry) - ?.ToList(); - - if(ops?.Any() != true) - continue; + private async Task GenerateExcelFileStreamAsync(Well well, int idDomain, IEnumerable detectedOperations, + CancellationToken cancellationToken) + { + using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken); - var sheetName = $"{well.Cluster}_{well.Caption}" - .Replace('.','_'); + using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled); - var sheet = workbook.AddWorksheet(sheetName); - AddHeader(sheet); - const int headerHeight = 1; - for(var i = 0; i< ops.Count; i++ ) - AddRow(sheet, ops[i], well, i + 1 + headerHeight); - } - } + await AddToWorkbookAsync(workbook, well, idDomain, detectedOperations, cancellationToken); - private static void AddHeader(IXLWorksheet sheet) - { - var rowNumber = 1; - sheet.Cell(rowNumber, 1).Value = "Name"; - sheet.Column(1).Width = 34; - - sheet.Cell(rowNumber, 2).Value = "DateStart"; - sheet.Column(2).Width = 17; - - sheet.Cell(rowNumber, 3).Value = "DateEnd"; - sheet.Column(3).Width = 17; - - sheet.Cell(rowNumber, 4).Value = "DepthStart"; - sheet.Column(4).Width = 9; - - sheet.Cell(rowNumber, 5).Value = "DepthEnd"; - sheet.Column(5).Width = 9; - - sheet.Cell(rowNumber, 6).Value = "KeyValue"; - sheet.Column(6).Width = 9; + MemoryStream memoryStream = new MemoryStream(); + workbook.SaveAs(memoryStream, new SaveOptions { }); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } - sheet.SheetView.FreezeRows(rowNumber); - } + private async Task AddToWorkbookAsync(XLWorkbook workbook, Well well, int idDomain, IEnumerable detectedOperations, + CancellationToken cancellationToken) + { + const string sheetName = "Операции"; - private static void AddRow(IXLWorksheet sheet, DetectedOperation operation, WellDto well, int rowNumber) - { - var timezoneoffsetHours = well.Timezone.Hours; - sheet.Cell(rowNumber, 1).Value = operation.OperationCategory.Name; - sheet.Cell(rowNumber, 2).Value = operation.DateStart.ToRemoteDateTime(timezoneoffsetHours); - sheet.Cell(rowNumber, 3).Value = operation.DateEnd.ToRemoteDateTime(timezoneoffsetHours); - sheet.Cell(rowNumber, 4).Value = operation.DepthStart; - sheet.Cell(rowNumber, 5).Value = operation.DepthEnd; - sheet.Cell(rowNumber, 6).Value = operation.Value; - } - } + 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 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 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> 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 ?? float.NaN, + Pressure = d.Pressure ?? float.NaN, + HookWeight = d.HookWeight ?? float.NaN, + BlockPosition = d.BlockPosition ?? float.NaN, + BitDepth = d.BitDepth ?? float.NaN, + RotorSpeed = d.RotorSpeed ?? float.NaN, + }) + .OrderBy(d => d.DateTime); + + var startDate = begin; + var detectedOperations = new List(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; + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs index c3246e05..a3cf05c4 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs @@ -6,9 +6,7 @@ using AsbCloudDb; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; -using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -333,13 +331,6 @@ namespace AsbCloudInfrastructure.Services.DetectOperations .ToArrayAsync(token); return result; } - - public Task ExportAsync(IEnumerable idsWells, CancellationToken token) - { - var exportService = new DetectedOperationExportService(db, wellService); - return exportService.ExportAsync(idsWells, token); - } - } } diff --git a/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorAbstract.cs b/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorAbstract.cs index 280f77f0..38a3aeac 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorAbstract.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorAbstract.cs @@ -5,7 +5,7 @@ using System.Linq; namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors { - public abstract class DetectorAbstract + internal abstract class DetectorAbstract { private readonly int stepLength = 3; diff --git a/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorDrilling.cs b/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorDrilling.cs index a507c2da..3e7af4af 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorDrilling.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorDrilling.cs @@ -4,7 +4,7 @@ using AsbCloudDb.Model; namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors; -public class DetectorDrilling : DetectorAbstract +internal class DetectorDrilling : DetectorAbstract { public override Func GetIdOperation => DefineDrillingOperation; diff --git a/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorSlipsTime.cs b/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorSlipsTime.cs index 6bd75389..8be8b197 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorSlipsTime.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/Detectors/DetectorSlipsTime.cs @@ -4,7 +4,7 @@ using AsbCloudDb.Model; namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors { - public class DetectorSlipsTime : DetectorAbstract + internal class DetectorSlipsTime : DetectorAbstract { protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end) => CalcDeltaMinutes(telemetry, begin, end); diff --git a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs index 58950cba..11c58f92 100644 --- a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs +++ b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs @@ -5,9 +5,10 @@ using AsbCloudApp.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; +using AsbCloudInfrastructure.Services.DetectOperations; using Microsoft.AspNetCore.Http; namespace AsbCloudWebApi.Controllers.SAUB @@ -22,11 +23,14 @@ namespace AsbCloudWebApi.Controllers.SAUB { private readonly IDetectedOperationService detectedOperationService; private readonly IWellService wellService; - - public DetectedOperationController(IDetectedOperationService detectedOperationService, IWellService wellService) + private readonly DetectedOperationExportService detectedOperationExportService; + + public DetectedOperationController(IDetectedOperationService detectedOperationService, IWellService wellService, + DetectedOperationExportService detectedOperationExportService) { this.detectedOperationService = detectedOperationService; this.wellService = wellService; + this.detectedOperationExportService = detectedOperationExportService; } /// @@ -118,42 +122,22 @@ namespace AsbCloudWebApi.Controllers.SAUB /// Создает excel файл с операциями по скважине /// /// id скважины - /// - /// Токен отмены задачи - /// Запрашиваемый файл + /// Идентификатор домена [HttpGet("export")] [Permission] [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)] - public async Task ExportAsync(int? idWell, int? idCluster, CancellationToken token) + public async Task ExportAsync(int idWell, [Range(1, 2)]int idDomain, CancellationToken token) { - if (idCluster is null && idWell is null) - return this.ValidationBadRequest(nameof(idWell), $"One of {nameof(idWell)} or {nameof(idCluster)} mast be set."); - - int? idCompany = User.GetCompanyId(); + var idCompany = User.GetCompanyId(); if (idCompany is null) return Forbid(); - - IEnumerable idsWells; - if (idCluster is not null) - { - var companyWells = await wellService.GetAsync(new() { IdCompany = idCompany }, token); - idsWells = companyWells.Where(w => w.IdCluster == idCluster) - .Select(w=>w.Id); - } - else - { - if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, - idWell!.Value, token).ConfigureAwait(false)) - return Forbid(); - idsWells = new List { (int)idWell }; - } - - var stream = await detectedOperationService.ExportAsync(idsWells, token); - var fileName = "operations.xlsx"; - return File(stream, fileName); + + var stream = await detectedOperationExportService.ExportAsync(idWell, idDomain, token); + + return File(stream, "application/octet-stream", "operations.xlsx"); } } } diff --git a/ConsoleApp1/DetectedOperations/DetectedOperationExportService.cs b/ConsoleApp1/DetectedOperations/DetectedOperationExportService.cs deleted file mode 100644 index 5b9623b4..00000000 --- a/ConsoleApp1/DetectedOperations/DetectedOperationExportService.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using AsbCloudDb.Model; -using AsbCloudInfrastructure; -using AsbCloudInfrastructure.Services.DetectOperations; -using AsbCloudInfrastructure.Services.DetectOperations.Detectors; -using ClosedXML.Excel; -using Microsoft.EntityFrameworkCore; - -namespace ConsoleApp1.DetectedOperations; - -public class DetectedOperationExportService -{ - private static readonly DetectorAbstract[] detectors = new DetectorAbstract[] - { - new DetectorDrilling(), - new DetectorSlipsTime(), - }; - - 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 Export(int idWell, 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); - - var stream = await GenerateExcelFileStreamAsync(well, operations, cancellationToken); - - using var fileStream = File.Create("DetectedOperations.xlsx"); - - stream.CopyTo(fileStream); - } - - private async Task> 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 ?? float.NaN, - Pressure = d.Pressure ?? float.NaN, - HookWeight = d.HookWeight ?? float.NaN, - BlockPosition = d.BlockPosition ?? float.NaN, - BitDepth = d.BitDepth ?? float.NaN, - RotorSpeed = d.RotorSpeed ?? float.NaN, - }) - .OrderBy(d => d.DateTime); - - int take = 4 * 86_400; // 4 дня - var startDate = begin; - var detectedOperations = new List(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) - .Take(take) - .ToArrayAsync(token); - - if (data.Length < gap) - break; - - bool isDetected = false; - int positionBegin = 0; - int positionEnd = data.Length - gap; - int step = 10; - while (positionEnd > positionBegin) - { - step++; - for (int i = 0; i < detectors.Length; i++) - { - if (detectors[i].TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result)) - { - 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; - } - - private async Task GenerateExcelFileStreamAsync(Well well, IEnumerable detectedOperations, - CancellationToken cancellationToken) - { - using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken); - - using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled); - - await AddToWorkbookAsync(workbook, well, 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, IEnumerable 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, detectedOperations.OrderBy(x => x.DateStart).ThenBy(x => x.DepthStart).ToArray(), cancellationToken); - } - - private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, IList 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 = - $"https://cloud.digitaldrilling.ru/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 GetExcelTemplateStreamAsync(CancellationToken cancellationToken) - { - string resourceName = Assembly.GetExecutingAssembly() - .GetManifestResourceNames() - .FirstOrDefault(n => n.EndsWith("Operations.xlsx"))!; - - using var stream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream(resourceName)!; - - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, cancellationToken); - memoryStream.Position = 0; - - return memoryStream; - } -} \ No newline at end of file