Add internal DetectedOperationExportService.

Add DetectedOperation statistics
This commit is contained in:
ngfrolov 2022-08-04 15:06:17 +05:00
parent 6f83e8c7cc
commit d2b98d3a77
10 changed files with 376 additions and 45 deletions

View File

@ -1,12 +1,10 @@
using System.Collections.Generic;
namespace AsbCloudApp.Data
namespace AsbCloudApp.Data.DetectedOperation
{
#nullable enable
/// <summary>
/// Статистика по операциям бурильщика
/// </summary>
public class DetectedOperationStatDto
public class DetectedOperationDrillersStatDto
{
/// <summary>
/// Бурильщик
@ -38,18 +36,5 @@ namespace AsbCloudApp.Data
/// </summary>
public double? Loss { get; set; }
}
/// <summary>
/// Автоматически определяемая операция
/// </summary>
public class DetectedOperationListDto
{
/// <summary>
/// Список всех операций
/// </summary>
public IEnumerable<DetectedOperationDto> Operations { get; set; }
public IEnumerable<DetectedOperationStatDto> Stats { get; set; }
}
#nullable disable
}

View File

@ -1,6 +1,6 @@
using System;
namespace AsbCloudApp.Data
namespace AsbCloudApp.Data.DetectedOperation
{
#nullable enable
/// <summary>

View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace AsbCloudApp.Data.DetectedOperation
{
/// <summary>
/// Автоматически определяемая операция
/// </summary>
public class DetectedOperationListDto
{
/// <summary>
/// Список всех операций
/// </summary>
public IEnumerable<DetectedOperationDto> Operations { get; set; }
/// <summary>
/// Статистика по бурильщикам
/// </summary>
public IEnumerable<DetectedOperationDrillersStatDto> Stats { get; set; }
}
#nullable disable
}

View File

@ -0,0 +1,60 @@
namespace AsbCloudApp.Data.DetectedOperation
{
#nullable enable
/// <summary>
/// Статистика по операциям например за период.
/// </summary>
public class DetectedOperationStatDto
{
/// <summary>
/// Id названия/описания операции
/// </summary>
public int IdCategory { get; set; }
/// <summary>
/// Название операции
/// </summary>
public string Category { get; set; } = string.Empty;
/// <summary>
/// Количество операций
/// </summary>
public int Count { get; set; }
/// <summary>
/// Среднее по ключевому показателю
/// </summary>
public double ValueAverage { get; set; }
/// <summary>
/// Мин по ключевому показателю
/// </summary>
public double ValueMin { get; set; }
/// <summary>
/// Макс по ключевому показателю
/// </summary>
public double ValueMax { get; set; }
/// <summary>
/// Суммарное время операций, мин
/// </summary>
public double MinutesTotal { get; set; }
/// <summary>
/// Мин продолжительность операции, мин
/// </summary>
public double MinutesMin { get; set; }
/// <summary>
/// Макс продолжительность операции, мин
/// </summary>
public double MinutesMax { get; set; }
/// <summary>
/// Средняя продолжительность операции, мин
/// </summary>
public double MinutesAverage { get; set; }
}
#nullable disable
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Requests
@ -15,10 +16,9 @@ namespace AsbCloudApp.Requests
public int IdWell { get; set; }
/// <summary>
/// категория операций
/// категории операций
/// </summary>
[Required]
public int IdCategory { get; set; }
public IEnumerable<int> IdsCategories { get; set; }
/// <summary>
/// Больше или равно дате

View File

@ -1,15 +1,21 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Requests;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services
{
#nullable enable
public interface IDetectedOperationService
{
Task<IEnumerable<WellOperationCategoryDto>> GetCategoriesAsync(int? idWell, CancellationToken token);
Task<DetectedOperationListDto> GetAsync(DetectedOperationRequest request, CancellationToken token);
Task<IEnumerable<WellOperationCategoryDto>?> GetCategoriesAsync(int? idWell, CancellationToken token);
Task<DetectedOperationListDto?> GetAsync(DetectedOperationRequest request, CancellationToken token);
Task<int> DeleteAsync(DetectedOperationRequest request, CancellationToken token);
Task<IEnumerable<DetectedOperationStatDto>?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token);
Task<Stream> ExportAsync(IEnumerable<int> idsWells, CancellationToken token);
}
#nullable disable
}

View File

@ -0,0 +1,117 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using ClosedXML.Excel;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.DetectOperations
{
internal class DetectedOperationExportService
{
private readonly IAsbCloudDbContext db;
private readonly IWellService wellService;
public DetectedOperationExportService(IAsbCloudDbContext db, IWellService wellService)
{
this.db = db;
this.wellService = wellService;
}
public async Task<Stream> ExportAsync(IEnumerable<int> idsWells, CancellationToken token)
{
using var workbook = new XLWorkbook(XLEventTracking.Disabled);
await AddSheetsAsync(workbook, idsWells, token);
MemoryStream memoryStream = new MemoryStream();
workbook.SaveAs(memoryStream, new SaveOptions { });
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
private async Task AddSheetsAsync(XLWorkbook workbook, IEnumerable<int> idsWells, CancellationToken token)
{
if(!idsWells.Any())
return;
var wells = idsWells.Select(i => wellService.GetOrDefault(i))
.Where(w => w is not null && w.IdTelemetry is not null);
if (!wells.Any())
return;
var idsTelemetries = wells.Select(w => w.IdTelemetry);
if (!idsTelemetries.Any())
return;
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 groups = operations.GroupBy(o => o.IdTelemetry);
foreach (var well in wells)
{
var ops = groups.FirstOrDefault(g => g.Key == well.IdTelemetry)
?.ToList();
if(ops?.Any() != true)
continue;
var sheetName = $"{well.Cluster}_{well.Caption}"
.Replace('.','_');
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);
}
}
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;
sheet.SheetView.FreezeRows(rowNumber);
}
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;
}
}
}

