diff --git a/AsbCloudApp/Data/DataSaubStatDto.cs b/AsbCloudApp/Data/DataSaubStatDto.cs new file mode 100644 index 00000000..639154df --- /dev/null +++ b/AsbCloudApp/Data/DataSaubStatDto.cs @@ -0,0 +1,132 @@ +using System; + +namespace AsbCloudApp.Data +{ + public class DataSaubStatDto + { + /// + /// + /// + public int Id { get; set; } + + /// + /// Дата и время начала + /// + public DateTimeOffset DateStart { get; set; } + + /// + /// Дата и время окончания + /// + public DateTimeOffset DateEnd { get; set; } + + /// + /// Глубина забоя по стволу начальная + /// + public double DepthStart { get; set; } + + /// + /// Глубина забоя по стволу конечная + /// + public double DepthEnd { get; set; } + + /// + /// Скорость бурения + /// + public double Speed { get; set; } + + /// + /// Ограничение скорости блока + /// + public double? BlockSpeedSp { get; set; } + + /// + /// Давление + /// + public double Pressure { get; set; } + + /// + /// Давление холостого хода + /// + public double? PressureIdle { get; set; } + + /// + /// Ограничение фактического давления + /// + public double? PressureSp { get; set; } + + /// + /// Фактическая нагрузка + /// + public double AxialLoad { get; set; } + + /// + /// Ограничение факт. нагрузки + /// + public double? AxialLoadSp { get; set; } + + /// + /// Максимально допустимая нагрузка + /// + public double? AxialLoadLimitMax { get; set; } + + /// + /// Фактический момент + /// + public double RotorTorque { get; set; } + + /// + /// Ограничение факт. момента + /// + public double? RotorTorqueSp { get; set; } + + /// + /// Максимально допустимый момент + /// + public double? RotorTorqueLimitMax { get; set; } + + /// + /// Работа при достижении ограничения + /// + public short? IdFeedRegulator { get; set; } + + /// + /// Фактическая скорость оборотов ВСП + /// + public double RotorSpeed { get; set; } + + /// + /// Название автоопределённой операции + /// + public int IdCategory { get; set; } + + /// + /// Флаги подсистем + /// + public int EnabledSubsystems { get; set; } + + /// + /// Наличие или отсутствие осцилляции + /// + public bool HasOscillation { get; set; } + + /// + /// Фактический расход + /// + public double Flow { get; set; } + + /// + /// Ключ телеметрии + /// + public int IdTelemetry { get; set; } + + /// + /// Телеметрия + /// + public TelemetryDto Telemetry { get; set; } = null!; + + /// + /// Категория автоопределенной операции + /// + public WellOperationCategoryDto OperationCategory { get; set; } = null!; + } +} diff --git a/AsbCloudApp/Repositories/IDataSaubStatRepository.cs b/AsbCloudApp/Repositories/IDataSaubStatRepository.cs new file mode 100644 index 00000000..efab95b7 --- /dev/null +++ b/AsbCloudApp/Repositories/IDataSaubStatRepository.cs @@ -0,0 +1,29 @@ +using AsbCloudApp.Data; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudApp.Repositories +{ + /// + /// Репозиторий работы с данными из таблицы t_data_daub_stat + /// + public interface IDataSaubStatRepository + { + /// + /// Получение последних по дате окончания бурения записей в разрезе телеметрий + /// + /// ключи телеметрий + /// + /// + Task> GetLastsAsync(int[] idTelemetries, CancellationToken token); + + /// + /// Вставка записей статистики + /// + /// + /// + /// + Task InsertRangeAsync(IEnumerable dataSaubStats, CancellationToken token); + } +} diff --git a/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs b/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs index d69b185e..bc942353 100644 --- a/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs +++ b/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs @@ -1,4 +1,5 @@ -using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Data; +using AsbCloudApp.Data.SAUB; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudDb.Model; @@ -29,6 +30,7 @@ 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>(); var cacheRequest = new TelemetryDataRequest() @@ -40,26 +42,19 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks if (!idTelemetries.Any()) return; - var stats = await db.Set() - .Where(s => idTelemetries.Contains(s.IdTelemetry)) - .GroupBy(s => s.IdTelemetry) - .Select(g => new - { - IdTelemetry = g.Key, - DateEnd = g.Max(s => s.DateEnd), - }) - .ToArrayAsync(token); + var dataSaubStatRepo = 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, token); + var statsCount = await CreateStatForTelemetryFromDate(db, idTelemetry, lastDate, dataSaubStatRepo, 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, CancellationToken token) + private async Task CreateStatForTelemetryFromDate(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset begin, IDataSaubStatRepository dataSaubStatRepo, CancellationToken token) { var detectedOperations = await db.Set() .Where(o => o.IdTelemetry == idTelemetry) @@ -89,15 +84,14 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks var dataSaubStats = CreateDataSaubStat(detectedOperations, telemetryDataSaub); - db.Set().AddRange(dataSaubStats); - return await db.SaveChangesAsync(token); + return await dataSaubStatRepo.InsertRangeAsync(dataSaubStats, token); } - private static IEnumerable CreateDataSaubStat(IEnumerable detectedOperations, TelemetryDataSaub[] telemetryDataSaub) + private static IEnumerable CreateDataSaubStat(IEnumerable detectedOperations, TelemetryDataSaub[] telemetryDataSaub) { var indexStart = 0; var indexEnd = 0; - var result = new List(); + var result = new List(); if (!telemetryDataSaub.Any()) return result; @@ -125,9 +119,9 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks return result; } - private static IEnumerable CalcStats(DetectedOperation operation, Span telemetryDataSaub) + private static IEnumerable CalcStats(DetectedOperation operation, Span telemetryDataSaub) { - var result = new List(); + var result = new List(); var indexStart = 0; for (var i = 1; i < telemetryDataSaub.Length; i++) @@ -150,13 +144,13 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks return result; } - private static DataSaubStat CalcStat(DetectedOperation operation, Span span) + private static DataSaubStatDto CalcStat(DetectedOperation operation, Span span) { var hasOscillation = operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyHasOscillation, out object? hasOscillationObject) && hasOscillationObject is true; var aggregatedValues = CalcAggregate(span); - var processMapDrillingCacheItem = new DataSaubStat + var processMapDrillingCacheItem = new DataSaubStatDto { DateStart = operation.DateStart, DateEnd = operation.DateEnd, diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 28a051ab..5b131299 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -222,6 +222,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient< IChangeLogRepository, diff --git a/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs b/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs new file mode 100644 index 00000000..92a228d8 --- /dev/null +++ b/AsbCloudInfrastructure/Repository/DataSaubStatRepository.cs @@ -0,0 +1,68 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services; +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 DataSaubStatRepository : IDataSaubStatRepository + { + private readonly IAsbCloudDbContext db; + private readonly ITelemetryService telemetryService; + + public DataSaubStatRepository(IAsbCloudDbContext dbContext, ITelemetryService telemetryService) + { + db = dbContext; + this.telemetryService = telemetryService; + + } + + public async Task> GetLastsAsync(int[] idTelemetries, CancellationToken token) + { + var timeZoneOffsets = idTelemetries + .Distinct() + .ToDictionary(idTelemetry => idTelemetry, idTelemetry => TimeSpan.FromHours(telemetryService.GetTimezone(idTelemetry).Hours)); + + var stats = await db.Set() + .Where(s => idTelemetries.Contains(s.IdTelemetry)) + .GroupBy(s => s.IdTelemetry, (key, group) => group.OrderByDescending(el => el.DateEnd).First()) + .ToArrayAsync(token); + + var result = stats.Select(s => ConvertToDto(s, timeZoneOffsets[s.IdTelemetry])); + + return result; + } + + public async Task InsertRangeAsync(IEnumerable dataSaubStats, CancellationToken token) + { + var entities = dataSaubStats.Select(data => ConvertToEntity(data)); + db.Set().AddRange(entities); + return await db.SaveChangesAsync(token); + } + + private static DataSaubStatDto ConvertToDto(DataSaubStat entity, TimeSpan timeSpan) + { + var dto = entity.Adapt(); + dto.DateStart = dto.DateStart.ToOffset(timeSpan); + dto.DateEnd = dto.DateEnd.ToOffset(timeSpan); + + return dto; + } + + private static DataSaubStat ConvertToEntity(DataSaubStatDto dto) + { + var entity = dto.Adapt(); + entity.DateStart = dto.DateStart.ToUniversalTime(); + entity.DateEnd = dto.DateEnd.ToUniversalTime(); + + return entity; + } + } +} diff --git a/AsbCloudWebApi.IntegrationTests/Repository/DataSaubStatRepositoryTest.cs b/AsbCloudWebApi.IntegrationTests/Repository/DataSaubStatRepositoryTest.cs new file mode 100644 index 00000000..f44d32ed --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Repository/DataSaubStatRepositoryTest.cs @@ -0,0 +1,165 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudDb.Model; +using Mapster; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace AsbCloudWebApi.IntegrationTests.Repository; + +public class DataSaubStatRepositoryTest : BaseIntegrationTest +{ + private static readonly TimeSpan timeSpan = TimeSpan.FromHours(1); + + private static readonly DataSaubStatDto[] statDtos = new DataSaubStatDto[2] + { + new() + { + IdTelemetry = 1, + DateEnd = new DateTimeOffset(2024, 1, 1, 20, 25, 0, timeSpan), + DateStart = new DateTimeOffset(2024, 1, 1, 20, 15, 0, timeSpan), + AxialLoad = 10.0, + AxialLoadLimitMax = 10.0, + AxialLoadSp = 10.0, + BlockSpeedSp = 1000, + DepthEnd = 10.0, + DepthStart = 5.0, + EnabledSubsystems = 1, + Flow = 10.0, + HasOscillation = true, + Id = default, + IdCategory = 2, + IdFeedRegulator = 1, + Pressure = 10.0, + PressureIdle = 10.0, + PressureSp = 10.0, + RotorSpeed = 9.0, + RotorTorque = 9.0, + RotorTorqueSp = 9.0, + RotorTorqueLimitMax = 9.0, + Speed = 10.0 + }, + new() + { + IdTelemetry = 1, + DateEnd = new DateTimeOffset(2024, 2, 2, 20, 25, 0, timeSpan), + DateStart = new DateTimeOffset(2024, 2, 2, 20, 15, 0, timeSpan), + AxialLoad = 10.0, + AxialLoadLimitMax = 10.0, + AxialLoadSp = 10.0, + BlockSpeedSp = 1000, + DepthEnd = 10.0, + DepthStart = 5.0, + EnabledSubsystems = 1, + Flow = 10.0, + HasOscillation = true, + Id = default, + IdCategory = 2, + IdFeedRegulator = 1, + Pressure = 10.0, + PressureIdle = 10.0, + PressureSp = 10.0, + RotorSpeed = 10.0, + RotorTorque = 10.0, + RotorTorqueSp = 10.0, + RotorTorqueLimitMax = 10.0, + Speed = 10.0 + } + }; + private static readonly WellOperationCategory category = new() + { + Id = 2, + IdParent = null, + Name = "Категория 2" + }; + + private readonly IDataSaubStatRepository dataSaubStatRepository; + + public DataSaubStatRepositoryTest(WebAppFactoryFixture factory) : base(factory) + { + dataSaubStatRepository = scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task GetLastDatesAsync_returns_success() + { + //prepare + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + + var dbSetSaubStat = dbContext.Set(); + var dbSetCategories = dbContext.Set(); + + var entities = statDtos.Select(stat => ConvertToEntity(stat)); + + dbSetCategories.Add(category); + dbContext.SaveChanges(); + + dbSetSaubStat.AddRange(entities); + dbContext.SaveChanges(); + + //act + var telemetryIds = statDtos.Select(stat => stat.IdTelemetry).ToArray(); + var result = await dataSaubStatRepository.GetLastsAsync(telemetryIds, CancellationToken.None); + + var expected = statDtos.Max(stat => stat.DateEnd); + var actual = result.First().DateEnd; + + //assert + Assert.True((expected - actual).Ticks == 0.0); + } + + [Fact] + public async Task InsertRangeAsync_returns_success() + { + //prepare + dbContext.CleanupDbSet(); + var dbSet = dbContext.Set(); + + var dbSetCategories = dbContext.Set(); + dbSetCategories.Add(category); + + dbContext.SaveChanges(); + + //act + var result = await dataSaubStatRepository.InsertRangeAsync(statDtos, CancellationToken.None); + + //assert + Assert.Equal(statDtos.Length, result); + + var statDtosFromDb = dbSet.Select(stat => ConvertToDto(stat, timeSpan)).ToArray(); + + var excludedProps = new[] { + nameof(DataSaubStat.Telemetry), + nameof(DataSaubStat.Id), + nameof(DataSaubStat.OperationCategory) + }; + foreach (var statDtoFromDb in statDtosFromDb) + { + var statDto = statDtos + .Where(stat => stat.DateStart == statDtoFromDb.DateStart) + .Where(stat => stat.DateEnd == statDtoFromDb.DateEnd) + .FirstOrDefault(); + + MatchHelper.Match(statDtoFromDb, statDto, excludedProps); + } + } + + private static DataSaubStat ConvertToEntity(DataSaubStatDto stat) + { + var entity = stat.Adapt(); + entity.DateStart = entity.DateStart.ToUniversalTime(); + entity.DateEnd = entity.DateEnd.ToUniversalTime(); + + return entity; + } + + private static DataSaubStatDto ConvertToDto(DataSaubStat stat, TimeSpan timeSpan) + { + var dto = stat.Adapt(); + dto.DateStart = dto.DateStart.ToOffset(timeSpan); + dto.DateEnd = dto.DateEnd.ToOffset(timeSpan); + + return dto; + } +}