diff --git a/AsbCloudApp/Data/DetectedOperationListDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationDrillersStatDto.cs
similarity index 66%
rename from AsbCloudApp/Data/DetectedOperationListDto.cs
rename to AsbCloudApp/Data/DetectedOperation/DetectedOperationDrillersStatDto.cs
index a18f6e87..65cce4aa 100644
--- a/AsbCloudApp/Data/DetectedOperationListDto.cs
+++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationDrillersStatDto.cs
@@ -1,12 +1,10 @@
-using System.Collections.Generic;
-
-namespace AsbCloudApp.Data
+namespace AsbCloudApp.Data.DetectedOperation
{
#nullable enable
///
/// Статистика по операциям бурильщика
///
- public class DetectedOperationStatDto
+ public class DetectedOperationDrillersStatDto
{
///
/// Бурильщик
@@ -38,18 +36,5 @@ namespace AsbCloudApp.Data
///
public double? Loss { get; set; }
}
-
- ///
- /// Автоматически определяемая операция
- ///
- public class DetectedOperationListDto
- {
- ///
- /// Список всех операций
- ///
- public IEnumerable Operations { get; set; }
-
- public IEnumerable Stats { get; set; }
- }
#nullable disable
}
diff --git a/AsbCloudApp/Data/DetectedOperationDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs
similarity index 98%
rename from AsbCloudApp/Data/DetectedOperationDto.cs
rename to AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs
index 59b28043..add539e4 100644
--- a/AsbCloudApp/Data/DetectedOperationDto.cs
+++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs
@@ -1,6 +1,6 @@
using System;
-namespace AsbCloudApp.Data
+namespace AsbCloudApp.Data.DetectedOperation
{
#nullable enable
///
diff --git a/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs
new file mode 100644
index 00000000..c52b2860
--- /dev/null
+++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace AsbCloudApp.Data.DetectedOperation
+{
+
+ ///
+ /// Автоматически определяемая операция
+ ///
+ public class DetectedOperationListDto
+ {
+ ///
+ /// Список всех операций
+ ///
+ public IEnumerable Operations { get; set; }
+
+ ///
+ /// Статистика по бурильщикам
+ ///
+ public IEnumerable Stats { get; set; }
+ }
+#nullable disable
+}
diff --git a/AsbCloudApp/Data/DetectedOperation/DetectedOperationStatDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationStatDto.cs
new file mode 100644
index 00000000..aebe2f46
--- /dev/null
+++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationStatDto.cs
@@ -0,0 +1,60 @@
+namespace AsbCloudApp.Data.DetectedOperation
+{
+#nullable enable
+ ///
+ /// Статистика по операциям например за период.
+ ///
+ public class DetectedOperationStatDto
+ {
+ ///
+ /// Id названия/описания операции
+ ///
+ public int IdCategory { get; set; }
+
+ ///
+ /// Название операции
+ ///
+ public string Category { get; set; } = string.Empty;
+
+ ///
+ /// Количество операций
+ ///
+ public int Count { get; set; }
+
+ ///
+ /// Среднее по ключевому показателю
+ ///
+ public double ValueAverage { get; set; }
+
+ ///
+ /// Мин по ключевому показателю
+ ///
+ public double ValueMin { get; set; }
+
+ ///
+ /// Макс по ключевому показателю
+ ///
+ public double ValueMax { get; set; }
+
+ ///
+ /// Суммарное время операций, мин
+ ///
+ public double MinutesTotal { get; set; }
+
+ ///
+ /// Мин продолжительность операции, мин
+ ///
+ public double MinutesMin { get; set; }
+
+ ///
+ /// Макс продолжительность операции, мин
+ ///
+ public double MinutesMax { get; set; }
+
+ ///
+ /// Средняя продолжительность операции, мин
+ ///
+ public double MinutesAverage { get; set; }
+ }
+#nullable disable
+}
diff --git a/AsbCloudApp/Requests/DetectedOperationRequest.cs b/AsbCloudApp/Requests/DetectedOperationRequest.cs
index 6d54a801..a6097b9c 100644
--- a/AsbCloudApp/Requests/DetectedOperationRequest.cs
+++ b/AsbCloudApp/Requests/DetectedOperationRequest.cs
@@ -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; }
///
- /// категория операций
+ /// категории операций
///
- [Required]
- public int IdCategory { get; set; }
+ public IEnumerable IdsCategories { get; set; }
///
/// Больше или равно дате
diff --git a/AsbCloudApp/Services/IDetectedOperationService.cs b/AsbCloudApp/Services/IDetectedOperationService.cs
index eb932c53..3d077443 100644
--- a/AsbCloudApp/Services/IDetectedOperationService.cs
+++ b/AsbCloudApp/Services/IDetectedOperationService.cs
@@ -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> GetCategoriesAsync(int? idWell, CancellationToken token);
- Task GetAsync(DetectedOperationRequest request, CancellationToken token);
+ Task?> GetCategoriesAsync(int? idWell, CancellationToken token);
+ Task GetAsync(DetectedOperationRequest request, CancellationToken token);
Task DeleteAsync(DetectedOperationRequest request, CancellationToken token);
+ Task?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token);
+ Task ExportAsync(IEnumerable idsWells, CancellationToken token);
}
+#nullable disable
}
diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs
new file mode 100644
index 00000000..ffca6b9f
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs
@@ -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 ExportAsync(IEnumerable 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 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;
+ }
+ }
+}
diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs
index 60ea4fcc..067d7e73 100644
--- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs
+++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs
@@ -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 GetAsync(DetectedOperationRequest request, CancellationToken token)
+ public async Task 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?> 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(groups.Count());
+ private static IEnumerable GetOperationsDrillersStat(IEnumerable operations)
+ {
+ var groups = operations.GroupBy(o => o.Driller);
+
+ var stats = new List(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?> 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 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 BuildQuery(WellDto well, DetectedOperationRequest request)
+ private IQueryable? 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> GetCategoriesAsync(int? idWell, CancellationToken token)
+ public async Task?> GetCategoriesAsync(int? idWell, CancellationToken token)
{
- IQueryable query = null;
+ IQueryable query;
if(idWell is null)
{
query = db.WellOperationCategories;
@@ -209,5 +282,12 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
.ToArrayAsync(token);
return result;
}
+
+ public Task ExportAsync(IEnumerable idsWells, CancellationToken token)
+ {
+ var exportService = new DetectedOperationExportService(db, wellService);
+ return exportService.ExportAsync(idsWells, token);
+ }
}
+#nullable disable
}
diff --git a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs
index d077c837..b4b7c098 100644
--- a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs
+++ b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs
@@ -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);
}
+ ///
+ /// Получить статистику по фильтрованному списку операций по телеметрии САУБ
+ ///
+ ///
+ ///
+ ///
+ [HttpGet("stat")]
+ [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)]
+ public async Task 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);
+ }
+
///
/// Удалить операции.
/// Удаленные операции будут определены повторно сервисом автоматизированного определения операций.
@@ -92,5 +113,46 @@ namespace AsbCloudWebApi.Controllers.SAUB
return true;
return false;
}
+
+ ///
+ /// Создает excel файл с операциями по скважине
+ ///
+ /// id скважины
+ ///
+ /// Токен отмены задачи
+ /// Запрашиваемый файл
+ [HttpGet]
+ [Route("export")]
+ [Permission]
+ [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)]
+ public async Task 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 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)idWell };
+ }
+
+ var stream = await detectedOperationService.ExportAsync(idsWells, token);
+ var fileName = "operations.xlsx";
+ return File(stream, "application/octet-stream", fileName);
+ }
}
}
diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs
index e73bb7f0..96f01ea8 100644
--- a/AsbCloudWebApi/Controllers/WellOperationController.cs
+++ b/AsbCloudWebApi/Controllers/WellOperationController.cs
@@ -268,7 +268,6 @@ namespace AsbCloudWebApi.Controllers
return File(stream, "application/octet-stream", fileName);
}
-
///
/// Создает excel файл с "сетевым графиком"
///