diff --git a/AsbCloudApp/Data/User/ContactDto.cs b/AsbCloudApp/Data/User/ContactDto.cs index dd6b3212..8445d801 100644 --- a/AsbCloudApp/Data/User/ContactDto.cs +++ b/AsbCloudApp/Data/User/ContactDto.cs @@ -35,7 +35,7 @@ public class ContactDto : IId /// /// Email /// - [RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage = "Некорректный email")] + [RegularExpression(@"^[a-zA-Z0-9_\-\.]{3,128}@[a-zA-Z0-9_\-\.]{2,128}\.[a-zA-Z]{1,32}$", ErrorMessage = "Некорректный email")] public string? Email { get; set; } /// diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index fcf31f00..612d58d9 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -33,6 +33,23 @@ namespace AsbCloudApp.Repositories /// /// Task> GetPageAsync(WellOperationRequest request, CancellationToken token); + + /// + /// Получить страницу с операцией + /// + /// + /// + /// + /// + /// + /// + /// + Task?> GetPageAsync(int idWell, + int id, + int operationType, + int? take, + IEnumerable? sortFields, + CancellationToken token); /// /// Получить статистику операции по скважине с группировкой по категориям diff --git a/AsbCloudApp/Requests/WellContactRequest.cs b/AsbCloudApp/Requests/WellContactRequest.cs new file mode 100644 index 00000000..26fd4f1b --- /dev/null +++ b/AsbCloudApp/Requests/WellContactRequest.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace AsbCloudApp.Requests +{ + /// + /// Запрос на получение контактов + /// + public class WellContactRequest + { + /// + /// Идентификаторы скважин + /// + public IEnumerable IdsWells { get; set; } = null!; + + /// + /// Ключ типа контактов + /// + public int? ContactTypeId { get; set; } + } +} diff --git a/AsbCloudApp/Services/IDataSaubStatService.cs b/AsbCloudApp/Services/IDataSaubStatService.cs new file mode 100644 index 00000000..f72d4480 --- /dev/null +++ b/AsbCloudApp/Services/IDataSaubStatService.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudApp.Services +{ + /// + /// Сервис записи данных в таблицу DataSaubStat, которая используется для построения РТК-отчета + /// + public interface IDataSaubStatService + { + /// + /// Расчет статистики DataSaubStat + /// + /// + /// Количество дней за которые должны были приходить данные, чтобы телеметрия попала в обработку. + /// + /// + /// + /// + Task CreateStatAsync(int lastDaysFilter, Action onProgressCallback, CancellationToken token); + + } +} diff --git a/AsbCloudApp/Services/IWellContactService.cs b/AsbCloudApp/Services/IWellContactService.cs index 266da573..cf7124db 100644 --- a/AsbCloudApp/Services/IWellContactService.cs +++ b/AsbCloudApp/Services/IWellContactService.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Data.User; +using AsbCloudApp.Requests; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -12,13 +13,12 @@ namespace AsbCloudApp.Services public interface IWellContactService { /// - /// Получение контактов по ключу скважины и типу контакта + /// Получение контактов по параметрам запроса /// - /// ключ скважины - /// тип контакта + /// ключ скважины /// /// - Task> GetAllAsync(int idWell, int contactTypeId, CancellationToken token); + Task> GetAllAsync(WellContactRequest request, CancellationToken token); /// /// Получение контакта по ключу diff --git a/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs b/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs index c7ea36ff..6a2f92de 100644 --- a/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs +++ b/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs @@ -16,7 +16,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Background.PeriodicWorks { /// - /// задача по добавлению данных в таблицу DataSaubStat, которая используется дл построения РТК-отчета + /// задача по добавлению данных в таблицу DataSaubStat, которая используется для построения РТК-отчета /// internal class WorkDataSaubStat : Work { @@ -29,209 +29,11 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) { + var dataSaubStatService = services.GetRequiredService(); - var telemetryDataCache = services.GetRequiredService>(); + if (dataSaubStatService != null ) + await dataSaubStatService.CreateStatAsync(Gap, onProgressCallback, token); - var cacheRequest = new TelemetryDataRequest() - { - GeDate = DateTime.UtcNow.AddDays(-Gap) - }; - var idTelemetries = telemetryDataCache.GetIds(cacheRequest).ToArray(); - - if (!idTelemetries.Any()) - 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.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 static async Task CreateStatForTelemetryFromDate( - int idTelemetry, - DateTimeOffset begin, - ITelemetryDataSaubService dataSaubService, - IDataSaubStatRepository dataSaubStatRepo, - IDetectedOperationRepository detectedOperationRepository, - CancellationToken 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 geDate = detectedOperations.First().DateStart; - var leDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd; - - var dataSaub = await dataSaubService.Get(idTelemetry, true, geDate, leDate, 100_000, token); - - if (!dataSaub.Any()) - return 0; - - 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, TelemetryDataSaubDto[] dataSaub) - { - var indexStart = 0; - var indexEnd = 0; - var result = new List(); - - if (!dataSaub.Any()) - return result; - - foreach (var operation in detectedOperations) - { - indexStart = Array.FindIndex(dataSaub, indexEnd, t => t.DateTime >= operation.DateStart); - if (indexStart < 0) - break; - - indexEnd = Array.FindIndex(dataSaub, indexStart, t => t.DateTime > operation.DateEnd); - - if (indexEnd < 0) - indexEnd = dataSaub.Length - 1; - - if (indexEnd == indexStart) - continue; - - var length = indexEnd - indexStart; - - var subset = dataSaub.AsSpan(indexStart, length); - var stats = CalcStats(operation, subset); - result.AddRange(stats); - } - return result; - } - - private static IEnumerable CalcStats(DetectedOperationDto operation, Span dataSaub) - { - var result = new List(); - - var indexStart = 0; - for (var i = 1; i < dataSaub.Length; i++) - { - var previous = dataSaub[i - 1]; - var current = dataSaub[i]; - - if (IsNewCacheItem(previous, current) || i == dataSaub.Length - 1) - { - var length = i - indexStart; - var span = dataSaub.Slice(indexStart, length); - indexStart = i; - if (length <= 2 || (span[^1].WellDepth - span[0].WellDepth) < 0.001) - continue; // мелкие выборки не учитываем. - var stat = CalcStat(operation, span); - result.Add(stat); - } - } - - return result; - } - - private static DataSaubStatDto CalcStat(DetectedOperationDto operation, Span span) - { - var aggregatedValues = CalcAggregate(span); - var dateStart = span[0].DateTime; - var dateEnd = span[^1].DateTime; - var depthStart = span[0].WellDepth; - var depthEnd = span[^1].WellDepth; - var speed = ((depthEnd - depthStart) / (dateEnd - dateStart).TotalHours); - - var processMapDrillingCacheItem = new DataSaubStatDto - { - DateStart = dateStart, - DateEnd = dateEnd, - DepthStart = depthStart, - DepthEnd = depthEnd, - Speed = speed, - BlockSpeedSp = span[0].BlockSpeedSp, - Pressure = aggregatedValues.Pressure, - PressureIdle = span[0].PressureIdle, - PressureSp = span[0].PressureSp, - AxialLoad = aggregatedValues.AxialLoad, - AxialLoadSp = span[0].AxialLoadSp, - AxialLoadLimitMax = span[0].AxialLoadLimitMax, - RotorTorque = aggregatedValues.RotorTorque, - RotorTorqueSp = span[0].RotorTorqueSp, - RotorTorqueLimitMax = span[0].RotorTorqueLimitMax, - IdFeedRegulator = span[0].IdFeedRegulator, - RotorSpeed = aggregatedValues.RotorSpeed, - IdCategory = operation.IdCategory, - EnabledSubsystems = operation.EnabledSubsystems, - HasOscillation = operation.EnabledSubsystems.IsAutoOscillation, - IdTelemetry = operation.IdTelemetry, - Flow = aggregatedValues.Flow - }; - return processMapDrillingCacheItem; - } - - private static ( - double Pressure, - double AxialLoad, - double RotorTorque, - double RotorSpeed, - double Flow - ) CalcAggregate(Span span) - { - var sumPressure = 0.0; - var sumAxialLoad = 0.0; - var sumRotorTorque = 0.0; - var sumRotorSpeed = 0.0; - var flow = span[0].Flow ?? 0.0; - var diffDepthTotal = span[^1].WellDepth - span[0].WellDepth; - for (var i = 0; i < span.Length - 1; i++) - { - var diffDepth = span[i + 1].WellDepth - span[i].WellDepth; - sumPressure += diffDepth * span[i].Pressure; - sumAxialLoad += diffDepth * span[i].AxialLoad; - sumRotorTorque += diffDepth * span[i].RotorTorque; - sumRotorSpeed += diffDepth * span[i].RotorSpeed; - flow = span[i + 1].Flow > flow ? span[i + 1].Flow ?? 0.0 : flow; - } - return ( - Pressure: sumPressure / diffDepthTotal, - AxialLoad: sumAxialLoad / diffDepthTotal, - RotorTorque: sumRotorTorque / diffDepthTotal, - RotorSpeed: sumRotorSpeed / diffDepthTotal, - Flow: flow - ); - } - - private static bool IsNewCacheItem(TelemetryDataSaubDto previous, TelemetryDataSaubDto current) - { - return !(current.Mode == previous.Mode) - || !(current.WellDepth >= previous.WellDepth) - || !(current.BlockSpeedSp == previous.BlockSpeedSp) - || !(current.PressureIdle == previous.PressureIdle) - || !(current.PressureSp == previous.PressureSp) - || !(current.AxialLoadSp == previous.AxialLoadSp) - || !(current.AxialLoadLimitMax == previous.AxialLoadLimitMax) - || !(current.HookWeightIdle == previous.HookWeightIdle) - || !(current.RotorTorqueIdle == previous.RotorTorqueIdle) - || !(current.RotorTorqueSp == previous.RotorTorqueSp) - || !(current.RotorTorqueLimitMax == previous.RotorTorqueLimitMax) - || !(current.IdFeedRegulator == previous.IdFeedRegulator); - } + } } } diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 9fbe1721..5ce1ada0 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -316,6 +316,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient< IChangeLogRepository, diff --git a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs index 157b204d..c47ebd05 100644 --- a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs +++ b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs @@ -117,6 +117,22 @@ public abstract class ChangeLogRepositoryAbstract : ICh } public async Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token) + { + using var transaction = db.Database.BeginTransaction(); + try + { + var result = await InsertRangeWithoutTransaction(idUser, dtos, token); + await transaction.CommitAsync(token); + return result; + } + catch + { + await transaction.RollbackAsync(token); + throw; + } + } + + private async Task InsertRangeWithoutTransaction(int idUser, IEnumerable dtos, CancellationToken token) { var result = 0; if (dtos.Any()) @@ -138,7 +154,8 @@ public abstract class ChangeLogRepositoryAbstract : ICh result += await SaveChangesWithExceptionHandling(token); } - return result; + + return result; } public async Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token) @@ -248,7 +265,7 @@ public abstract class ChangeLogRepositoryAbstract : ICh try { result += await Clear(idUser, request, token); - result += await InsertRange(idUser, dtos, token); + result += await InsertRangeWithoutTransaction(idUser, dtos, token); await transaction.CommitAsync(token); return result; diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 8fa9433d..843956cb 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -40,7 +40,7 @@ public class WellOperationRepository : CrudRepositoryBase wellOperationCategoryRepository.Get(true, false).ToDictionary(c => c.Id)); LazyWellSectionTypes = new(() => GetSectionTypes().ToDictionary(c => c.Id)); } - + public IEnumerable GetSectionTypes() => memoryCache .GetOrCreateBasic(dbContext.WellSectionTypes) @@ -71,6 +71,48 @@ public class WellOperationRepository : CrudRepositoryBase?> GetPageAsync(int idWell, + int id, + int operationType, + int? take, + IEnumerable? sortFields, + CancellationToken token) + { + var request = new WellOperationRequest(new[] { idWell }) + { + OperationType = operationType, + SortFields = sortFields, + }; + + var (wellOperations, count) = await GetWithDaysAndNpvAsync(request, token); + + var skip = 0; + take ??= 32; + + while (skip < count) + { + var page = wellOperations.Skip(skip) + .Take(take.Value); + + if (page.Any(x => x.Id == id)) + { + var paginationContainer = new PaginationContainer + { + Skip = skip, + Take = take.Value, + Items = page, + Count = count + }; + + return paginationContainer; + } + + skip += take.Value; + } + + return null; + } + public async Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token) { var query = BuildQuery(request); @@ -209,9 +251,7 @@ public class WellOperationRepository : CrudRepositoryBase e.DateStart) - .FirstOrDefault()!; + var firstWellOperation = wellOperationsWithType.MinBy(e => e.DateStart); var operationsWithNpt = wellOperationsWithType .Where(o => WellOperationCategory.NonProductiveTimeSubIds.Contains(o.IdCategory)); @@ -224,11 +264,13 @@ public class WellOperationRepository : CrudRepositoryBase { - var dto = Convert(entity); + var dto = Convert(entity, timezoneOffset); dto.Day = (entity.DateStart - firstWellOperation.DateStart).TotalDays; dto.NptHours = operationsWithNpt .Where(o => o.DateStart <= entity.DateStart) @@ -427,15 +469,11 @@ public class WellOperationRepository : CrudRepositoryBase(); - dto.DateStart = src.DateStart.ToOffset(timeZoneOffset); - dto.LastUpdateDate = src.LastUpdateDate.ToOffset(timeZoneOffset); + dto.DateStart = src.DateStart.ToOffset(timezoneOffset); + dto.LastUpdateDate = src.LastUpdateDate.ToOffset(timezoneOffset); dto.OperationCategoryName = LazyWellCategories.Value.TryGetValue(src.IdCategory, out WellOperationCategoryDto? category) ? category.Name : string.Empty; dto.WellSectionTypeCaption = LazyWellSectionTypes.Value.TryGetValue(src.IdWellSectionType, out WellSectionTypeDto? sectionType) ? sectionType.Caption : string.Empty; diff --git a/AsbCloudInfrastructure/Services/DataSaubStatService.cs b/AsbCloudInfrastructure/Services/DataSaubStatService.cs new file mode 100644 index 00000000..e54a380d --- /dev/null +++ b/AsbCloudInfrastructure/Services/DataSaubStatService.cs @@ -0,0 +1,237 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.DetectedOperation; +using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudDb.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services +{ + + public class DataSaubStatService : IDataSaubStatService + { + private IDataSaubStatRepository dataSaubStatRepository; + private ITelemetryDataCache telemetryDataCache; + private ITelemetryDataSaubService dataSaubService; + private IDetectedOperationRepository detectedOperationRepository; + + public DataSaubStatService( + IDataSaubStatRepository dataSaubStatRepository, + ITelemetryDataCache telemetryDataCache, + ITelemetryDataSaubService dataSaubService, + IDetectedOperationRepository detectedOperationRepository) + { + this.dataSaubStatRepository = dataSaubStatRepository; + this.telemetryDataCache = telemetryDataCache; + this.dataSaubService = dataSaubService; + this.detectedOperationRepository = detectedOperationRepository; + } + + public async Task CreateStatAsync(int lastDaysFilter, Action onProgressCallback, CancellationToken token) + { + var cacheRequest = new TelemetryDataRequest() + { + GeDate = DateTime.UtcNow.AddDays(-lastDaysFilter) + }; + var idTelemetries = telemetryDataCache.GetIds(cacheRequest).ToArray(); + + if (!idTelemetries.Any()) + return; + + var stats = await dataSaubStatRepository.GetLastsAsync(idTelemetries, token); + + for (var i = 0; i < idTelemetries.Length; i++) + { + var idTelemetry = idTelemetries[i]; + var lastDate = stats.FirstOrDefault(s => s.IdTelemetry == idTelemetry)?.DateEnd.ToUniversalTime() ?? DateTimeOffset.UnixEpoch; + var statsCount = await CreateStatForTelemetryFromDate(idTelemetry, lastDate, token); + if(onProgressCallback != null) + onProgressCallback($"Calculate stat for telemetry: {idTelemetry}; from {lastDate}; results count: {statsCount};", 1d * i / idTelemetries.Length); + } + } + + private async Task CreateStatForTelemetryFromDate( + int idTelemetry, + DateTimeOffset begin, + CancellationToken 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 geDate = detectedOperations.First().DateStart; + var leDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd; + + var dataSaub = await dataSaubService.Get(idTelemetry, true, geDate, leDate, 100_000, token); + + if (!dataSaub.Any()) + return 0; + + if (dataSaub is not TelemetryDataSaubDto[] dataSaubArray) + dataSaubArray = dataSaub.ToArray(); + + var dataSaubStats = CreateDataSaubStat(detectedOperations, dataSaubArray); + + return await dataSaubStatRepository.InsertRangeAsync(dataSaubStats, token); + } + + private static IEnumerable CreateDataSaubStat(IEnumerable detectedOperations, TelemetryDataSaubDto[] dataSaub) + { + var indexStart = 0; + var indexEnd = 0; + var result = new List(); + + if (!dataSaub.Any()) + return result; + + foreach (var operation in detectedOperations) + { + indexStart = Array.FindIndex(dataSaub, indexEnd, t => t.DateTime >= operation.DateStart); + if (indexStart < 0) + break; + + indexEnd = Array.FindIndex(dataSaub, indexStart, t => t.DateTime > operation.DateEnd); + + if (indexEnd < 0) + indexEnd = dataSaub.Length - 1; + + if (indexEnd == indexStart) + continue; + + var length = indexEnd - indexStart + 1; + + var subset = dataSaub.AsSpan(indexStart, length); + var stats = CalcStats(operation, subset); + result.AddRange(stats); + } + return result; + } + + private static IEnumerable CalcStats(DetectedOperationDto operation, Span dataSaub) + { + var result = new List(); + + var indexStart = 0; + for (var i = 1; i < dataSaub.Length; i++) + { + var previous = dataSaub[i - 1]; + var current = dataSaub[i]; + + if (IsNewCacheItem(previous, current) || i == dataSaub.Length - 1) + { + var length = i - indexStart + 1; + var span = dataSaub.Slice(indexStart, length); + indexStart = i; + if (length <= 2 || (span[^1].WellDepth - span[0].WellDepth) < 0.001) + continue; // мелкие выборки не учитываем. + var stat = CalcStat(operation, span); + result.Add(stat); + } + } + + return result; + } + + private static DataSaubStatDto CalcStat(DetectedOperationDto operation, Span span) + { + var aggregatedValues = CalcAggregate(span); + var dateStart = span[0].DateTime; + var dateEnd = span[^1].DateTime; + var depthStart = span[0].WellDepth; + var depthEnd = span[^1].WellDepth; + var speed = ((depthEnd - depthStart) / (dateEnd - dateStart).TotalHours); + + var processMapDrillingCacheItem = new DataSaubStatDto + { + DateStart = dateStart, + DateEnd = dateEnd, + DepthStart = depthStart, + DepthEnd = depthEnd, + Speed = speed, + BlockSpeedSp = span[0].BlockSpeedSp, + Pressure = aggregatedValues.Pressure, + PressureIdle = span[0].PressureIdle, + PressureSp = span[0].PressureSp, + AxialLoad = aggregatedValues.AxialLoad, + AxialLoadSp = span[0].AxialLoadSp, + AxialLoadLimitMax = span[0].AxialLoadLimitMax, + RotorTorque = aggregatedValues.RotorTorque, + RotorTorqueSp = span[0].RotorTorqueSp, + RotorTorqueLimitMax = span[0].RotorTorqueLimitMax, + IdFeedRegulator = span[0].IdFeedRegulator, + RotorSpeed = aggregatedValues.RotorSpeed, + IdCategory = operation.IdCategory, + EnabledSubsystems = operation.EnabledSubsystems, + HasOscillation = operation.EnabledSubsystems.IsAutoOscillation, + IdTelemetry = operation.IdTelemetry, + Flow = aggregatedValues.Flow + }; + return processMapDrillingCacheItem; + } + + private static ( + double Pressure, + double AxialLoad, + double RotorTorque, + double RotorSpeed, + double Flow + ) CalcAggregate(Span span) + { + var sumPressure = 0.0; + var sumAxialLoad = 0.0; + var sumRotorTorque = 0.0; + var sumRotorSpeed = 0.0; + var flow = span[0].Flow ?? 0.0; + var diffDepthTotal = span[^1].WellDepth - span[0].WellDepth; + for (var i = 0; i < span.Length - 1; i++) + { + var diffDepth = span[i + 1].WellDepth - span[i].WellDepth; + sumPressure += diffDepth * span[i].Pressure; + sumAxialLoad += diffDepth * span[i].AxialLoad; + sumRotorTorque += diffDepth * span[i].RotorTorque; + sumRotorSpeed += diffDepth * span[i].RotorSpeed; + flow = span[i + 1].Flow > flow ? span[i + 1].Flow ?? 0.0 : flow; + } + return ( + Pressure: sumPressure / diffDepthTotal, + AxialLoad: sumAxialLoad / diffDepthTotal, + RotorTorque: sumRotorTorque / diffDepthTotal, + RotorSpeed: sumRotorSpeed / diffDepthTotal, + Flow: flow + ); + } + + private static bool IsNewCacheItem(TelemetryDataSaubDto previous, TelemetryDataSaubDto current) + { + return !(current.Mode == previous.Mode) + || !(current.WellDepth >= previous.WellDepth) + || !(current.BlockSpeedSp == previous.BlockSpeedSp) + || !(current.PressureIdle == previous.PressureIdle) + || !(current.PressureSp == previous.PressureSp) + || !(current.AxialLoadSp == previous.AxialLoadSp) + || !(current.AxialLoadLimitMax == previous.AxialLoadLimitMax) + || !(current.HookWeightIdle == previous.HookWeightIdle) + || !(current.RotorTorqueIdle == previous.RotorTorqueIdle) + || !(current.RotorTorqueSp == previous.RotorTorqueSp) + || !(current.RotorTorqueLimitMax == previous.RotorTorqueLimitMax) + || !(current.IdFeedRegulator == previous.IdFeedRegulator); + } + } + +} diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs index e2ab1e13..76bcefa2 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs @@ -157,8 +157,8 @@ internal class SubsystemService : ISubsystemService SumDepthInterval = sumDepthInterval, OperationCount = operationCount, }; - - oscillationStat.KUsage = oscillationStat.SumDepthInterval / oscillationStat.SumOperationDepthInterval; + if(oscillationStat.SumOperationDepthInterval != 0d) + oscillationStat.KUsage = oscillationStat.SumDepthInterval / oscillationStat.SumOperationDepthInterval; return oscillationStat; } diff --git a/AsbCloudInfrastructure/Services/WellContactService.cs b/AsbCloudInfrastructure/Services/WellContactService.cs index 1bb45b77..770375f3 100644 --- a/AsbCloudInfrastructure/Services/WellContactService.cs +++ b/AsbCloudInfrastructure/Services/WellContactService.cs @@ -1,6 +1,7 @@ using AsbCloudApp.Data; using AsbCloudApp.Data.User; using AsbCloudApp.Exceptions; +using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb.Model; using Mapster; @@ -21,17 +22,22 @@ namespace AsbCloudInfrastructure.Services this.db = db; } - public async Task> GetAllAsync(int wellId, int contactTypeId, CancellationToken token) + public async Task> GetAllAsync(WellContactRequest request, CancellationToken token) { var query = db.Contacts - .Where(c => c.IdCompanyType == contactTypeId) - .Where(c => c.IdWell == wellId) - .Select(c => c.Adapt()); + .Where(c => request.IdsWells.Contains(c.IdWell)); + + if (request.ContactTypeId.HasValue) + { + query = query.Where(c => c.IdCompanyType == request.ContactTypeId); + }; var entities = await query.AsNoTracking() .ToArrayAsync(token); - return entities; + var dtos = entities.Select(c => c.Adapt()); + + return dtos; } public async Task GetAsync(int idWell, int id, CancellationToken token) diff --git a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationFactTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationFactTemplate.xlsx index 12fc2a44..cc90ef89 100644 Binary files a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationFactTemplate.xlsx and b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationFactTemplate.xlsx differ diff --git a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx index ca2d01c7..3ff28d43 100644 Binary files a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx and b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx differ diff --git a/AsbCloudWebApi.Tests/Services/DataSaubStatServiceTest.cs b/AsbCloudWebApi.Tests/Services/DataSaubStatServiceTest.cs new file mode 100644 index 00000000..6d65a1f2 --- /dev/null +++ b/AsbCloudWebApi.Tests/Services/DataSaubStatServiceTest.cs @@ -0,0 +1,323 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.DetectedOperation; +using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudInfrastructure.Services; +using Mapster; +using NSubstitute; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace AsbCloudWebApi.Tests.Services; + +public class DataSaubStatServiceTest +{ + private readonly int Gap = 5; + private readonly IDataSaubStatRepository dataSaubStatRepositoryMock = Substitute.For(); + private readonly ITelemetryDataCache telemetryDataCacheMock = Substitute.For>(); + private readonly IDetectedOperationRepository detectedOperationRepositoryMock = Substitute.For(); + private readonly ITelemetryDataSaubService dataSaubServiceMock = Substitute.For(); + + private DataSaubStatService dataSaubStatService; + + private int[] idTelemetries = [1]; + private IEnumerable dataSaubStatDtos = new List() + { + new DataSaubStatDto { + Id = 1, + AxialLoad = 1, + AxialLoadLimitMax = 1, + AxialLoadSp = 1, + BlockSpeedSp = 1, + DateEnd = DateTime.UtcNow, + DateStart = DateTime.UtcNow.AddHours(-1), + DepthEnd = 2, + DepthStart = 1, + EnabledSubsystems = 1, + Flow = 1, + HasOscillation = true, + IdCategory = 1, + IdFeedRegulator = 1, + IdTelemetry = 1, + Pressure = 1, + PressureIdle = 1, + PressureSp = 1, + RotorSpeed = 1, + RotorTorque = 1, + RotorTorqueLimitMax = 1, + RotorTorqueSp = 1, + Speed = 1 + }, + new DataSaubStatDto { + Id = 2, + AxialLoad = 2, + AxialLoadLimitMax = 2, + AxialLoadSp = 2, + BlockSpeedSp = 2, + DateEnd = DateTime.UtcNow, + DateStart = DateTime.UtcNow.AddHours(-1), + DepthEnd = 3, + DepthStart = 2, + EnabledSubsystems = 2, + Flow = 2, + HasOscillation = true, + IdCategory = 2, + IdFeedRegulator = 2, + IdTelemetry = 2, + Pressure = 2, + PressureIdle = 2, + PressureSp = 2, + RotorSpeed = 2, + RotorTorque = 2, + RotorTorqueLimitMax = 2, + RotorTorqueSp = 2, + Speed = 2 + }, + new DataSaubStatDto { + Id = 3, + AxialLoad = 3, + AxialLoadLimitMax = 3, + AxialLoadSp = 3, + BlockSpeedSp = 3, + DateEnd = DateTime.UtcNow, + DateStart = DateTime.UtcNow.AddHours(-1), + DepthEnd = 4, + DepthStart = 3, + EnabledSubsystems = 3, + Flow = 3, + HasOscillation = true, + IdCategory = 3, + IdFeedRegulator = 3, + IdTelemetry = 3, + Pressure = 3, + PressureIdle = 3, + PressureSp = 3, + RotorSpeed = 3, + RotorTorque = 3, + RotorTorqueLimitMax = 3, + RotorTorqueSp = 3, + Speed = 3 + }, + }; + + private List detectedOperationDtos = new List() { + new DetectedOperationDto { + Id = 1, + DateEnd = DateTimeOffset.UtcNow, + DateStart = DateTimeOffset.UtcNow.AddHours(-1), + DepthStart = 1, + DepthEnd = 2, + IdCategory = 5002, + IdTelemetry = 1, + Value = 1, + IdEditor = 1, + IdUserAtStart = 1 + } + }; + + private List telemetryDataSaubDtos = new List { + new TelemetryDataSaubDto() + { + IdTelemetry = 1, + DateTime = DateTime.UtcNow.AddMinutes(-30), + AxialLoad = 1, + AxialLoadLimitMax = 1, + AxialLoadSp = 1, + BitDepth = 1, + BlockPosition = 1, + BlockPositionMax = 1, + BlockPositionMin = 1, + BlockSpeed = 1, + BlockSpeedSp = 1, + BlockSpeedSpDevelop = 1, + BlockSpeedSpRotor = 1, + BlockSpeedSpSlide = 1, + Flow = 1, + FlowDeltaLimitMax = 1, + FlowIdle = 1, + HookWeight = 1, + HookWeightIdle = 1, + HookWeightLimitMax = 1, + HookWeightLimitMin = 1, + IdFeedRegulator = 1, + IdUser = 1, + Mode = 1, + Mse = 1, + MseState = 1, + Pressure = 1, + PressureDeltaLimitMax = 1, + PressureIdle = 1, + PressureSp = 1, + PressureSpDevelop = 1, + PressureSpRotor = 1, + PressureSpSlide = 1, + Pump0Flow = 1, + Pump1Flow = 1, + Pump2Flow = 1, + RotorSpeed = 1, + RotorTorque = 1, + RotorTorqueIdle = 1, + RotorTorqueSp = 1, + RotorTorqueLimitMax = 1, + WellDepth = 10, + } + }; + + public DataSaubStatServiceTest() + { + telemetryDataCacheMock + .GetIds(Arg.Any()) + .Returns(idTelemetries); + + dataSaubStatRepositoryMock + .GetLastsAsync(Arg.Any(), Arg.Any()) + .Returns(dataSaubStatDtos); + + var telemetrySaubDto = telemetryDataSaubDtos.FirstOrDefault(); + if (telemetrySaubDto != null) + { + //заполнение списка телеметрий следующим образом: + // - всего в списке 6 элементов: + // - все они попадают в диапазон, определенный датами DateStart и DateEnd соответствующей записи detectedOperation + // - из этих 6-х записей у 2-х записей меняется параметр, + // являющийся признаком начала нового интервала (новой записи dataSaubStat) + // таким образом, в базе данных должно создаться 1 новая запись dataSaubStat (insertedDataSaubStatCount = 1) + var telemetrySaubDto1 = telemetrySaubDto.Adapt(); + telemetrySaubDto1.DateTime = DateTime.UtcNow.AddMinutes(-20); + telemetryDataSaubDtos.Add(telemetrySaubDto1); + + var telemetrySaubDto2 = telemetrySaubDto.Adapt(); + telemetrySaubDto2.DateTime = DateTime.UtcNow.AddMinutes(-10); + telemetrySaubDto2.RotorTorqueLimitMax = 2; + telemetryDataSaubDtos.Add(telemetrySaubDto2); + + var telemetrySaubDto3 = telemetrySaubDto.Adapt(); + telemetrySaubDto3.DateTime = DateTime.UtcNow.AddMinutes(-8); + telemetrySaubDto3.RotorTorqueLimitMax = 2; + telemetryDataSaubDtos.Add(telemetrySaubDto3); + + var telemetrySaubDto4 = telemetrySaubDto.Adapt(); + telemetrySaubDto4.DateTime = DateTime.UtcNow.AddMinutes(-6); + telemetrySaubDto4.RotorTorqueLimitMax = 2; + telemetryDataSaubDtos.Add(telemetrySaubDto4); + + var telemetrySaubDto5 = telemetrySaubDto.Adapt(); + telemetrySaubDto5.DateTime = DateTime.UtcNow.AddMinutes(-4); + telemetrySaubDto5.RotorTorqueLimitMax = 2; + telemetryDataSaubDtos.Add(telemetrySaubDto5); + + var telemetrySaubDto6 = telemetrySaubDto.Adapt(); + telemetrySaubDto6.DateTime = DateTime.UtcNow.AddMinutes(-2); + telemetrySaubDto6.RotorTorqueLimitMax = 3; + telemetrySaubDto6.WellDepth = 11; + telemetryDataSaubDtos.Add(telemetrySaubDto6); + } + + dataSaubServiceMock + .Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(telemetryDataSaubDtos); + + dataSaubStatService = new DataSaubStatService( + dataSaubStatRepositoryMock, + telemetryDataCacheMock, + dataSaubServiceMock, + detectedOperationRepositoryMock); + } + + [Fact] + public async Task Create_1DataSaubStatItems_ShouldReturn__Success() + { + var insertedDataSaubStatCount = 1; + + detectedOperationRepositoryMock + .Get(Arg.Any(), Arg.Any()) + .Returns(detectedOperationDtos); + + + dataSaubStatRepositoryMock + .InsertRangeAsync(Arg.Any>(), Arg.Any()) + .Returns(insertedDataSaubStatCount); + + Action action = (message, percent) => + { + //assert + Assert.NotNull(percent); + Assert.InRange(percent.Value, 0.0, 1.0); + }; + + //act + await dataSaubStatService.CreateStatAsync(Gap, action, CancellationToken.None); + + //assert + await dataSaubStatRepositoryMock.Received().InsertRangeAsync( + Arg.Is>(l => l.Count() == insertedDataSaubStatCount), + Arg.Any()); + } + + + + [Fact] + public async Task Create_2DataSaubStatItems_ShouldReturn__Success() + { + var insertedDataSaubStatCount = 2; + + var detectedOperationDto = detectedOperationDtos.FirstOrDefault(); + if (detectedOperationDto != null) + { + var detectedOperationDto1 = detectedOperationDto.Adapt(); + detectedOperationDto1.DateStart = DateTimeOffset.UtcNow.AddMinutes(1); + detectedOperationDto1.DateEnd = DateTimeOffset.UtcNow.AddHours(1); + + detectedOperationDtos.Add(detectedOperationDto1); + } + + var telemetryDataSaubDto = telemetryDataSaubDtos.LastOrDefault(); + if (telemetryDataSaubDto != null) + { + var telemetryDataSaubDto1 = telemetryDataSaubDto.Adapt(); + telemetryDataSaubDto1.DateTime = DateTime.UtcNow.AddMinutes(10); + telemetryDataSaubDto1.WellDepth = telemetryDataSaubDto.WellDepth + 1; + + var telemetryDataSaubDto2 = telemetryDataSaubDto.Adapt(); + telemetryDataSaubDto2.DateTime = DateTime.UtcNow.AddMinutes(20); + telemetryDataSaubDto2.WellDepth = telemetryDataSaubDto1.WellDepth + 1; + + var telemetryDataSaubDto3 = telemetryDataSaubDto.Adapt(); + telemetryDataSaubDto3.DateTime = DateTime.UtcNow.AddMinutes(30); + telemetryDataSaubDto3.WellDepth = telemetryDataSaubDto2.WellDepth + 1; + + telemetryDataSaubDtos.Add(telemetryDataSaubDto1); + telemetryDataSaubDtos.Add(telemetryDataSaubDto2); + telemetryDataSaubDtos.Add(telemetryDataSaubDto3); + } + + detectedOperationRepositoryMock + .Get(Arg.Any(), Arg.Any()) + .Returns(detectedOperationDtos); + + dataSaubStatRepositoryMock + .InsertRangeAsync(Arg.Any>(), Arg.Any()) + .Returns(insertedDataSaubStatCount); + + Action action = (message, percent) => + { + //assert + Assert.NotNull(percent); + Assert.InRange(percent.Value, 0.0, 1.0); + }; + + //act + await dataSaubStatService.CreateStatAsync(Gap, action, CancellationToken.None); + + //assert + await dataSaubStatRepositoryMock.Received().InsertRangeAsync( + Arg.Is>(l => l.Count() == insertedDataSaubStatCount), + Arg.Any()); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/WellContactController.cs b/AsbCloudWebApi/Controllers/WellContactController.cs index bdb6a7ae..1bd43564 100644 --- a/AsbCloudWebApi/Controllers/WellContactController.cs +++ b/AsbCloudWebApi/Controllers/WellContactController.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Data.User; +using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb.Model; using Microsoft.AspNetCore.Authorization; @@ -49,12 +50,43 @@ namespace AsbCloudWebApi.Controllers /// [HttpGet("type/{contactTypeId}")] [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] - public async Task GetAllAsync(int idWell, int contactTypeId, CancellationToken token) + public async Task GetByTypeAsync(int idWell, int contactTypeId, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) return Forbid(); - var result = await wellContactsRepository.GetAllAsync(idWell, contactTypeId, token); + var request = new WellContactRequest() + { + IdsWells = new int[] { idWell }, + ContactTypeId = contactTypeId + }; + + var result = await wellContactsRepository.GetAllAsync(request, token); + return Ok(result); + } + + /// + /// Получение контактов по массиву ключей скважины + /// + /// ключи скважин + /// + /// + [HttpGet("/api/well/[controller]")] + [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + public async Task GetAllAsync([FromQuery] IEnumerable idsWells, CancellationToken token) + { + foreach(var idWell in idsWells) + { + if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) + return Forbid(); + } + + var request = new WellContactRequest() + { + IdsWells = idsWells + }; + + var result = await wellContactsRepository.GetAllAsync(request, token); return Ok(result); } diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index eda82a08..90b7404f 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -187,7 +187,7 @@ public class WellOperationController : ControllerBase [HttpGet] [Permission] [ProducesResponseType(typeof(PaginationContainer), StatusCodes.Status200OK)] - public async Task GetPageOperationsAsync( + public async Task GetPageAsync( [FromRoute] int idWell, [FromQuery] WellOperationRequestBase request, CancellationToken token) @@ -201,6 +201,37 @@ public class WellOperationController : ControllerBase return Ok(result); } + /// + /// Получение страницу с нужной операцией + /// + /// id скважины + /// id операции + /// тип получаемых операций + /// кол-во записей на странице + /// параметры сортировки страниц + /// + /// + [HttpGet("getPageWithOperation")] + [Permission] + [ProducesResponseType(typeof(PaginationContainer), StatusCodes.Status200OK)] + public async Task GetPageWithOperationAsync([FromRoute] int idWell, + int id, + int operationType, + int? take, + [FromQuery] IEnumerable? sortFields, + CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + var paginationContainer = await wellOperationRepository.GetPageAsync(idWell, id, operationType, take, sortFields, token); + + if (paginationContainer == null) + return NoContent(); + + return Ok(paginationContainer); + } + /// /// Создает excel файл с "сетевым графиком" ///