View File

@ -1,4 +1,5 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
@ -7,12 +8,14 @@ using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.DetectOperations
{
#nullable enable
public class DetectedOperationService : IDetectedOperationService
{
public const int IdOperationRotor = 1;
@ -33,27 +36,50 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
this.scheduleService = scheduleService;
}
public async Task<DetectedOperationListDto> GetAsync(DetectedOperationRequest request, CancellationToken token)
public async Task<DetectedOperationListDto?> GetAsync(DetectedOperationRequest request, CancellationToken token)
{
var dtos = await GetOperationsAsync(request, token);
if (dtos?.Any() != true)
return null;
var stats = GetOperationsDrillersStat(dtos);
var result = new DetectedOperationListDto
{
Operations = dtos,
Stats = stats
};
return result;
}
private async Task<IEnumerable<DetectedOperationDto>?> GetOperationsAsync(DetectedOperationRequest request, CancellationToken token)
{
var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
if (well?.IdTelemetry is null || well.Timezone is null)
return null;
var query = BuildQuery(well, request)
.AsNoTracking();
?.AsNoTracking();
if (query is null)
return null;
var data = await query.ToListAsync(token);
var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
var schedules = await scheduleService.GetByIdWellAsync(request.IdWell, token);
var dtos = data.Select(o => Convert(o, well, operationValues, schedules));
var groups = dtos.GroupBy(o => o.Driller);
return dtos;
}
var stats = new List<DetectedOperationStatDto>(groups.Count());
private static IEnumerable<DetectedOperationDrillersStatDto> GetOperationsDrillersStat(IEnumerable<DetectedOperationDto> operations)
{
var groups = operations.GroupBy(o => o.Driller);
var stats = new List<DetectedOperationDrillersStatDto>(groups.Count());
foreach (var group in groups)
{
var itemsWithTarget = group.Where(i => i.OperationValue is not null);
var stat = new DetectedOperationStatDto
var stat = new DetectedOperationDrillersStatDto
{
Driller = group.Key,
AverageValue = group.Sum(e => e.Value) / group.Count(),
@ -62,20 +88,62 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
if (itemsWithTarget.Any())
{
var itemsOutOfTarget = itemsWithTarget.Where(o => !IsTargetOk(o));
stat.AverageTargetValue = itemsWithTarget.Average(e => e.OperationValue.TargetValue);
stat.AverageTargetValue = itemsWithTarget.Average(e => e.OperationValue?.TargetValue);
stat.Efficiency = 100d * itemsOutOfTarget.Count() / itemsWithTarget.Count();
stat.Loss = itemsOutOfTarget.Sum(DeltaToTarget);
}
stats.Add(stat);
}
return stats;
}
var result = new DetectedOperationListDto
{
Operations = dtos,
Stats = stats
};
return result;
public async Task<IEnumerable<DetectedOperationStatDto>?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token)
{
var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
if (well?.IdTelemetry is null || well.Timezone is null)
return null;
var query = BuildQuery(well, request)
?.AsNoTracking();
if (query is null)
return null;
var entities = await query
.Select(o => new {
o.IdCategory,
DurationMinutes = (o.DateEnd - o.DateStart).TotalMinutes,
o.Value,
})
.ToListAsync(token);
if (!entities.Any())
return null;
var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
var categories = await query
.Select(o => new {o.IdCategory, o.OperationCategory.Name })
.Distinct()
.ToDictionaryAsync(c=>c.IdCategory, c=>c.Name, token);
var dtos = entities
.GroupBy(o => o.IdCategory)
.OrderBy(g => g.Key)
.Select(g => new DetectedOperationStatDto{
IdCategory = g.Key,
Category = categories[g.Key],
Count = g.Count(),
MinutesAverage = g.Average(o => o.DurationMinutes),
MinutesMin = g.Min(o => o.DurationMinutes),
MinutesMax = g.Max(o => o.DurationMinutes),
MinutesTotal = g.Sum(o => o.DurationMinutes),
ValueAverage = g.Average(o => o.Value),
ValueMax = g.Max(o => o.Value),
ValueMin = g.Min(o => o.Value),
});
return dtos;
}
public async Task<int> DeleteAsync(DetectedOperationRequest request, CancellationToken token)
@ -85,6 +153,10 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
return 0;
var query = BuildQuery(well, request);
if (query is null)
return 0;
db.DetectedOperations.RemoveRange(query);
return await db.SaveChangesAsync(token);
}
@ -93,10 +165,10 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
{
return (op.IdCategory) switch
{
IdOperationRotor => op.Value > op.OperationValue.TargetValue,
IdOperationSlide => op.Value > op.OperationValue.TargetValue,
IdOperationSlipsTime => op.Value > op.OperationValue.TargetValue,
_ => op.Value > op.OperationValue.TargetValue,
IdOperationRotor => op.Value > op.OperationValue?.TargetValue,
IdOperationSlide => op.Value > op.OperationValue?.TargetValue,
IdOperationSlipsTime => op.Value > op.OperationValue?.TargetValue,
_ => op.Value > op.OperationValue?.TargetValue,
};
}
@ -106,12 +178,12 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
{
IdOperationRotor => 0,
IdOperationSlide => 0,
IdOperationSlipsTime => op.Value - op.OperationValue.TargetValue,
IdOperationSlipsTime => op.Value - op.OperationValue?.TargetValue??0,
_ => 0,
};
}
private IQueryable<DetectedOperation> BuildQuery(WellDto well, DetectedOperationRequest request)
private IQueryable<DetectedOperation>? BuildQuery(WellDto well, DetectedOperationRequest request)
{
if (well?.IdTelemetry is null || well.Timezone is null)
return null;
@ -122,7 +194,8 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
if (request is not null)
{
query = query.Where(o => request.IdCategory == o.IdCategory);
if (request.IdsCategories?.Any() == true)
query = query.Where(o => request.IdsCategories.Contains(o.IdCategory));
if (request.GtDate is not null)
query = query.Where(o => o.DateStart >= request.GtDate.Value.ToUtcDateTimeOffset(well.Timezone.Hours));
@ -182,9 +255,9 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
return dto;
}
public async Task<IEnumerable<WellOperationCategoryDto>> GetCategoriesAsync(int? idWell, CancellationToken token)
public async Task<IEnumerable<WellOperationCategoryDto>?> GetCategoriesAsync(int? idWell, CancellationToken token)
{
IQueryable<WellOperationCategory> query = null;
IQueryable<WellOperationCategory> query;
if(idWell is null)
{
query = db.WellOperationCategories;
@ -209,5 +282,12 @@ 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);
}
}
#nullable disable
}

