diff --git a/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs index 0386e8b3..2b0fc768 100644 --- a/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs +++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs @@ -1,100 +1,85 @@ using System; using System.ComponentModel.DataAnnotations; -namespace AsbCloudApp.Data.DetectedOperation +namespace AsbCloudApp.Data.DetectedOperation; + +/// +/// Автоматически определенная операция +/// +public class DetectedOperationDto: IId { + /// + [Required] + public int Id { get; set; } + /// - /// Автоматически определяемая операция + /// Id телеметрии /// - public class DetectedOperationDto : IId, IWellRelated - { - /// - [Required] - public int Id { get; set; } + [Required] + public int IdTelemetry { get; set; } - /// - [Required] - public int IdWell { get; set; } + /// + /// Id названия/описания операции + /// + [Required] + public int IdCategory { get; set; } - /// - /// Id телеметрии - /// - [Required] - public int IdTelemetry { get; set; } + /// + /// Id пользователя панели на момент начала операции + /// + [Required] + public int IdUserAtStart { get; set; } - /// - /// Id названия/описания операции - /// - [Required] - public int IdCategory { get; set; } + /// + /// Дата завершения операции в часовом поясе скважины + /// + [Required] + public DateTimeOffset DateEnd { get; set; } - /// - /// Id пользователя панели - /// - [Required] - public int IdUsersAtStart { get; set; } + /// + /// Дата начала операции в часовом поясе скважины + /// + [Required] + public DateTimeOffset DateStart { get; set; } - /// - /// Дата начала операции в часовом поясе скважины - /// - [Required] - public DateTime DateStart { get; set; } + /// + /// глубина на завершения операции, м + /// + [Required] + public double DepthEnd { get; set; } - /// - /// Дата завершения операции в часовом поясе скважины - /// - [Required] - public DateTime DateEnd { get; set; } + /// + /// глубина на начало операции, м + /// + [Required] + public double DepthStart { get; set; } - /// - /// Продолжительность операции в минутах - /// - [Required] - public double DurationMinutes => (DateEnd - DateStart).TotalMinutes; + /// + /// Продолжительность операции в минутах + /// + [Required] + public double DurationMinutes => (DateEnd - DateStart).TotalMinutes; - /// - /// глубина на начало операции, м - /// - [Required] - public double DepthStart { get; set; } + /// + /// Флаг включенной подсистемы + /// + [Required] + public int EnabledSubsystems { get; set; } - /// - /// глубина на завершения операции, м - /// - [Required] - public double DepthEnd { get; set; } + /// + /// название/описание операции + /// + [Required] + public WellOperationCategoryDto OperationCategory { get; set; } = null!; - /// - /// название/описание операции - /// - [Required] - public WellOperationCategoryDto OperationCategory { get; set; } = null!; + /// + /// Пользователь панели оператора + /// + public string? TelemetryUserName { get; set; } - /// - /// Пользователь панели оператора - /// - public string? TelemetryUserName { get; set; } - - /// - /// Бурильщик - /// - public DrillerDto? Driller { get; set; } - - /// - /// Целевые/нормативные показатели - /// - public OperationValueDto? OperationValue { get; set; } - - /// - /// Ключевой параметр операции - /// - [Required] - public double Value { get; set; } - - /// - /// Флаг включенной подсистемы - /// - [Required] - public int EnabledSubsystems { get; set; } - } -} + /// + /// Ключевой параметр операции + /// + [Required] + public double Value { get; set; } +} \ No newline at end of file diff --git a/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs index fef979bb..32dd6040 100644 --- a/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs +++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs @@ -13,7 +13,7 @@ namespace AsbCloudApp.Data.DetectedOperation /// Список всех операций /// [Required] - public IEnumerable Operations { get; set; } = Enumerable.Empty(); + public IEnumerable Operations { get; set; } = Enumerable.Empty(); /// /// Статистика по бурильщикам diff --git a/AsbCloudApp/Data/DetectedOperation/DetectedOperationWithDrillerDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationWithDrillerDto.cs new file mode 100644 index 00000000..612b079c --- /dev/null +++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationWithDrillerDto.cs @@ -0,0 +1,18 @@ +namespace AsbCloudApp.Data.DetectedOperation +{ + /// + /// Автоматически определяемая операция + /// + public class DetectedOperationWithDrillerDto : DetectedOperationDto + { + /// + /// Бурильщик + /// + public DrillerDto? Driller { get; set; } + + /// + /// Целевые/нормативные показатели + /// + public OperationValueDto? OperationValue { get; set; } + } +} diff --git a/AsbCloudApp/Data/SimpleTimezoneDto.cs b/AsbCloudApp/Data/SimpleTimezoneDto.cs index 3ec75814..5d2e1f8f 100644 --- a/AsbCloudApp/Data/SimpleTimezoneDto.cs +++ b/AsbCloudApp/Data/SimpleTimezoneDto.cs @@ -1,25 +1,32 @@ +using System; + namespace AsbCloudApp.Data { /// - /// + /// временная зона /// public class SimpleTimezoneDto { /// - /// UTC + /// смещение в часах относительно UTC /// public double Hours { get; set; } /// - /// + /// идентификатор часовой зоны /// public string? TimezoneId { get; set; } /// - /// + /// запрет на переопределение /// public bool IsOverride { get; set; } + /// + /// Смещение часового пояса + /// + public TimeSpan Offset => TimeSpan.FromHours(Hours); + /// public override bool Equals(object? obj) { diff --git a/AsbCloudApp/Data/TimeDto.cs b/AsbCloudApp/Data/TimeDto.cs index 6a3620f2..20ffa4ac 100644 --- a/AsbCloudApp/Data/TimeDto.cs +++ b/AsbCloudApp/Data/TimeDto.cs @@ -86,6 +86,14 @@ namespace AsbCloudApp.Data second = fullDate.Second; } + /// + public TimeDto(DateTimeOffset fullDate) + { + hour = fullDate.Hour; + minute = fullDate.Minute; + second = fullDate.Second; + } + /// /// Makes System.TimeOnly /// diff --git a/AsbCloudApp/Exceptions/ArgumentInvalidException.cs b/AsbCloudApp/Exceptions/ArgumentInvalidException.cs index 5db63313..641f18ce 100644 --- a/AsbCloudApp/Exceptions/ArgumentInvalidException.cs +++ b/AsbCloudApp/Exceptions/ArgumentInvalidException.cs @@ -14,6 +14,7 @@ namespace AsbCloudApp.Exceptions /// public IDictionary ErrorState { get; } = null!; + // TODO: swap arguments, inherit from ArgumentException /// /// конструктор /// diff --git a/AsbCloudApp/Repositories/IDetectedOperationRepository.cs b/AsbCloudApp/Repositories/IDetectedOperationRepository.cs new file mode 100644 index 00000000..5a52b455 --- /dev/null +++ b/AsbCloudApp/Repositories/IDetectedOperationRepository.cs @@ -0,0 +1,66 @@ +using AsbCloudApp.Data.DetectedOperation; +using AsbCloudApp.Requests; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; + +namespace AsbCloudApp.Repositories; + +/// +/// Таблица автоматически определенных операций +/// +public interface IDetectedOperationRepository +{ + /// + /// Добавление записей + /// + /// + /// + /// + /// + Task Insert(int? idUser, IEnumerable dtos, CancellationToken token); + + /// + /// Получить автоматически определенные операции по телеметрии + /// + /// + /// + /// + Task> Get(DetectedOperationByTelemetryRequest request, CancellationToken token); + + /// + /// Редактирование записей + /// + /// + /// + /// + /// + Task Update(int idUser, IEnumerable dtos, CancellationToken token); + + /// + /// Добавляет Dto у которых id == 0, изменяет dto у которых id != 0 + /// + /// + /// + /// + /// + Task UpdateOrInsert(int idUser, IEnumerable dtos, CancellationToken token); + + /// + /// Удалить операции + /// + /// + /// + /// + /// + Task Delete(int idUser, DetectedOperationByTelemetryRequest request, CancellationToken token); + + /// + /// Удаление записей + /// + /// + /// + /// + /// + Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token); +} diff --git a/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs b/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs new file mode 100644 index 00000000..4e18c231 --- /dev/null +++ b/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs @@ -0,0 +1,17 @@ +using AsbCloudApp.Data; +using System.Collections.Generic; + +namespace AsbCloudApp.Repositories +{ + /// + /// сервис операций по скважине + /// + public interface IWellOperationCategoryRepository + { + /// + /// список названий операций + /// + /// + IEnumerable Get(bool includeParents); + } +} \ No newline at end of file diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index 8cb313ac..ff3bfb8e 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -13,10 +13,12 @@ namespace AsbCloudApp.Repositories /// public interface IWellOperationRepository { + //TODO: replace all references /// /// список названий операций /// /// + [Obsolete("use IWellOperationCategoryRepository.GetCategories(bool includeParents)")] IEnumerable GetCategories(bool includeParents); /// diff --git a/AsbCloudApp/Requests/DetectedOperationRequest.cs b/AsbCloudApp/Requests/DetectedOperationRequest.cs index dcf66aab..243d6e9f 100644 --- a/AsbCloudApp/Requests/DetectedOperationRequest.cs +++ b/AsbCloudApp/Requests/DetectedOperationRequest.cs @@ -1,56 +1,114 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; -namespace AsbCloudApp.Requests +namespace AsbCloudApp.Requests; + +/// +/// Запрос на получение операций определенных по телеметрии +/// +public class DetectedOperationByTelemetryRequest : DetectedOperationRequest { /// - /// Параметры запроса на получение операций определенных по телеметрии + /// id телеметрии /// - public class DetectedOperationRequest : RequestBase + [Required] + public int IdTelemetry { get; set; } + + /// + /// Запрос на получение операций определенных по id телеметрии + /// + public DetectedOperationByTelemetryRequest() + {} + + /// + /// Запрос на получение операций определенных по id телеметрии. Copy + /// + /// + /// + public DetectedOperationByTelemetryRequest(int idTelemetry, DetectedOperationRequest request) + :base(request) { - /// - /// категория операций - /// - [Required] - public int IdWell { get; set; } - - /// - /// Список id телеметрий - /// пустой список - нет фильтрации - /// - public IEnumerable IdsTelemetries { get; set; } = Array.Empty(); - - /// - /// категории операций - /// - public IEnumerable IdsCategories { get; set; } = Array.Empty(); - - /// - /// Больше или равно дате - /// - public DateTimeOffset? GeDateStart { get; set; } - - /// - /// Меньше или равно дате - /// - public DateTimeOffset? LeDateEnd { get; set; } - - /// - /// Больше или равно глубины забоя - /// - public double? GeDepth { get; set; } - - /// - /// Меньше или равно глубины забоя - /// - public double? LeDepth { get; set; } - - /// - /// Фильтр по пользователю панели - /// - public int? IdTelemetryUser { get; set; } - + IdTelemetry = idTelemetry; + } +} + +/// +/// Запрос на получение операций определенных по id скважины +/// +public class DetectedOperationByWellRequest : DetectedOperationRequest +{ + /// + /// id скважины + /// + [Required] + public int IdWell { get; set; } + + /// + /// Запрос на получение операций определенных по id скважины + /// + public DetectedOperationByWellRequest() + {} + + /// + /// Запрос на получение операций определенных по id скважины. Copy + /// + public DetectedOperationByWellRequest(int idWell, DetectedOperationRequest request) + : base(request) + { + IdWell = idWell; + } +} + +/// +/// Запрос на получение операций определенных по телеметрии +/// +public class DetectedOperationRequest : RequestBase +{ + /// + /// категории операций + /// + public IEnumerable IdsCategories { get; set; } + + /// + /// Больше или равно дате + /// + public DateTimeOffset? GeDateStart { get; set; } + + /// + /// Меньше или равно дате + /// + public DateTimeOffset? LeDateEnd { get; set; } + + /// + /// Больше или равно глубины забоя + /// + public double? GeDepthStart { get; set; } + + /// + /// Меньше или равно глубины забоя + /// + public double? LeDepthEnd { get; set; } + + /// + /// Запрос на получение операций определенных по телеметрии + /// + public DetectedOperationRequest() + { + IdsCategories = new List(); + } + + /// + /// Запрос на получение операций определенных по телеметрии. Copy + /// + /// + public DetectedOperationRequest(DetectedOperationRequest request) + : base(request) + { + IdsCategories = request.IdsCategories; + GeDateStart = request.GeDateStart; + LeDateEnd = request.LeDateEnd; + GeDepthStart = request.GeDepthStart; + LeDepthEnd = request.LeDepthEnd; } } diff --git a/AsbCloudApp/Requests/RequestBase.cs b/AsbCloudApp/Requests/RequestBase.cs index 2ae96e54..c5d70a68 100644 --- a/AsbCloudApp/Requests/RequestBase.cs +++ b/AsbCloudApp/Requests/RequestBase.cs @@ -23,5 +23,23 @@ namespace AsbCloudApp.Requests /// Указать направление сортировки можно через пробел "asc" или "desc" /// public IEnumerable? SortFields { get; set; } + + /// + /// Базовые параметры запроса + /// + public RequestBase() + { + } + + /// + /// Базовые параметры запроса. Копирующий конструктор + /// + /// + public RequestBase(RequestBase request) + { + Skip = request.Skip; + Take = request.Take; + SortFields = request.SortFields; + } } } diff --git a/AsbCloudApp/Services/IDetectedOperationService.cs b/AsbCloudApp/Services/IDetectedOperationService.cs index e4f8f201..5109fa56 100644 --- a/AsbCloudApp/Services/IDetectedOperationService.cs +++ b/AsbCloudApp/Services/IDetectedOperationService.cs @@ -19,7 +19,7 @@ namespace AsbCloudApp.Services /// /// /// - Task?> GetCategoriesAsync(int? idWell, CancellationToken token); + Task> GetCategoriesAsync(int? idWell, CancellationToken token); /// /// Получить автоматически определенные по телеметрии операции с анализом по бурильщикам @@ -27,7 +27,7 @@ namespace AsbCloudApp.Services /// /// /// - Task GetAsync(DetectedOperationRequest request, CancellationToken token); + Task GetAsync(DetectedOperationByWellRequest request, CancellationToken token); /// /// Получить автоматически определенные по телеметрии операции @@ -35,7 +35,7 @@ namespace AsbCloudApp.Services /// /// /// - Task> GetOperationsAsync(DetectedOperationRequest request, CancellationToken token); + Task> GetOperationsAsync(DetectedOperationByWellRequest request, CancellationToken token); /// /// Удалить операции @@ -43,7 +43,7 @@ namespace AsbCloudApp.Services /// /// /// - Task DeleteAsync(DetectedOperationRequest request, CancellationToken token); + Task DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token); /// /// Статистика по операциям @@ -51,6 +51,6 @@ namespace AsbCloudApp.Services /// /// /// - Task?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token); + Task> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token); } } diff --git a/AsbCloudApp/Services/ITelemetryDataSaubService.cs b/AsbCloudApp/Services/ITelemetryDataSaubService.cs index a0bb271d..e13240c0 100644 --- a/AsbCloudApp/Services/ITelemetryDataSaubService.cs +++ b/AsbCloudApp/Services/ITelemetryDataSaubService.cs @@ -12,6 +12,18 @@ namespace AsbCloudApp.Services /// public interface ITelemetryDataSaubService : ITelemetryDataService { + /// + /// Получение телеметрии для РТК статистики + /// + /// + /// + /// + /// + /// + /// + /// + Task> Get(int idTelemetry, bool isBitOnBottom, DateTimeOffset geDate, DateTimeOffset leDate, int take, CancellationToken token); + /// /// усредненная статистика по 1м за весь период /// diff --git a/AsbCloudDb/EFExtensionsExceptionHandling.cs b/AsbCloudDb/EFExtensionsExceptionHandling.cs new file mode 100644 index 00000000..aa1996b5 --- /dev/null +++ b/AsbCloudDb/EFExtensionsExceptionHandling.cs @@ -0,0 +1,34 @@ +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using Npgsql; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudDb +{ + public static class EFExtensionsExceptionHandling + { + public static async Task SaveChangesWithExceptionHandling(this IAsbCloudDbContext db, CancellationToken token) + { + try + { + var result = await db.SaveChangesAsync(token); + return result; + } + catch (DbUpdateException ex) + { + if (ex.InnerException is PostgresException pgException) + TryConvertPostgresExceptionToValidateException(pgException); + throw; + } + } + + private static void TryConvertPostgresExceptionToValidateException(PostgresException pgException) + { + if (pgException.SqlState == PostgresErrorCodes.ForeignKeyViolation) + // TODO: replace ArgumentException by new Exception + throw new ArgumentException(pgException.Message + "\r\n" + pgException.Detail, "dtos"); + } + } +} diff --git a/AsbCloudDb/EFExtentionOrderBy.cs b/AsbCloudDb/EFExtensionsSortBy.cs similarity index 99% rename from AsbCloudDb/EFExtentionOrderBy.cs rename to AsbCloudDb/EFExtensionsSortBy.cs index 0b46c29b..933281c7 100644 --- a/AsbCloudDb/EFExtentionOrderBy.cs +++ b/AsbCloudDb/EFExtensionsSortBy.cs @@ -7,7 +7,7 @@ using System.Reflection; namespace AsbCloudDb { - public static class EFExtentionsSortBy + public static class EFExtensionsSortBy { struct TypeAcessor { diff --git a/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs b/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs index bc942353..d7daf9ad 100644 --- a/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs +++ b/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs @@ -1,10 +1,11 @@ using AsbCloudApp.Data; +using AsbCloudApp.Data.DetectedOperation; using AsbCloudApp.Data.SAUB; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; +using AsbCloudApp.Services; using AsbCloudDb.Model; -using AsbCloudInfrastructure.Services.DetectOperations.Detectors; -using Microsoft.EntityFrameworkCore; +using AsbCloudInfrastructure.Services.DetectOperations; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; @@ -19,7 +20,6 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks /// internal class WorkDataSaubStat : Work { - private int MechanicalDrillingCategoryId = 4001; private int Gap = 60; public WorkDataSaubStat() : base("Generate DataSaubStat entries and save them into Db") @@ -29,7 +29,6 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) { - using var db = services.GetRequiredService(); var telemetryDataCache = services.GetRequiredService>(); @@ -43,96 +42,102 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks return; var dataSaubStatRepo = services.GetRequiredService(); + var dataSaubService = services.GetRequiredService(); + var detectedOperationRepository = services.GetRequiredService(); var stats = await dataSaubStatRepo.GetLastsAsync(idTelemetries, token); for( var i =0; i < idTelemetries.Length; i++) { var idTelemetry = idTelemetries[i]; - var lastDate = stats.FirstOrDefault(s => s.IdTelemetry == idTelemetry)?.DateEnd ?? DateTimeOffset.UnixEpoch; - var statsCount = await CreateStatForTelemetryFromDate(db, idTelemetry, lastDate, dataSaubStatRepo, token); + var lastDate = stats.FirstOrDefault(s => s.IdTelemetry == idTelemetry)?.DateEnd.ToUniversalTime() ?? DateTimeOffset.UnixEpoch; + var statsCount = await CreateStatForTelemetryFromDate(idTelemetry, lastDate, dataSaubService, dataSaubStatRepo, detectedOperationRepository, token); onProgressCallback($"Calculate stat for telemetry: {idTelemetry}; from {lastDate}; results count: {statsCount};", 100*i / idTelemetries.Length); } } - private async Task CreateStatForTelemetryFromDate(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset begin, IDataSaubStatRepository dataSaubStatRepo, CancellationToken token) + private static async Task CreateStatForTelemetryFromDate( + int idTelemetry, + DateTimeOffset begin, + ITelemetryDataSaubService dataSaubService, + IDataSaubStatRepository dataSaubStatRepo, + IDetectedOperationRepository detectedOperationRepository, + CancellationToken token) { - var detectedOperations = await db.Set() - .Where(o => o.IdTelemetry == idTelemetry) - .Where(o => o.DateStart > begin) - .Where(o => o.OperationCategory.IdParent == MechanicalDrillingCategoryId) - .OrderBy(o => o.DateStart) - .Take(250) - .ToArrayAsync(token); + var detectedOperationRequest = new DetectedOperationByTelemetryRequest { + GeDateStart = begin, + IdTelemetry = idTelemetry, + IdsCategories = WellOperationCategory.MechanicalDrillingSubIds, + SortFields = new[] {nameof(DetectedOperation.DateStart) }, + Take = 250, + }; + + var detectedOperations = await detectedOperationRepository.Get(detectedOperationRequest, token); if (!detectedOperations.Any()) return 0; - var minDate = detectedOperations.First().DateStart; - var maxDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd; + var geDate = detectedOperations.First().DateStart; + var leDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd; - var telemetryDataSaub = await db.Set() - .Where(t => t.IdTelemetry == idTelemetry) - .Where(t => t.DateTime >= minDate) - .Where(t => t.DateTime <= maxDate) - .Where(t => Math.Abs(t.BitDepth - t.WellDepth) < 0.0001) - .OrderBy(t => t.DateTime) - .Take(100_000) - .ToArrayAsync(token); + var dataSaub = await dataSaubService.Get(idTelemetry, true, geDate, leDate, 100_000, token); - if (!telemetryDataSaub.Any()) + if (!dataSaub.Any()) return 0; - var dataSaubStats = CreateDataSaubStat(detectedOperations, telemetryDataSaub); + if(dataSaub is not TelemetryDataSaubDto[] dataSaubArray) + dataSaubArray = dataSaub.ToArray(); + + var dataSaubStats = CreateDataSaubStat(detectedOperations, dataSaubArray); return await dataSaubStatRepo.InsertRangeAsync(dataSaubStats, token); } - private static IEnumerable CreateDataSaubStat(IEnumerable detectedOperations, TelemetryDataSaub[] telemetryDataSaub) + private static IEnumerable CreateDataSaubStat(IEnumerable detectedOperations, TelemetryDataSaubDto[] dataSaub) { var indexStart = 0; var indexEnd = 0; var result = new List(); - if (!telemetryDataSaub.Any()) + if (!dataSaub.Any()) return result; foreach (var operation in detectedOperations) { - indexStart = Array.FindIndex(telemetryDataSaub, indexEnd, t => t.DateTime >= operation.DateStart); + indexStart = Array.FindIndex(dataSaub, indexEnd, t => t.DateTime >= operation.DateStart); if (indexStart < 0) break; - indexEnd = Array.FindIndex(telemetryDataSaub, indexStart, t => t.DateTime > operation.DateEnd); + indexEnd = Array.FindIndex(dataSaub, indexStart, t => t.DateTime > operation.DateEnd); if (indexEnd < 0) - indexEnd = telemetryDataSaub.Length - 1; + indexEnd = dataSaub.Length - 1; if (indexEnd == indexStart) continue; var length = indexEnd - indexStart; - var subset = telemetryDataSaub.AsSpan(indexStart, length); + var subset = dataSaub.AsSpan(indexStart, length); var stats = CalcStats(operation, subset); result.AddRange(stats); } return result; } - private static IEnumerable CalcStats(DetectedOperation operation, Span telemetryDataSaub) + private static IEnumerable CalcStats(DetectedOperationDto operation, Span dataSaub) { var result = new List(); var indexStart = 0; - for (var i = 1; i < telemetryDataSaub.Length; i++) + for (var i = 1; i < dataSaub.Length; i++) { - var previous = telemetryDataSaub[i - 1]; - var current = telemetryDataSaub[i]; + var previous = dataSaub[i - 1]; + var current = dataSaub[i]; - if (IsNewCacheItem(previous, current) || i == telemetryDataSaub.Length - 1) + if (IsNewCacheItem(previous, current) || i == dataSaub.Length - 1) { var length = i - indexStart; - var span = telemetryDataSaub.Slice(indexStart, length); + var span = dataSaub.Slice(indexStart, length); indexStart = i; if (length <= 2 || (span[^1].WellDepth - span[0].WellDepth) < 0.001) continue; // мелкие выборки не учитываем. @@ -144,10 +149,9 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks return result; } - private static DataSaubStatDto CalcStat(DetectedOperation operation, Span span) + private static DataSaubStatDto CalcStat(DetectedOperationDto operation, Span span) { - var hasOscillation = operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyHasOscillation, out object? hasOscillationObject) - && hasOscillationObject is true; + var hasOscillation = EnabledSubsystemsFlags.AutoOscillation.HasEnabledSubsystems(operation.EnabledSubsystems); var aggregatedValues = CalcAggregate(span); var processMapDrillingCacheItem = new DataSaubStatDto @@ -184,7 +188,7 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks double RotorTorque, double RotorSpeed, double Flow - ) CalcAggregate(Span span) + ) CalcAggregate(Span span) { var sumPressure = 0.0; var sumAxialLoad = 0.0; @@ -210,7 +214,7 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks ); } - private static bool IsNewCacheItem(TelemetryDataSaub previous, TelemetryDataSaub current) + private static bool IsNewCacheItem(TelemetryDataSaubDto previous, TelemetryDataSaubDto current) { return !(current.Mode == previous.Mode) || !(current.WellDepth >= previous.WellDepth) diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 363ec8ee..92d305c5 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -327,6 +327,8 @@ namespace AsbCloudInfrastructure services.AddTransient, ProcessMapPlanService>(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddSingleton(); diff --git a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs index 64208a8e..f9265ba8 100644 --- a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs +++ b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs @@ -18,11 +18,11 @@ public abstract class ChangeLogRepositoryAbstract : ICh where TEntity : ChangeLogAbstract where TRequest : ChangeLogBaseRequest { - protected readonly IAsbCloudDbContext context; + protected readonly IAsbCloudDbContext db; - public ChangeLogRepositoryAbstract(IAsbCloudDbContext context) + public ChangeLogRepositoryAbstract(IAsbCloudDbContext db) { - this.context = context; + this.db = db; } public async Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token) @@ -30,9 +30,10 @@ public abstract class ChangeLogRepositoryAbstract : ICh var result = 0; if (dtos.Any()) { + using var transaction = await db.Database.BeginTransactionAsync(token); var entities = dtos.Select(Convert); var creation = DateTimeOffset.UtcNow; - var dbSet = context.Set(); + var dbSet = db.Set(); foreach (var entity in entities) { entity.Id = default; @@ -46,6 +47,7 @@ public abstract class ChangeLogRepositoryAbstract : ICh } result += await SaveChangesWithExceptionHandling(token); + await transaction.CommitAsync(token); } return result; } @@ -62,9 +64,8 @@ public abstract class ChangeLogRepositoryAbstract : ICh var ids = dtos.Select(d => d.Id); - using var transaction = context.Database.BeginTransaction(); var result = 0; - var dbSet = context + var dbSet = db .Set(); var entitiesToDelete = await dbSet @@ -80,13 +81,14 @@ public abstract class ChangeLogRepositoryAbstract : ICh throw new ArgumentInvalidException(nameof(dtos), $"записи с id:[{stringnotFoundIds}] не найдены, или не актуальны."); } + using var transaction = db.Database.BeginTransaction(); foreach (var entity in entitiesToDelete) { entity.IdState = ChangeLogAbstract.IdStateReplaced; entity.Obsolete = updateTime; entity.IdEditor = idUser; } - result += await context.SaveChangesAsync(token); + result += await db.SaveChangesAsync(token); var entitiesNew = dtos.Select(Convert); foreach (var entity in entitiesNew) @@ -143,7 +145,7 @@ public abstract class ChangeLogRepositoryAbstract : ICh public async Task ClearAndInsertRange(int idUser, TRequest request, IEnumerable dtos, CancellationToken token) { var result = 0; - var transaction = await context.Database.BeginTransactionAsync(token); + using var transaction = await db.Database.BeginTransactionAsync(token); result += await Clear(idUser, request, token); result += await InsertRange(idUser, dtos, token); await transaction.CommitAsync(token); @@ -153,7 +155,7 @@ public abstract class ChangeLogRepositoryAbstract : ICh public async Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token) { var updateTime = DateTimeOffset.UtcNow; - var query = context.Set() + var query = db.Set() .Where(e => ids.Contains(e.Id)) .Where(e => e.Obsolete == null); @@ -269,7 +271,7 @@ public abstract class ChangeLogRepositoryAbstract : ICh { try { - var result = await context.SaveChangesAsync(token); + var result = await db.SaveChangesAsync(token); return result; } catch (DbUpdateException ex) diff --git a/AsbCloudInfrastructure/Repository/DetectedOperationRepository.cs b/AsbCloudInfrastructure/Repository/DetectedOperationRepository.cs new file mode 100644 index 00000000..50ec6c9e --- /dev/null +++ b/AsbCloudInfrastructure/Repository/DetectedOperationRepository.cs @@ -0,0 +1,191 @@ +using AsbCloudApp.Data.DetectedOperation; +using AsbCloudApp.Exceptions; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudDb; +using AsbCloudDb.Model; +using Mapster; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Repository; + +public class DetectedOperationRepository : IDetectedOperationRepository +{ + private readonly IAsbCloudDbContext db; + private readonly ITelemetryService telemetryService; + + public DetectedOperationRepository( + IAsbCloudDbContext db, + ITelemetryService telemetryService) + { + this.db = db; + this.telemetryService = telemetryService; + } + + public async Task Delete(int idUser, DetectedOperationByTelemetryRequest request, CancellationToken token) + { + var query = BuildQuery(request); + db.Set().RemoveRange(query); + return await db.SaveChangesAsync(token); + } + + public async Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token) + { + var query = db.Set() + .Where(e => ids.Contains( e.Id)); + + db.Set() + .RemoveRange(query); + + return await db.SaveChangesAsync(token); + } + + public async Task> Get(DetectedOperationByTelemetryRequest request, CancellationToken token) + { + var query = BuildQuery(request) + .Include(o => o.OperationCategory); + var entities = await query.ToArrayAsync(token); + var offset = telemetryService.GetTimezone(request.IdTelemetry).Offset; + var dtos = entities.Select(o => Convert(o, offset)); + + return dtos; + } + + public async Task Insert(int? idUser, IEnumerable dtos, CancellationToken token) + { + if(!dtos.Any()) + return 0; + + var entities = dtos.Select(Convert); + var dbset = db.Set(); + foreach(var entity in entities) + { + entity.Id = default; + dbset.Add(entity); + } + + return await db.SaveChangesWithExceptionHandling(token); + } + + public async Task Update(int idUser, IEnumerable dtos, CancellationToken token) + { + if (!dtos.Any()) + return 0; + + var ids = dtos + .Select(o => o.Id) + .Distinct() + .ToArray(); + + if (ids.Any(id => id == default)) + throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь Id"); + + if (ids.Length != dtos.Count()) + throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь уникальные Id"); + + var dbSet = db.Set(); + + var existingEntitiesCount = await dbSet + .Where(o => ids.Contains(o.Id)) + .CountAsync(token); + + if (ids.Length != existingEntitiesCount) + throw new ArgumentInvalidException(nameof(dtos), "Все записи должны существовать в БД"); + + var entities = dtos + .Select(Convert) + .ToArray(); + + var entries = new Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry[entities.Length]; + for(var i = 0; i < entities.Length; i++) + entries[i] = dbSet.Update(entities[i]); + + var result = await db.SaveChangesWithExceptionHandling(token); + + for (var i = 0; i < entries.Length; i++) + entries[i].State = EntityState.Detached; + + return result; + } + + public async Task UpdateOrInsert(int idUser, IEnumerable dtos, CancellationToken token) + { + var result = 0; + + var itemsToInsert = dtos.Where(e => e.Id == 0); + if (itemsToInsert.Any()) + result += await Insert(idUser, itemsToInsert, token); + + var itemsToUpdate = dtos.Where(e => e.Id != 0); + if (itemsToUpdate.Any()) + result += await Update(idUser, itemsToUpdate, token); + + return result; + } + + private IQueryable BuildQuery(DetectedOperationByTelemetryRequest request) + { + var query = db.Set() + .Where(o => o.IdTelemetry == request.IdTelemetry); + + if (request.IdsCategories.Any()) + query = query.Where(o => request.IdsCategories.Contains(o.IdCategory)); + + if (request.GeDepthStart is not null) + query = query.Where(o => o.DepthStart >= request.GeDepthStart); + + if (request.LeDepthEnd is not null) + query = query.Where(o => o.DepthEnd <= request.LeDepthEnd); + + if (request.GeDateStart is not null) + { + var geDate = request.GeDateStart.Value.ToUniversalTime(); + query = query.Where(o => o.DateStart >= geDate); + } + + if (request.LeDateEnd is not null) + { + var leDate = request.LeDateEnd.Value.ToUniversalTime(); + query = query.Where(o => o.DateEnd <= leDate); + } + + if (request.SortFields?.Any() == true) + { + query = query.SortBy(request.SortFields); + } + else + query = query + .OrderBy(o => o.DateStart) + .ThenBy(o => o.DepthStart); + + if (request.Skip.HasValue) + query = query.Skip((int)request.Skip); + + if (request.Take.HasValue) + query = query.Take((int)request.Take); + + return query; + } + + private static DetectedOperationDto Convert(DetectedOperation entity, TimeSpan offset) + { + var dto = entity.Adapt(); + dto.DateStart = entity.DateStart.ToOffset(offset); + dto.DateEnd = entity.DateEnd.ToOffset(offset); + return dto; + } + + private static DetectedOperation Convert(DetectedOperationDto dto) + { + var entity = dto.Adapt(); + entity.DateStart = dto.DateStart.ToUniversalTime(); + entity.DateEnd = dto.DateEnd.ToUniversalTime(); + return entity; + } +} diff --git a/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs b/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs index 0133b527..7da062ac 100644 --- a/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs +++ b/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs @@ -23,7 +23,7 @@ public class ProcessMapPlanBaseRepository : ChangeLogRepositoryAb protected override IQueryable BuildQuery(ProcessMapPlanBaseRequestWithWell request) { - var query = context + var query = db .Set() .Include(e => e.Author) .Include(e => e.Editor) diff --git a/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs new file mode 100644 index 00000000..84da5f3d --- /dev/null +++ b/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs @@ -0,0 +1,44 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudDb.Model; +using Mapster; +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudInfrastructure.Repository; + +public class WellOperationCategoryRepository : IWellOperationCategoryRepository +{ + private readonly IAsbCloudDbContext db; + private readonly IMemoryCache memoryCache; + + public WellOperationCategoryRepository(IAsbCloudDbContext db, IMemoryCache memoryCache) + { + this.db = db; + this.memoryCache = memoryCache; + } + + public IEnumerable Get(bool includeParents) + { + var categories = memoryCache + .GetOrCreateBasic(db.Set()); + + if (!includeParents) + { + var parentIds = categories + .Select(o => o.IdParent) + .Distinct(); + + categories = categories + .Where(o => !parentIds.Contains(o.Id)); + } + + var result = categories + .OrderBy(o => o.Name) + .Adapt>(); + + return result; + } +} diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index c9d3c7fc..1330a282 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -28,35 +28,20 @@ public class WellOperationRepository : IWellOperationRepository private readonly IAsbCloudDbContext db; private readonly IMemoryCache memoryCache; private readonly IWellService wellService; + private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; - public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService) + public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService, IWellOperationCategoryRepository wellOperationCategoryRepository) { this.db = db; this.memoryCache = memoryCache; this.wellService = wellService; + this.wellOperationCategoryRepository = wellOperationCategoryRepository; } /// public IEnumerable GetCategories(bool includeParents) { - var categories = memoryCache - .GetOrCreateBasic(db.Set()); - - if (!includeParents) - { - var parentIds = categories - .Select(o => o.IdParent) - .Distinct(); - - categories = categories - .Where(o => !parentIds.Contains(o.Id)); - } - - var result = categories - .OrderBy(o => o.Name) - .Adapt>(); - - return result; + return wellOperationCategoryRepository.Get(includeParents); } /// diff --git a/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs b/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs index e4271c8f..87108fd2 100644 --- a/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs +++ b/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs @@ -282,7 +282,7 @@ public class DailyReportService : IDailyReportService var leDateEnd = dailyReport.Date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); dailyReport.TimeBalanceBlock.WellOperationSlipsTimeCount = (await detectedOperationService.GetAsync( - new DetectedOperationRequest + new DetectedOperationByWellRequest { IdsCategories = new[] { idWellOperationSlipsTime }, IdWell = dailyReport.IdWell, diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs index ef781a1e..987f274f 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs @@ -1,6 +1,5 @@ using AsbCloudDb.Model; using ClosedXML.Excel; -using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.IO; @@ -9,22 +8,21 @@ 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; +using AsbCloudApp.Services; +using AsbCloudApp.Data; +using AsbCloudInfrastructure.Services.DetectOperations.Detectors; namespace AsbCloudInfrastructure.Services.DetectOperations; public class DetectedOperationExportService { - private readonly DetectorAbstract[] detectors = - { - new DetectorDrilling(), - new DetectorSlipsTime() - }; - - private const int headerRowsCount = 1; + private readonly IAsbCloudDbContext db; + private readonly IWellService wellService; + private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; + private const int headerRowsCount = 1; private const string cellDepositName = "B1"; private const string cellClusterName = "B2"; @@ -42,14 +40,14 @@ public class DetectedOperationExportService 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) + public DetectedOperationExportService( + IAsbCloudDbContext db, + IWellService wellService, + IWellOperationCategoryRepository wellOperationCategoryRepository) { - this.dbContext = dbContext; - this.wellOperationRepository = wellOperationRepository; + this.db = db; + this.wellService = wellService; + this.wellOperationCategoryRepository = wellOperationCategoryRepository; } /// @@ -57,15 +55,12 @@ public class DetectedOperationExportService /// /// ключ скважины /// хост - /// + /// /// /// - public async Task ExportAsync(int idWell, string host, CancellationToken cancellationToken) + public async Task ExportAsync(int idWell, string host, CancellationToken token) { - var well = await dbContext.Set() - .Include(w => w.Cluster) - .ThenInclude(c => c.Deposit) - .FirstOrDefaultAsync(w => w.Id == idWell, cancellationToken); + var well = await wellService.GetOrDefaultAsync(idWell, token); if (well is null) throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} does not exist"); @@ -73,19 +68,19 @@ public class DetectedOperationExportService if (!well.IdTelemetry.HasValue) throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} has no telemetry"); - var operations = await DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, cancellationToken); + var operations = await WorkOperationDetection.DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, db, token); - return await GenerateExcelFileStreamAsync(well, host, operations, cancellationToken); + return await GenerateExcelFileStreamAsync(well, host, operations, token); } - private async Task GenerateExcelFileStreamAsync(Well well, string host, IEnumerable operationDetectorResults, + private async Task GenerateExcelFileStreamAsync(WellDto 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); + AddToWorkbook(workbook, well, host, operationDetectorResults); MemoryStream memoryStream = new MemoryStream(); workbook.SaveAs(memoryStream, new SaveOptions { }); @@ -93,36 +88,34 @@ public class DetectedOperationExportService return memoryStream; } - private async Task AddToWorkbookAsync(XLWorkbook workbook, Well well, string host, IEnumerable operationDetectorResults, - CancellationToken cancellationToken) + private void AddToWorkbook(XLWorkbook workbook, WellDto well, string host, IEnumerable operations) { const string sheetName = "Операции"; - if (!operationDetectorResults.Any()) + if (!operations.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); + var orderedOperations = operations + .OrderBy(x => x.DateStart) + .ThenBy(x => x.DepthStart).ToArray(); + + AddToSheet(sheet, well, host, orderedOperations); } - private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, string host, IList operationDetectorResults, - CancellationToken cancellationToken) + private void AddToSheet(IXLWorksheet sheet, WellDto well, string host, IList operations) { - var wellOperationCategories = await dbContext.WellOperationCategories.ToListAsync(cancellationToken); + var wellOperationCategories = wellOperationCategoryRepository.Get(true); - sheet.Cell(cellDepositName).SetCellValue(well.Cluster.Deposit.Caption); - sheet.Cell(cellClusterName).SetCellValue(well.Cluster.Caption); + sheet.Cell(cellDepositName).SetCellValue(well.Deposit); + sheet.Cell(cellClusterName).SetCellValue(well.Cluster); sheet.Cell(cellWellName).SetCellValue(well.Caption); - sheet.Cell(cellDeltaDate).SetCellValue(operationDetectorResults.Max(o => o.Operation.DateEnd) - operationDetectorResults.Min(o => o.Operation.DateStart)); + sheet.Cell(cellDeltaDate).SetCellValue((TimeSpan)(Enumerable.Max(operations, (Func)(o => o.DateEnd)) - Enumerable.Min(operations, (Func)(o => o.DateStart)))); - var detectedOperations = operationDetectorResults.Select(o => o.Operation).ToArray(); - - for (int i = 0; i < operationDetectorResults.Count; i++) + for (int i = 0; i < operations.Count; i++) { - var current = detectedOperations[i]; + var current = operations[i]; var dateStart = current.DateStart.ToRemoteDateTime(well.Timezone.Hours); var dateEnd = current.DateEnd.ToRemoteDateTime(well.Timezone.Hours); @@ -153,17 +146,18 @@ public class DetectedOperationExportService 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 + var deltaDepth = i > 0 && i + 1 < operations.Count + ? current.DepthStart - operations[i - 1].DepthEnd : 0; + row.Cell(columnDeltaDepth).SetCellValue(deltaDepth); - var comment = CreateComment(operationDetectorResults[i]); + var comment = CreateComment(operations[i]); row.Cell(columnComment).SetCellValue(comment); } } - private static string GetCategoryName(IEnumerable wellOperationCategories, DetectedOperation current) + private static string GetCategoryName(IEnumerable wellOperationCategories, DetectedOperation current) { var idCategory = current.IdCategory; if (idCategory == WellOperationCategory.IdSlide && @@ -204,9 +198,8 @@ public class DetectedOperationExportService return memoryStream; } - private static string CreateComment(OperationDetectorResult operationDetectorResult) + private static string CreateComment(DetectedOperation operation) { - var operation = operationDetectorResult.Operation; switch (operation.IdCategory) { case WellOperationCategory.IdRotor: @@ -224,69 +217,4 @@ public class DetectedOperationExportService 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; - } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs index 23a5d20b..bd8fba77 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Data.DetectedOperation; +using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb; @@ -11,274 +12,200 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Services.DetectOperations +namespace AsbCloudInfrastructure.Services.DetectOperations; + +public class DetectedOperationService : IDetectedOperationService { + private readonly IDetectedOperationRepository operationRepository; + private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; + private readonly IWellService wellService; + private readonly IRepositoryWellRelated operationValueService; + private readonly IScheduleRepository scheduleService; - public class DetectedOperationService : IDetectedOperationService + public DetectedOperationService( + IDetectedOperationRepository operationRepository, + IWellOperationCategoryRepository wellOperationCategoryRepository, + IWellService wellService, + IRepositoryWellRelated operationValueRepository, + IScheduleRepository scheduleRepository) { - private readonly IAsbCloudDbContext db; - private readonly IWellService wellService; - private readonly IRepositoryWellRelated operationValueService; - private readonly IScheduleRepository scheduleService; + this.operationRepository = operationRepository; + this.wellOperationCategoryRepository = wellOperationCategoryRepository; + this.wellService = wellService; + this.operationValueService = operationValueRepository; + this.scheduleService = scheduleRepository; + } - public DetectedOperationService(IAsbCloudDbContext db, IWellService wellService, - IRepositoryWellRelated operationValueService, IScheduleRepository scheduleService) + public async Task GetAsync(DetectedOperationByWellRequest request, CancellationToken token) + { + var dtos = await GetOperationsAsync(request, token); + if (dtos?.Any() != true) + return new DetectedOperationListDto(); + + var stats = GetOperationsDrillersStat(dtos); + var result = new DetectedOperationListDto { - this.db = db; - this.wellService = wellService; - this.operationValueService = operationValueService; - this.scheduleService = scheduleService; + Operations = dtos, + Stats = stats + }; + return result; + } + + public async Task> GetOperationsAsync(DetectedOperationByWellRequest request, CancellationToken token) + { + var well = await wellService.GetOrDefaultAsync(request.IdWell, token); + if (well?.IdTelemetry is null) + return Enumerable.Empty(); + + var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request); + var data = await operationRepository.Get(requestByTelemetry, token); + + var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token); + var schedules = await scheduleService.GetByIdWellAsync(request.IdWell, token); + var dtos = data.Select(o => Convert(o, operationValues, schedules)); + return dtos; + } + + public async Task> GetCategoriesAsync(int? idWell, CancellationToken token) + { + if(idWell is null) + { + return wellOperationCategoryRepository.Get(false); } - - public async Task GetAsync(DetectedOperationRequest request, CancellationToken token) + else { - 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; - } - - public async Task> GetOperationsAsync(DetectedOperationRequest request, CancellationToken token) - { - var well = await wellService.GetOrDefaultAsync(request.IdWell, token); + var well = await wellService.GetOrDefaultAsync((int )idWell, token); if (well?.IdTelemetry is null) - return Enumerable.Empty(); + return Enumerable.Empty(); - var query = BuildQuery(well, request).AsNoTracking(); - - 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)); - return dtos; - } - - 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 DetectedOperationDrillersStatDto - { - Driller = group.Key, - AverageValue = group.Sum(e => e.Value) / group.Count(), - Count = group.Count(), - }; - if (itemsWithTarget.Any()) - { - var itemsOutOfTarget = itemsWithTarget.Where(o => !IsTargetOk(o)); - 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; - } - - 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) - { - var well = await wellService.GetOrDefaultAsync(request.IdWell, token); - if (well?.IdTelemetry is null || well.Timezone is null) - return 0; - - var query = BuildQueryBase(well, request); - - if (query is null) - return 0; - - db.DetectedOperations.RemoveRange(query); - return await db.SaveChangesAsync(token); - } - - private static bool IsTargetOk(DetectedOperationDto op) - { - return (op.IdCategory) switch - { - WellOperationCategory.IdRotor => op.Value > op.OperationValue?.TargetValue, - WellOperationCategory.IdSlide => op.Value > op.OperationValue?.TargetValue, - WellOperationCategory.IdSlipsTime => op.Value > op.OperationValue?.TargetValue, - _ => op.Value > op.OperationValue?.TargetValue, + var request = new DetectedOperationByTelemetryRequest() + { + IdTelemetry = well.IdTelemetry.Value }; - } - private static double DeltaToTarget(DetectedOperationDto op) - { - return (op.IdCategory) switch - { - WellOperationCategory.IdRotor => 0, - WellOperationCategory.IdSlide => 0, - WellOperationCategory.IdSlipsTime => op.Value - op.OperationValue?.TargetValue??0, - _ => 0, - }; - } - - private IQueryable BuildQueryBase(WellDto well, DetectedOperationRequest request) - { - var query = db.Set() - .Where(o => o.IdTelemetry == well.IdTelemetry); - - if (request.IdsTelemetries.Any()) - { - query = query - .Union(db.Set().Where(o => request.IdsTelemetries.Contains(o.IdTelemetry))); - } - - if (request.IdsCategories.Any()) - query = query.Where(o => request.IdsCategories.Contains(o.IdCategory)); - - if (request.GeDateStart is not null) - query = query.Where(o => o.DateStart >= request.GeDateStart.Value.Date.ToUtcDateTimeOffset(well.Timezone.Hours)); - - if (request.LeDateEnd is not null) - query = query.Where(o => o.DateEnd <= request.LeDateEnd.Value.Date.ToUtcDateTimeOffset(well.Timezone.Hours)); - - if (request.GeDepth is not null) - query = query.Where(o => o.DepthStart >= request.GeDepth); - - if (request.LeDepth is not null) - query = query.Where(o => o.DepthEnd <= request.LeDepth); - - if (request.IdTelemetryUser is not null) - query = query.Where(o => o.IdUsersAtStart == request.IdTelemetryUser); - - return query; - } - - private IQueryable BuildQuery(WellDto well, DetectedOperationRequest request) - { - IQueryable query = BuildQueryBase(well, request) - .Include(o => o.OperationCategory); - - if (request.SortFields?.Any() == true) - { - query = query.SortBy(request.SortFields); - } - else - query = query - .OrderBy(o => o.DateStart) - .ThenBy(o => o.DepthStart); - - if (request.Skip.HasValue) - query = query.Skip((int)request.Skip); - - if (request.Take.HasValue) - query = query.Take((int)request.Take); - - return query; - } - - private static DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable operationValues, IEnumerable schedules) - { - var dto = operation.Adapt(); - dto.IdWell = well.Id; - var dateStart = operation.DateStart.ToRemoteDateTime(well.Timezone.Hours); - dto.DateStart = dateStart; - dto.DateEnd = operation.DateEnd.ToRemoteDateTime(well.Timezone.Hours); - dto.OperationValue = operationValues.FirstOrDefault(v => v.IdOperationCategory == dto.IdCategory - && v.DepthStart <= dto.DepthStart - && v.DepthEnd > dto.DepthStart); - - var timeStart = new TimeDto(dateStart); - var driller = schedules.FirstOrDefault(s => - s.DrillStart <= dateStart && - s.DrillEnd > dateStart && ( - s.ShiftStart > s.ShiftEnd - ) ^ (s.ShiftStart <= timeStart && - s.ShiftEnd > timeStart - )) - ?.Driller; - dto.Driller = driller; - return dto; - } - - public async Task?> GetCategoriesAsync(int? idWell, CancellationToken token) - { - IQueryable query; - if(idWell is null) - { - query = db.WellOperationCategories; - } - else - { - var well = await wellService.GetOrDefaultAsync((int )idWell, token); - if (well?.IdTelemetry is null) - return null; - var idTelemetry = (int)well.IdTelemetry; - query = db.DetectedOperations - .Include(o => o.OperationCategory) - .Where(o => o.IdTelemetry == idTelemetry) - .Select(o => o.OperationCategory) - .Distinct(); - } - - var result = await query - .Where(c => c.Id < 1000) - .AsNoTracking() - .Select(c => c.Adapt()) - .ToArrayAsync(token); - return result; + var operations = await operationRepository.Get(request, token); + var categories = operations + .Select(o => o.OperationCategory) + .Distinct(); + return categories; } } + public async Task> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token) + { + var well = await wellService.GetOrDefaultAsync(request.IdWell, token); + if (well?.IdTelemetry is null || well.Timezone is null) + return Enumerable.Empty(); + + var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request); + + var operations = await operationRepository.Get(requestByTelemetry, token); + + if (!operations.Any()) + return Enumerable.Empty(); + + var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token); + + var dtos = operations + .GroupBy(o => (o.IdCategory, o.OperationCategory.Name)) + .OrderBy(g => g.Key) + .Select(g => new DetectedOperationStatDto + { + IdCategory = g.Key.IdCategory, + Category = g.Key.Name, + 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(DetectedOperationByWellRequest request, CancellationToken token) + { + var well = await wellService.GetOrDefaultAsync(request.IdWell, token); + if (well?.IdTelemetry is null || well.Timezone is null) + return 0; + + var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request); + var result = await operationRepository.Delete(-1, requestByTelemetry, token); + return result; + } + + 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 DetectedOperationDrillersStatDto + { + Driller = group.Key, + AverageValue = group.Sum(e => e.Value) / group.Count(), + Count = group.Count(), + }; + if (itemsWithTarget.Any()) + { + var itemsOutOfTarget = itemsWithTarget.Where(o => !IsTargetOk(o)); + 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; + } + + private static bool IsTargetOk(DetectedOperationWithDrillerDto op) + { + return (op.IdCategory) switch + { + WellOperationCategory.IdRotor => op.Value > op.OperationValue?.TargetValue, + WellOperationCategory.IdSlide => op.Value > op.OperationValue?.TargetValue, + WellOperationCategory.IdSlipsTime => op.Value > op.OperationValue?.TargetValue, + _ => op.Value > op.OperationValue?.TargetValue, + }; + } + + private static double DeltaToTarget(DetectedOperationWithDrillerDto op) + { + return (op.IdCategory) switch + { + WellOperationCategory.IdRotor => 0, + WellOperationCategory.IdSlide => 0, + WellOperationCategory.IdSlipsTime => op.Value - op.OperationValue?.TargetValue??0, + _ => 0, + }; + } + + + private static DetectedOperationWithDrillerDto Convert(DetectedOperationDto operation, IEnumerable operationValues, IEnumerable schedules) + { + var dto = operation.Adapt(); + dto.OperationValue = operationValues.FirstOrDefault(v => v.IdOperationCategory == dto.IdCategory + && v.DepthStart <= dto.DepthStart + && v.DepthEnd > dto.DepthStart); + + var dateStart = dto.DateStart; + var timeStart = new TimeDto(dateStart); + var driller = schedules.FirstOrDefault(s => + s.DrillStart <= dateStart && + s.DrillEnd > dateStart && ( + s.ShiftStart > s.ShiftEnd + ) ^ (s.ShiftStart <= timeStart && + s.ShiftEnd > timeStart + )) + ?.Driller; + dto.Driller = driller; + + return dto; + } } diff --git a/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs index 2dc09b02..aece226e 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs @@ -86,7 +86,8 @@ public class WorkOperationDetection: Work } } - private static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + //todo: move this logic to DetectedOperationsService + internal static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) { var query = db.TelemetryDataSaub .AsNoTracking() diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs index d918f384..fdced02c 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs @@ -1,5 +1,4 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Data.SAUB; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Services; @@ -11,142 +10,163 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; -using System.Text; using System.Text.Csv; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Services.SAUB +namespace AsbCloudInfrastructure.Services.SAUB; + +public class TelemetryDataSaubService : TelemetryDataBaseService, ITelemetryDataSaubService { + private readonly ITelemetryUserService telemetryUserService; - public class TelemetryDataSaubService : TelemetryDataBaseService, ITelemetryDataSaubService + public TelemetryDataSaubService( + IAsbCloudDbContext db, + ITelemetryService telemetryService, + ITelemetryUserService telemetryUserService, + ITelemetryDataCache telemetryDataCache) + : base(db, telemetryService, telemetryDataCache) { - private readonly ITelemetryUserService telemetryUserService; - - public TelemetryDataSaubService( - IAsbCloudDbContext db, - ITelemetryService telemetryService, - ITelemetryUserService telemetryUserService, - ITelemetryDataCache telemetryDataCache) - : base(db, telemetryService, telemetryDataCache) - { - this.telemetryUserService = telemetryUserService; - } - - public async Task> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token) - { - var timezone = telemetryService.GetTimezone(idTelemetry); - var timezoneOffset = TimeSpan.FromHours(timezone.Hours); - int[] modes = new int[] { 0, 1, 3 }; - - db.Database.SetCommandTimeout(TimeSpan.FromMinutes(1.5)); - - var query = db.Set() - .Where(t => t.IdTelemetry == idTelemetry) - .Where(t => t.BlockPosition > 0.0001) - .Where(t => t.WellDepth > 0.0001) - .Where(t => modes.Contains(t.Mode)) - .Where(t => t.WellDepth - t.BitDepth < 0.01) - .GroupBy(t => new { - t.DateTime.Hour, - WellDepthX10 = Math.Truncate(t.WellDepth * 10), - t.Mode, - t.IdFeedRegulator}) - .Select(g => new TelemetryDataSaubStatDto - { - Count = g.Count(), - IdMode = g.Key.Mode, - IdFeedRegulator = g.Key.IdFeedRegulator, - - DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified), - DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified), - - WellDepthMin = g.Min(t => t.WellDepth), - WellDepthMax = g.Max(t => t.WellDepth), - - Pressure = g.Average(t => t.Pressure), - PressureSp = g.Average(t => t.PressureSp!.Value), - PressureIdle = g.Average(t => t.PressureIdle!.Value), - PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value), - PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value), - PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value), - - AxialLoad = g.Average(t => t.AxialLoad), - AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value), - AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value), - - RotorTorque = g.Average(t => t.RotorTorque), - RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value), - RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value), - - BlockSpeed = g.Average(t => t.BlockSpeed!.Value), - BlockSpeedSp = g.Average(t => t.BlockSpeedSp!.Value), - }) - .Where(s => s.WellDepthMin != s.WellDepthMax) - .Where(s => s.Count > 3) - .OrderBy(t => t.DateMin); - - return await query.ToArrayAsync(token); - } - - public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset) - { - var entity = src.Adapt(); - var telemetryUser = telemetryUserService - .GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User)) - .FirstOrDefault(); - entity.IdUser = telemetryUser?.Id; - entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset); - return entity; - } - - public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset) - { - var dto = src.Adapt(); - var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser??int.MinValue); - dto.User = telemetryUser?.MakeDisplayName(); - dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset); - dto.BitDepth = src.BitDepth <= src.WellDepth - ? src.BitDepth - : src.WellDepth; - return dto; - } - - public async Task GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token) - { - double intervalSec = (endDate - beginDate).TotalSeconds; - if (intervalSec > 60*60*24*3) - throw new ArgumentInvalidException(nameof(endDate), "Слишком большой диапазон"); - - var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell) - ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина id:{idWell} не содержит телеметрии"); - - var approxPointsCount = intervalSec switch - { - < 2048 => 2048, - < 8_192 => 4_096, - < 131_072 => 16_384, - _ => 32_768 - }; - - var data = await GetAsync(idWell, beginDate, intervalSec, approxPointsCount, token ); - - var fileName = $"DataSaub idWell{idWell}"; - if (telemetry.Info is not null) - fileName += $" {telemetry.Info?.Cluster}, {telemetry.Info?.Well}"; - fileName += $" {beginDate:yyyy-MM-DDTHH-mm} - {endDate:yyyy-MM-DDTHH-mm}.csv"; - - var outStream = new MemoryStream(); - using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true)) - { - var entryFile = archive.CreateEntry(fileName, CompressionLevel.Optimal); - using var entryStream = entryFile.Open(); - var serializer = new CsvSerializer(); - serializer.Serialize(data, entryStream); - } - outStream.Seek(0, SeekOrigin.Begin); - return outStream; - } + this.telemetryUserService = telemetryUserService; } + public async Task> Get(int idTelemetry, bool isBitOnBottom, DateTimeOffset geDate, DateTimeOffset leDate, int take, CancellationToken token) + { + var offset = telemetryService.GetTimezone(idTelemetry).Offset; + var geDateUtc = geDate.ToUniversalTime(); + var leDateUtc = leDate.ToUniversalTime(); + + var query = db.Set() + .Where(t => t.IdTelemetry == idTelemetry) + .Where(t => t.DateTime >= geDateUtc) + .Where(t => t.DateTime <= leDateUtc); + + if (isBitOnBottom) + query = query.Where(t => Math.Abs(t.BitDepth - t.WellDepth) < 0.0001); + + query = query + .OrderBy(t => t.DateTime) + .Take(take); + + var entities = await query.ToArrayAsync(token); + var dtos = entities.Select(e => Convert(e, offset.TotalHours)); + return dtos; + } + + public async Task> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token) + { + var timezone = telemetryService.GetTimezone(idTelemetry); + var timezoneOffset = TimeSpan.FromHours(timezone.Hours); + int[] modes = new int[] { 0, 1, 3 }; + + db.Database.SetCommandTimeout(TimeSpan.FromMinutes(1.5)); + + var query = db.Set() + .Where(t => t.IdTelemetry == idTelemetry) + .Where(t => t.BlockPosition > 0.0001) + .Where(t => t.WellDepth > 0.0001) + .Where(t => modes.Contains(t.Mode)) + .Where(t => t.WellDepth - t.BitDepth < 0.01) + .GroupBy(t => new + { + t.DateTime.Hour, + WellDepthX10 = Math.Truncate(t.WellDepth * 10), + t.Mode, + t.IdFeedRegulator + }) + .Select(g => new TelemetryDataSaubStatDto + { + Count = g.Count(), + IdMode = g.Key.Mode, + IdFeedRegulator = g.Key.IdFeedRegulator, + + DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified), + DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified), + + WellDepthMin = g.Min(t => t.WellDepth), + WellDepthMax = g.Max(t => t.WellDepth), + + Pressure = g.Average(t => t.Pressure), + PressureSp = g.Average(t => t.PressureSp!.Value), + PressureIdle = g.Average(t => t.PressureIdle!.Value), + PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value), + PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value), + PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value), + + AxialLoad = g.Average(t => t.AxialLoad), + AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value), + AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value), + + RotorTorque = g.Average(t => t.RotorTorque), + RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value), + RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value), + + BlockSpeed = g.Average(t => t.BlockSpeed!.Value), + BlockSpeedSp = g.Average(t => t.BlockSpeedSp!.Value), + }) + .Where(s => s.WellDepthMin != s.WellDepthMax) + .Where(s => s.Count > 3) + .OrderBy(t => t.DateMin); + + return await query.ToArrayAsync(token); + } + + public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset) + { + var entity = src.Adapt(); + var telemetryUser = telemetryUserService + .GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User)) + .FirstOrDefault(); + entity.IdUser = telemetryUser?.Id; + entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset); + return entity; + } + + public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset) + { + var dto = src.Adapt(); + var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser ?? int.MinValue); + dto.User = telemetryUser?.MakeDisplayName(); + dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset); + dto.BitDepth = src.BitDepth <= src.WellDepth + ? src.BitDepth + : src.WellDepth; + return dto; + } + + public async Task GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token) + { + double intervalSec = (endDate - beginDate).TotalSeconds; + if (intervalSec > 60 * 60 * 24 * 3) + throw new ArgumentInvalidException(nameof(endDate), "Слишком большой диапазон"); + + var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell) + ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина id:{idWell} не содержит телеметрии"); + + var approxPointsCount = intervalSec switch + { + < 2048 => 2048, + < 8_192 => 4_096, + < 131_072 => 16_384, + _ => 32_768 + }; + + var data = await GetAsync(idWell, beginDate, intervalSec, approxPointsCount, token); + + var fileName = $"DataSaub idWell{idWell}"; + if (telemetry.Info is not null) + fileName += $" {telemetry.Info?.Cluster}, {telemetry.Info?.Well}"; + fileName += $" {beginDate:yyyy-MM-DDTHH-mm} - {endDate:yyyy-MM-DDTHH-mm}.csv"; + + var outStream = new MemoryStream(); + using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true)) + { + var entryFile = archive.CreateEntry(fileName, CompressionLevel.Optimal); + using var entryStream = entryFile.Open(); + var serializer = new CsvSerializer(); + serializer.Serialize(data, entryStream); + } + outStream.Seek(0, SeekOrigin.Begin); + return outStream; + } } diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs index fd27efdf..93c5ab2f 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs @@ -45,17 +45,16 @@ internal class SubsystemService : ISubsystemService if(!well.IdTelemetry.HasValue) return Enumerable.Empty(); - var detectedOperationSummaryRequest = new DetectedOperationRequest - { + var detectedOperationSummaryRequest = new DetectedOperationByWellRequest + { IdWell = request.IdWell, - IdsTelemetries = new[] { well.IdTelemetry.Value }, IdsCategories = WellOperationCategory.MechanicalDrillingSubIds, GeDateStart = request.GeDate, LeDateEnd = request.LeDate, - GeDepth = request.GeDepth, - LeDepth = request.LeDepth, + GeDepthStart = request.GeDepth, + LeDepthEnd = request.LeDepth, }; var operations = await detectedOperationService.GetOperationsAsync(detectedOperationSummaryRequest, @@ -78,7 +77,7 @@ internal class SubsystemService : ISubsystemService return result; } - private async Task> CalcStatAsync(IEnumerable operations, CancellationToken token) + private async Task> CalcStatAsync(IEnumerable operations, CancellationToken token) { if (!subsystems.Any()) subsystems = (await subsystemRepository.GetAllAsync(token)).ToDictionary(s => s.Id, s => s); @@ -92,7 +91,7 @@ internal class SubsystemService : ISubsystemService return stat; } - private SubsystemStatDto CalcOscillationStat(IEnumerable operations) + private SubsystemStatDto CalcOscillationStat(IEnumerable operations) { operations = operations.Where(o => o.IdCategory == WellOperationCategory.IdSlide); @@ -114,7 +113,7 @@ internal class SubsystemService : ISubsystemService return oscillationStat; } - private IEnumerable CalcApdStat(IEnumerable operations) + private IEnumerable CalcApdStat(IEnumerable operations) { var apdRotorAndSlide = operations .Where(o => WellOperationCategory.MechanicalDrillingSubIds.Contains(o.IdCategory)) @@ -169,7 +168,7 @@ internal class SubsystemService : ISubsystemService } private static (double SumDepthInterval, double UsedTimeHours, int Count) AggregateOperations(int idSubsystem, - IEnumerable operations) => + IEnumerable operations) => idSubsystem switch { IdSubsystemAPDRotor => CalcOperationsByEnableSubsystems(operations, EnabledSubsystemsFlags.AutoRotor), @@ -180,7 +179,7 @@ internal class SubsystemService : ISubsystemService }; private static (double SumDepthInterval, double UsedTimeHours, int OperationCount) CalcOperationsByEnableSubsystems( - IEnumerable operations, + IEnumerable operations, EnabledSubsystemsFlags enabledSubsystems) { var filtered = operations.Where(o => enabledSubsystems.HasEnabledSubsystems(o.EnabledSubsystems)); @@ -215,7 +214,7 @@ internal class SubsystemService : ISubsystemService var leDateUtc = leDate?.ToUtcDateTimeOffset(hoursOffset); - var request = new DetectedOperationRequest + var request = new DetectedOperationByWellRequest { IdWell = well.Id, IdsCategories = WellOperationCategory.MechanicalDrillingSubIds, diff --git a/AsbCloudInfrastructure/Services/WellService.cs b/AsbCloudInfrastructure/Services/WellService.cs index befa8851..4d6ec10a 100644 --- a/AsbCloudInfrastructure/Services/WellService.cs +++ b/AsbCloudInfrastructure/Services/WellService.cs @@ -36,13 +36,18 @@ namespace AsbCloudInfrastructure.Services .ThenInclude(r => r.Company) .AsNoTracking(); - public WellService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, ITimezoneService timezoneService, WellInfoService wellInfoService) + public WellService(IAsbCloudDbContext db, + IMemoryCache memoryCache, + ITelemetryService telemetryService, + ITimezoneService timezoneService, + WellInfoService wellInfoService, + IWellOperationCategoryRepository wellOperationCategoryRepository) : base(db, memoryCache, MakeQueryWell) { this.telemetryService = telemetryService; this.timezoneService = timezoneService; this.wellInfoService = wellInfoService; - this.wellOperationRepository = new WellOperationRepository(db, memoryCache, this); + this.wellOperationRepository = new WellOperationRepository(db, memoryCache, this, wellOperationCategoryRepository); companyTypesService = new CrudCacheRepositoryBase(dbContext, memoryCache); } diff --git a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs index 02f2d4cb..c6bd6dda 100644 --- a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs +++ b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs @@ -5,7 +5,6 @@ using AsbCloudApp.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; using AsbCloudInfrastructure.Services.DetectOperations; @@ -57,7 +56,7 @@ namespace AsbCloudWebApi.Controllers.SAUB [HttpGet] [ProducesResponseType(typeof(DetectedOperationListDto), (int)System.Net.HttpStatusCode.OK)] public async Task GetAsync( - [FromQuery] DetectedOperationRequest request, + [FromQuery] DetectedOperationByWellRequest request, CancellationToken token) { if (!await UserHasAccessToWellAsync(request.IdWell, token)) @@ -76,7 +75,7 @@ namespace AsbCloudWebApi.Controllers.SAUB [HttpGet("stat")] [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] public async Task GetStatAsync( - [FromQuery] DetectedOperationRequest request, + [FromQuery] DetectedOperationByWellRequest request, CancellationToken token) { if (!await UserHasAccessToWellAsync(request.IdWell, token)) @@ -98,7 +97,7 @@ namespace AsbCloudWebApi.Controllers.SAUB [Permission] [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] public async Task DeleteAsync( - [FromQuery] DetectedOperationRequest request, + [FromQuery] DetectedOperationByWellRequest request, CancellationToken token) { if (!await UserHasAccessToWellAsync(request.IdWell, token))