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 domains = new Dictionary { { 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 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 GenerateExcelFileStreamAsync(Well well, int idDomain, IEnumerable 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 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 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, 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(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; } }