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.Text; using System.Threading; using System.Threading.Tasks; using AsbCloudInfrastructure.Services.DetectOperations.Detectors; using AsbCloudApp.Repositories; using AsbCloudInfrastructure.Repository; 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 const int columnComment = 10; private readonly IAsbCloudDbContext dbContext; private readonly IWellOperationRepository wellOperationRepository; public DetectedOperationExportService(IAsbCloudDbContext dbContext, IWellOperationRepository wellOperationRepository) { this.dbContext = dbContext; this.wellOperationRepository = wellOperationRepository; } 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 operationDetectorResults, CancellationToken cancellationToken) { using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken); using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled); await AddToWorkbookAsync(workbook, well, idDomain, operationDetectorResults, 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 operationDetectorResults, CancellationToken cancellationToken) { const string sheetName = "Операции"; if (!operationDetectorResults.Any()) return; var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetName) ?? throw new FileFormatException($"Книга excel не содержит листа {sheetName}."); await AddToSheetAsync(sheet, well, idDomain, operationDetectorResults .OrderBy(x => x.Operation.DateStart).ThenBy(x => x.Operation.DepthStart).ToArray(), cancellationToken); } private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, int idDomain, IList operationDetectorResults, 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 = operationDetectorResults.Max(o => o.Operation.DateEnd) - operationDetectorResults.Min(o => o.Operation.DateStart); var detectedOperations = operationDetectorResults.Select(o => o.Operation).ToArray(); for (int i = 0; i < operationDetectorResults.Count; i++) { var current = detectedOperations[i]; var dateStart = current.DateStart.ToRemoteDateTime(well.Timezone.Hours); var dateEnd = current.DateEnd.ToRemoteDateTime(well.Timezone.Hours); var row = sheet.Row(5 + i + headerRowsCount); row.Cell(columnOperationName).Value = GetCategoryName(wellOperationCategories, current); row.Cell(columnDateEnd).Value = dateEnd; row.Cell(columnDuration).Value = (dateEnd - dateStart).TotalMinutes; row.Cell(columnDepthStart).Value = current.DepthStart; row.Cell(columnDepthEnd).Value = current.DepthEnd; row.Cell(columnDepth).Value = current.DepthEnd - current.DepthStart; if (current.ExtraData.TryGetValue("IdReasonOfEnd", out object? idReasonOfEndObject) && idReasonOfEndObject is int idReasonOfEnd) row.Cell(columnIdReasonOfEnd).Value = GetIdReasonOfEnd(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.Length ? current.DepthStart - detectedOperations[i - 1].DepthEnd : 0; row.Cell(columnComment).Value = CreateComment(operationDetectorResults[i]); } } private static string GetCategoryName(IEnumerable wellOperationCategories, DetectedOperation current) { var idCategory = current.IdCategory; if (idCategory == WellOperationCategory.IdSlide && current.ExtraData[DetectorDrilling.ExtraDataKeyHasOscillation] is bool hasOscillation && hasOscillation) 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 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 static string CreateComment(OperationDetectorResult operationDetectorResult) { var operation = operationDetectorResult.Operation; switch (operation.IdCategory) { case WellOperationCategory.IdRotor: case WellOperationCategory.IdSlide: { var avgRotorSpeed = operation.ExtraData[DetectorDrilling.ExtraDataKeyAvgRotorSpeed]; var dispersionOfNormalizedRotorSpeed = operation.ExtraData[DetectorDrilling.ExtraDataKeyDispersionOfNormalizedRotorSpeed]; var isAfbEnabledObject = operation.ExtraData[DetectorDrilling.ExtraDataKeyIsAfbEnabled]; var AfbState = ""; if (isAfbEnabledObject is bool isAfbEnabled && isAfbEnabled) AfbState = "АКБ - вкл"; var comment = $"Средняя скорость оборотов ротора: {avgRotorSpeed}\r\n" + $"Дисперсия нормированных оборотов ротора: {dispersionOfNormalizedRotorSpeed} \r\n" + $"{AfbState}"; return comment; } default: return string.Empty; } } 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 detectedOperationResults = 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; while (positionEnd > positionBegin) { foreach (var detector in detectors) { if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result)) continue; detectedOperationResults.Add(result!); lastDetectedOperation = result!.Operation; isDetected = true; positionBegin = result.TelemetryEnd; break; } positionBegin += 1; } if (isDetected) startDate = lastDetectedOperation!.DateEnd; else startDate = data[positionEnd].DateTime; } return detectedOperationResults; } }