forked from ddrilling/AsbCloudServer
Правки + перенёс экспорт автоопределённых операций в апи
This commit is contained in:
parent
909f8abb4d
commit
081c0c5070
@ -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
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<DetectedOperationStatDto>?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Выгрузка в Excel
|
||||
/// </summary>
|
||||
/// <param name="idsWells"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<Stream> ExportAsync(IEnumerable<int> idsWells, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Services\DetectOperations\DetectOperations.xlsx" />
|
||||
<EmbeddedResource Include="Services\DailyReport\DailyReportTemplate.xlsx" />
|
||||
<EmbeddedResource Include="Services\Trajectory\PlannedTrajectoryTemplate.xlsx" />
|
||||
<EmbeddedResource Include="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
|
||||
|
@ -289,6 +289,8 @@ namespace AsbCloudInfrastructure
|
||||
services.AddTransient<IWellOperationExcelParser<WellOperationImportDefaultOptionsDto>, WellOperationDefaultExcelParser>();
|
||||
services.AddTransient<IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto>, WellOperationGazpromKhantosExcelParser>();
|
||||
|
||||
services.AddTransient<DetectedOperationExportService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
@ -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<int, string> domains = new Dictionary<int, string>
|
||||
{
|
||||
{ 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<Stream> ExportAsync(IEnumerable<int> 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<int> 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<Stream> 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<Stream> GenerateExcelFileStreamAsync(Well well, int idDomain, IEnumerable<DetectedOperation> 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<DetectedOperation> 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<DetectedOperation> 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<Stream> 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<IEnumerable<DetectedOperation>> 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<DetectedOperation>(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;
|
||||
}
|
||||
}
|
@ -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<Stream> ExportAsync(IEnumerable<int> idsWells, CancellationToken token)
|
||||
{
|
||||
var exportService = new DetectedOperationExportService(db, wellService);
|
||||
return exportService.ExportAsync(idsWells, token);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -4,7 +4,7 @@ using AsbCloudDb.Model;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors;
|
||||
|
||||
public class DetectorDrilling : DetectorAbstract
|
||||
internal class DetectorDrilling : DetectorAbstract
|
||||
{
|
||||
public override Func<DetectableTelemetry[], int, int, int> GetIdOperation => DefineDrillingOperation;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -118,42 +122,22 @@ namespace AsbCloudWebApi.Controllers.SAUB
|
||||
/// Создает excel файл с операциями по скважине
|
||||
/// </summary>
|
||||
/// <param name="idWell">id скважины</param>
|
||||
/// <param name="idCluster"></param>
|
||||
/// <param name="token"> Токен отмены задачи </param>
|
||||
/// <returns>Запрашиваемый файл</returns>
|
||||
/// <param name="idDomain">Идентификатор домена</param>
|
||||
[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<IActionResult> ExportAsync(int? idWell, int? idCluster, CancellationToken token)
|
||||
public async Task<IActionResult> 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<int> 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> { (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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<IEnumerable<DetectedOperation>> 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<DetectedOperation>(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<Stream> GenerateExcelFileStreamAsync(Well well, IEnumerable<DetectedOperation> 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<DetectedOperation> 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<DetectedOperation> 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<Stream> 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user