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 AsbCloudApp.Data.DetectedOperation; using AsbCloudInfrastructure.Services.DetectOperations.Detectors; using AsbCloudApp.Repositories; using Microsoft.AspNetCore.Http.Extensions; using AsbCloudApp.Exceptions; namespace AsbCloudInfrastructure.Services.DetectOperations; public class DetectedOperationExportService { private readonly DetectorAbstract[] detectors = { 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 const int columnComment = 10; //TODO: удалить неиспользуемую зависимость private readonly IAsbCloudDbContext dbContext; private readonly IWellOperationRepository wellOperationRepository; public DetectedOperationExportService(IAsbCloudDbContext dbContext, IWellOperationRepository wellOperationRepository) { this.dbContext = dbContext; this.wellOperationRepository = wellOperationRepository; } /// /// Экспорт excel файла с операциями по скважине /// /// ключ скважины /// хост /// /// /// public async Task ExportAsync(int idWell, string host, CancellationToken cancellationToken) { var well = await dbContext.Set() .Include(w => w.Cluster) .ThenInclude(c => c.Deposit) .FirstOrDefaultAsync(w => w.Id == idWell, cancellationToken); if (well is null) throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} does not exist"); if (!well.IdTelemetry.HasValue) throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} has no telemetry"); var operations = await DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, cancellationToken); return await GenerateExcelFileStreamAsync(well, host, operations, cancellationToken); } private async Task GenerateExcelFileStreamAsync(Well well, string host, IEnumerable operationDetectorResults, CancellationToken cancellationToken) { using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken); using var workbook = new XLWorkbook(excelTemplateStream); await AddToWorkbookAsync(workbook, well, host, 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, string host, IEnumerable operationDetectorResults, CancellationToken cancellationToken) { const string sheetName = "Операции"; if (!operationDetectorResults.Any()) return; var sheet = workbook.GetWorksheet(sheetName); await AddToSheetAsync(sheet, well, host, operationDetectorResults .OrderBy(x => x.Operation.DateStart).ThenBy(x => x.Operation.DepthStart).ToArray(), cancellationToken); } private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, string host, IList operationDetectorResults, CancellationToken cancellationToken) { var wellOperationCategories = await dbContext.WellOperationCategories.ToListAsync(cancellationToken); 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)); 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); 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); if (current.ExtraData.TryGetValue("IdReasonOfEnd", out object? idReasonOfEndObject) && idReasonOfEndObject is int idReasonOfEnd) { var reasonOfEnd = GetIdReasonOfEnd(idReasonOfEnd); row.Cell(columnIdReasonOfEnd).SetCellValue(reasonOfEnd); } var query = new QueryBuilder(); query.Add("end", dateStart.AddSeconds(1800 * 0.9).ToString("yyyy-MM-ddTHH:mm:ss.fff")); query.Add("range", "1800"); row.Cell(columnDateStart).SetCellValue(dateStart); var link = $"{host}/well/{well.Id}/telemetry/monitoring{query}"; row.Cell(columnDateStart).SetHyperlink(link); var deltaDepth = i > 0 && i + 1 < detectedOperations.Length ? current.DepthStart - detectedOperations[i - 1].DepthEnd : 0; row.Cell(columnDeltaDepth).SetCellValue(deltaDepth); var comment = CreateComment(operationDetectorResults[i]); row.Cell(columnComment).SetCellValue(comment); } } private static string GetCategoryName(IEnumerable wellOperationCategories, DetectedOperation current) { var idCategory = current.IdCategory; if (idCategory == WellOperationCategory.IdSlide && EnabledSubsystemsFlags.AutoOscillation.HasEnabledSubsystems(current.EnabledSubsystems)) 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 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}"; 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; } }