View File

@ -1,10 +1,12 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -61,6 +63,25 @@ namespace AsbCloudWebApi.Controllers.SAUB
return Ok(result);
}
/// <summary>
/// Получить статистику по фильтрованному списку операций по телеметрии САУБ
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("stat")]
[ProducesResponseType(typeof(IEnumerable<DetectedOperationStatDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetStatAsync(
[FromQuery] DetectedOperationRequest request,
CancellationToken token = default)
{
if (!await UserHasAccesToWellAsync(request.IdWell, token))
return Forbid();
var result = await detectedOperationService.GetOperationsStatAsync(request, token);
return Ok(result);
}
/// <summary>
/// Удалить операции.
/// Удаленные операции будут определены повторно сервисом автоматизированного определения операций.
@ -92,5 +113,46 @@ namespace AsbCloudWebApi.Controllers.SAUB
return true;
return false;
}
/// <summary>
/// Создает excel файл с операциями по скважине
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="idCluster"></param>
/// <param name="token"> Токен отмены задачи </param>
/// <returns>Запрашиваемый файл</returns>
[HttpGet]
[Route("export")]
[Permission]
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> ExportAsync(int? idWell, int? idCluster, CancellationToken token = default)
{
if (idCluster is null && idWell is null)
return this.MakeBadRequest(nameof(idWell), $"One of {nameof(idWell)} or {nameof(idCluster)} mast be set.");
int? idCompany = User.GetCompanyId();
if (idCompany is null)
return Forbid();
IEnumerable<int> idsWells;
if (idCluster is not null)
{
var companyWells = await wellService.GetWellsByCompanyAsync((int)idCompany, token);
idsWells = companyWells.Where(w => w.IdCluster == idCluster)
.Select(w=>w.Id);
}
else
{
if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany,
(int)idWell, 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, "application/octet-stream", fileName);
}
}
}

View File

@ -268,7 +268,6 @@ namespace AsbCloudWebApi.Controllers
return File(stream, "application/octet-stream", fileName);
}
/// <summary>
/// Создает excel файл с "сетевым графиком"
/// </summary>