diff --git a/AsbCloudApp/Data/BackgroundWorkDto.cs b/AsbCloudApp/Data/BackgroundWorkDto.cs new file mode 100644 index 00000000..b2a3d161 --- /dev/null +++ b/AsbCloudApp/Data/BackgroundWorkDto.cs @@ -0,0 +1,206 @@ +using System; +using System.Diagnostics; + +namespace AsbCloudApp.Data +{ + /// + /// Информация о фоновой работе + /// + public class BackgroundWorkDto + { + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. + /// + public string Id { get; init; } = null!; + + /// + /// Класс описания состояния + /// + public class CurrentStateInfo + { + private string state = "start"; + + /// + /// Время последнего запуска + /// + public DateTime Start { get; } = DateTime.Now; + + /// + /// Текущее время выполнения + /// + public TimeSpan ExecutionTime => DateTime.Now - Start; + + /// + /// Текстовое описание того, что происходит в задаче. + /// + public string State + { + get => state; + internal set + { + state = value; + StateUpdate = DateTime.Now; + } + } + + /// + /// Прогресс + /// + public double Progress { get; internal set; } = 0; + + /// + /// Время последнего запуска + /// + public DateTime StateUpdate { get; private set; } = DateTime.Now; + } + + /// + /// Инфо о последней ошибке + /// + public class LastErrorInfo : LastCompleteInfo + { + /// + /// + /// + /// + /// + public LastErrorInfo(CurrentStateInfo state, string errorText) + : base(state) + { + ErrorText = errorText; + } + + /// + /// Последняя ошибка + /// + public string ErrorText { get; init; } = null!; + } + + /// + /// Инфо о последнем завершении + /// + public class LastCompleteInfo + { + /// + /// Дата запуска + /// + public DateTime Start { get; init; } + + /// + /// Дата завершения + /// + public DateTime End { get; init; } + + /// + /// Продолжительность последнего выполнения + /// + public TimeSpan ExecutionTime => End - Start; + + /// + /// Состояние на момент завершения + /// + public string State { get; init; } + + /// + /// ctor + /// + /// + public LastCompleteInfo(CurrentStateInfo state) + { + Start = state.Start; + End = DateTime.Now; + State = state.State; + } + } + + /// + /// Текущее состояние + /// + public CurrentStateInfo? CurrentState { get; private set; } + + /// + /// Последняя ошибка + /// + public LastErrorInfo? LastError { get; private set; } + + /// + /// Последняя завершенная + /// + public LastCompleteInfo? LastComplete { get; private set; } + + /// + /// Кол-во запусков + /// + public int CountStart { get; private set; } + + /// + /// Кол-во завершений + /// + public int CountComplete { get; private set; } + + /// + /// Кол-во ошибок + /// + public int CountErrors { get; private set; } + + /// + /// Максимально допустимое время выполнения работы + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); + + private string WorkNameForTrace => $"Backgroud work:\"{Id}\""; + + /// + /// Обновления состояния при запуске работы + /// + protected void SetStatusStart() + { + CurrentState = new(); + CountStart++; + Trace.TraceInformation($"{WorkNameForTrace} state: starting"); + } + + /// + /// Обновления состояния в процессе работы + /// + protected void UpdateStatus(string newState, double? progress) + { + if (CurrentState is null) + return; + + CurrentState.State = newState; + if (progress.HasValue) + CurrentState.Progress = progress.Value; + + Trace.TraceInformation($"{WorkNameForTrace} state: {newState}"); + } + + /// + /// Обновления состояния при успешном завершении работы + /// + protected void SetStatusComplete() + { + if (CurrentState is null) + return; + + LastComplete = new(CurrentState); + CurrentState = null; + CountComplete++; + Trace.TraceInformation($"{WorkNameForTrace} state: completed"); + } + + /// + /// Обновления состояния при ошибке в работе + /// + protected void SetLastError(string errorMessage) + { + if (CurrentState is null) + return; + + LastError = new LastErrorInfo(CurrentState, errorMessage); + CurrentState = null; + CountErrors++; + Trace.TraceError($"{WorkNameForTrace} throw exception[{CountErrors}]: {errorMessage}"); + } + } +} \ No newline at end of file diff --git a/AsbCloudApp/Data/SectionByOperationsDto.cs b/AsbCloudApp/Data/SectionByOperationsDto.cs new file mode 100644 index 00000000..80d2a3b4 --- /dev/null +++ b/AsbCloudApp/Data/SectionByOperationsDto.cs @@ -0,0 +1,47 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace AsbCloudApp.Data; + +/// +/// Параметры секции определяемые по операциям из ГГД +/// +public class SectionByOperationsDto +{ + /// + /// Id скважины + /// + public int IdWell { get; set; } + + /// + /// 0 = план или 1 = факт или прогноз = 2 + /// + public int IdType { get; set; } + + /// + /// id секции скважины + /// + public int IdWellSectionType { get; set; } + + /// + /// Глубина начала первой операции в секции, м + /// + [Range(0, 50_000)] + public double DepthStart { get; set; } + + /// + /// Дата начала первой операции в секции + /// + public DateTimeOffset DateStart { get; set; } + + /// + /// Глубина после завершения последней операции операции в секции, м + /// + [Range(0, 50_000)] + public double DepthEnd { get; set; } + + /// + /// Дата после завершения последней операции операции в секции + /// + public DateTimeOffset DateEnd { get; set; } +} diff --git a/AsbCloudApp/Data/WellboreDto.cs b/AsbCloudApp/Data/WellboreDto.cs index dbd9b697..c153b14f 100644 --- a/AsbCloudApp/Data/WellboreDto.cs +++ b/AsbCloudApp/Data/WellboreDto.cs @@ -7,7 +7,10 @@ namespace AsbCloudApp.Data; /// public class WellboreDto { - public WellWithTimezoneDto Well { get; set; } + /// + /// Скважина + /// + public WellWithTimezoneDto Well { get; set; } = null!; /// /// Идентификатор diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index b02d3e42..faab324f 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -97,5 +97,13 @@ namespace AsbCloudApp.Repositories /// /// Task DeleteAsync(IEnumerable ids, CancellationToken token); + + /// + /// Получить секции скважин из операций ГГД. Секцие поделены на плановые и фактические. + /// + /// + /// + /// + Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token); } } \ No newline at end of file diff --git a/AsbCloudApp/Services/Notifications/NotificationService.cs b/AsbCloudApp/Services/Notifications/NotificationService.cs index 85c521e3..4537f04e 100644 --- a/AsbCloudApp/Services/Notifications/NotificationService.cs +++ b/AsbCloudApp/Services/Notifications/NotificationService.cs @@ -90,7 +90,6 @@ public class NotificationService /// Отправка уведомлений, которые не были отправлены /// /// - /// /// /// public async Task RenotifyAsync(int idUser, CancellationToken cancellationToken) diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs index 7c88fade..cacb5a60 100644 --- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs +++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs @@ -1,96 +1,49 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Background +namespace AsbCloudInfrastructure.Background; + +/// +/// Сервис для фонового выполнения работы +/// +public class BackgroundWorker : BackgroundService { - /// - /// Сервис для фонового выполнения работы - /// - public class BackgroundWorker : BackgroundService + private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); + private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); + private readonly IServiceProvider serviceProvider; + + public WorkStore WorkStore { get; } = new WorkStore(); + public Work? CurrentWork; + + public BackgroundWorker(IServiceProvider serviceProvider) { - private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); - private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); - private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2); - private readonly IServiceProvider serviceProvider; - private readonly WorkQueue workQueue = new WorkQueue(); - public string? CurrentWorkId; - public BackgroundWorker(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - } + this.serviceProvider = serviceProvider; + } - /// - /// Добавление задачи в очередь. - /// Не периодические задачи будут выполняться вперед. - /// - /// - /// Id mast be unique - public void Push(WorkBase work) + protected override async Task ExecuteAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) { - workQueue.Push(work); - } - - /// - /// Проверяет наличие работы с указанным Id - /// - /// - /// - public bool Contains(string id) - { - return workQueue.Contains(id); - } - - /// - /// Удаление работы по ID - /// - /// - /// - public bool Delete(string id) - { - return workQueue.Delete(id); - } - - protected override async Task ExecuteAsync(CancellationToken token) - { - while (!token.IsCancellationRequested) + var work = WorkStore.GetNext(); + if (work is null) { - var dateStart = DateTime.Now; - var work = workQueue.Pop(); - if (work is null) - { - await Task.Delay(executePeriod, token); - continue; - } - CurrentWorkId = work.Id; - using var scope = serviceProvider.CreateScope(); - - try - { - Trace.TraceInformation($"Backgroud work:\"{work.Id}\" start."); - var task = work.ActionAsync(work.Id, scope.ServiceProvider, token); - await task.WaitAsync(work.Timeout, token); - - work.ExecutionTime = DateTime.Now - dateStart; - Trace.TraceInformation($"Backgroud work:\"{work.Id}\" done. ExecutionTime: {work.ExecutionTime:hh\\:mm\\:ss\\.fff}"); - } - catch (Exception exception) - { - Trace.TraceError($"Backgroud work:\"{work.Id}\" throw exception: {exception.Message}"); - if (work.OnErrorAsync is not null) - { - using var task = Task.Run( - async () => await work.OnErrorAsync(work.Id, exception, token), - token); - await task.WaitAsync(exceptionHandleTimeout, token); - } - } - CurrentWorkId = null; - await Task.Delay(minDelay, token); + await Task.Delay(executePeriod, token); + continue; } + + CurrentWork = work; + using var scope = serviceProvider.CreateScope(); + + var result = await work.Start(scope.ServiceProvider, token); + + if (!result) + WorkStore.Felled.Add(work); + + CurrentWork = null; + await Task.Delay(minDelay, token); } } } diff --git a/AsbCloudInfrastructure/Background/OrderedList.cs b/AsbCloudInfrastructure/Background/OrderedList.cs new file mode 100644 index 00000000..1f583a82 --- /dev/null +++ b/AsbCloudInfrastructure/Background/OrderedList.cs @@ -0,0 +1,41 @@ +using System.Linq; + +namespace System.Collections.Generic +{ + public class OrderedList: IEnumerable, ICollection + where T : notnull + { + private readonly List list = new List(); + + private readonly Func keySelector; + private readonly bool isDescending = false; + + private IOrderedEnumerable OrdredList => isDescending + ? list.OrderByDescending(keySelector) + : list.OrderBy(keySelector); + + public int Count => list.Count; + + public bool IsReadOnly => false; + + public OrderedList(Func keySelector, bool isDescending = false) + { + this.keySelector = keySelector; + this.isDescending = isDescending; + } + + public void Add(T item) => list.Add(item); + + public void Clear()=> list.Clear(); + + public bool Contains(T item)=> list.Contains(item); + + public void CopyTo(T[] array, int arrayIndex)=> list.CopyTo(array, arrayIndex); + + public bool Remove(T item)=> list.Remove(item); + + public IEnumerator GetEnumerator() => OrdredList.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/AsbCloudInfrastructure/Background/Work.cs b/AsbCloudInfrastructure/Background/Work.cs new file mode 100644 index 00000000..9372343b --- /dev/null +++ b/AsbCloudInfrastructure/Background/Work.cs @@ -0,0 +1,98 @@ +using AsbCloudApp.Data; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Background; + +/// +/// Класс разовой работы. +/// Разовая работа приоритетнее периодической. +/// +public abstract class Work : BackgroundWorkDto +{ + private sealed class WorkBase : Work + { + private Func, CancellationToken, Task> ActionAsync { get; } + public WorkBase(string id, Func, CancellationToken, Task> actionAsync) + : base(id) + { + ActionAsync = actionAsync; + } + + protected override Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) + => ActionAsync(id, services, onProgressCallback, token); + } + + /// + /// Делегат обработки ошибки. + /// Не должен выполняться долго. + /// + public Func? OnErrorAsync { get; set; } + + /// + /// макс продолжительность обработки исключения + /// + public TimeSpan OnErrorHandlerTimeout { get; set; } = TimeSpan.FromSeconds(5); + + /// + /// Базовая работа + /// + /// + public Work(string id) + { + Id = id; + } + + /// + /// Создать работу на основе делегата + /// + /// + /// + /// + [Obsolete("Use implement Work class")] + public static Work CreateByDelegate(string id, Func, CancellationToken, Task> actionAsync) + { + return new WorkBase(id, actionAsync); + } + + /// + /// Запустить работу + /// + /// + /// + /// True - success, False = fail + public async Task Start(IServiceProvider services, CancellationToken token) + { + SetStatusStart(); + try + { + var task = Action(Id, services, UpdateStatus, token); + await task.WaitAsync(Timeout, token); + SetStatusComplete(); + return true; + } + catch (Exception exception) + { + SetLastError(exception.Message); + if (OnErrorAsync is not null) + { + var task = Task.Run( + async () => await OnErrorAsync(Id, exception, token), + token); + await task.WaitAsync(OnErrorHandlerTimeout, token); + } + } + return false; + } + + /// + /// делегат фоновой работы + /// + /// Идентификатор работы + /// Поставщик сервисов + /// on progress callback. String - new state text. double? - optional progress 0-100% + /// + /// + protected abstract Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token); +} diff --git a/AsbCloudInfrastructure/Background/WorkBase.cs b/AsbCloudInfrastructure/Background/WorkBase.cs deleted file mode 100644 index e8adf04c..00000000 --- a/AsbCloudInfrastructure/Background/WorkBase.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Background -{ - - /// - /// Класс разовой работы. - /// Разовая работа приоритетнее периодической. - /// - public class WorkBase - { - /// - /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. - /// - public string Id { get; private set; } - - /// - /// Делегат работы. - /// - /// Параметры: - /// - /// - /// string - /// Id Идентификатор работы - /// - /// - /// IServiceProvider - /// Поставщик сервисов - /// - /// - /// CancellationToken - /// Токен отмены задачи - /// - /// - /// - /// - internal Func ActionAsync { get; set; } - - /// - /// Делегат обработки ошибки. - /// Не должен выполняться долго. - /// - public Func? OnErrorAsync { get; set; } - - /// - /// максимально допустимое время выполнения работы - /// - public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); - - /// - /// Фактическое время успешного выполнения работы - /// - public TimeSpan? ExecutionTime { get; internal set; } - - /// - /// Время последнего запуска - /// - public DateTime LastStart { get; set; } - - public WorkBase(string id, Func actionAsync) - { - Id = id; - ActionAsync = actionAsync; - } - } - -} diff --git a/AsbCloudInfrastructure/Background/WorkPeriodic.cs b/AsbCloudInfrastructure/Background/WorkPeriodic.cs index cbd34fec..4cd22af6 100644 --- a/AsbCloudInfrastructure/Background/WorkPeriodic.cs +++ b/AsbCloudInfrastructure/Background/WorkPeriodic.cs @@ -1,36 +1,42 @@ using System; -using System.Threading; -using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Background +namespace AsbCloudInfrastructure.Background; + +/// +/// Класс периодической работы. +/// +public class WorkPeriodic { + public Work Work { get; } /// - /// Класс периодической работы. + /// Период выполнения задачи /// - public class WorkPeriodic : WorkBase + public TimeSpan Period { get; set; } + + /// + /// Время следующего запуска + /// + public DateTime NextStart { - /// - /// Период выполнения задачи - /// - public TimeSpan Period { get; set; } - - /// - /// Время следующего запуска - /// - public DateTime NextStart => LastStart + Period; - - /// - /// Класс периодической работы - /// - /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки - /// Делегат работы - /// Период выполнения задачи - public WorkPeriodic(string id, Func actionAsync, TimeSpan period) - : base(id, actionAsync) + get { - Period = period; + var lastStart = Work.LastComplete?.Start ?? DateTime.MinValue; + if (Work.LastError?.Start > lastStart) + lastStart = Work.LastError.Start; + return lastStart + Period; } } + /// + /// Класс периодической работы + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки + /// Делегат работы + /// Период выполнения задачи + public WorkPeriodic(Work work, TimeSpan period) + { + Work = work; + Period = period; + } } diff --git a/AsbCloudInfrastructure/Background/WorkQueue.cs b/AsbCloudInfrastructure/Background/WorkQueue.cs deleted file mode 100644 index ce77fa94..00000000 --- a/AsbCloudInfrastructure/Background/WorkQueue.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace AsbCloudInfrastructure.Background -{ - - /// - /// - /// Очередь работ - /// - /// Не периодические задачи будут возвращаться первыми, как самые приоритетные. - /// - class WorkQueue - { - private Queue Primary = new(8); - private readonly List Periodic = new(8); - - /// - /// Добавление работы. - /// - /// - /// Id mast be unique - public void Push(WorkBase work) - { - if (Periodic.Any(w => w.Id == work.Id)) - throw new ArgumentException("work.Id is not unique", nameof(work)); - - if (Primary.Any(w => w.Id == work.Id)) - throw new ArgumentException("work.Id is not unique", nameof(work)); - - if (work is WorkPeriodic workPeriodic) - { - Periodic.Add(workPeriodic); - return; - } - - Primary.Enqueue(work); - } - - /// - /// Удаление работы по ID - /// - /// - /// - public bool Delete(string id) - { - var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id); - if (workPeriodic is not null) - { - Periodic.Remove(workPeriodic); - return true; - } - - var work = Primary.FirstOrDefault(w => w.Id == id); - if (work is not null) - { - Primary = new Queue(Primary.Where(w => w.Id != id)); - return true; - } - - return false; - } - - public bool Contains(string id) - { - var result = Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id); - return result; - } - - /// - /// - /// Возвращает приоритетную задачу. - /// - /// - /// Если приоритетные закончились, то ищет ближайшую периодическую. - /// Если до старта ближайшей периодической работы меньше 20 сек, - /// то этой задаче устанавливается время последнего запуска в now и она возвращается. - /// Если больше 20 сек, то возвращается null. - /// - /// - /// - /// - public WorkBase? Pop() - { - if (Primary.Any()) - return Primary.Dequeue(); - - var work = GetNextPeriodic(); - if (work is null || work.NextStart > DateTime.Now) - return null; - - work.LastStart = DateTime.Now; - return work; - } - - private WorkPeriodic? GetNextPeriodic() - { - var work = Periodic - .OrderBy(w => w.NextStart) - .ThenByDescending(w => w.Period) - .FirstOrDefault(); - return work; - } - } - -} diff --git a/AsbCloudInfrastructure/Background/WorkStore.cs b/AsbCloudInfrastructure/Background/WorkStore.cs new file mode 100644 index 00000000..b57fc104 --- /dev/null +++ b/AsbCloudInfrastructure/Background/WorkStore.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudInfrastructure.Background; + +/// +/// +/// Очередь работ +/// +/// Не периодические задачи будут возвращаться первыми, как самые приоритетные. +/// +public class WorkStore +{ + private readonly List periodics = new(8); + + /// + /// Список периодических задач + /// + public IEnumerable Periodics => periodics; + + /// + /// Работы выполняемые один раз + /// + public Queue RunOnceQueue { get; private set; } = new(8); + + /// + /// Завершившиеся с ошибкой + /// + public CyclycArray Felled { get; } = new(16); + + /// + /// Добавить фоновую работу выполняющуюся с заданным периодом + /// + /// + /// + public void AddPeriodic(TimeSpan period) + where T : Work, new() + { + var work = new T(); + var periodic = new WorkPeriodic(work, period); + periodics.Add(periodic); + } + + /// + /// Добавить фоновую работу выполняющуюся с заданным периодом + /// + /// + /// + public void AddPeriodic(Work work, TimeSpan period) + { + var periodic = new WorkPeriodic(work, period); + periodics.Add(periodic); + } + + /// + /// Удаление работы по ID из одноразовой очереди + /// + /// + /// + public bool TryRemoveFromRunOnceQueue(string id) + { + var work = RunOnceQueue.FirstOrDefault(w => w.Id == id); + if (work is not null) + { + RunOnceQueue = new Queue(RunOnceQueue.Where(w => w.Id != id)); + return true; + } + + return false; + } + + /// + /// + /// Возвращает приоритетную задачу. + /// + /// + /// Если приоритетные закончились, то ищет ближайшую периодическую. + /// Если до старта ближайшей периодической работы меньше 20 сек, + /// то этой задаче устанавливается время последнего запуска в now и она возвращается. + /// Если больше 20 сек, то возвращается null. + /// + /// + /// + /// + public Work? GetNext() + { + if (RunOnceQueue.Any()) + return RunOnceQueue.Dequeue(); + + var work = GetNextPeriodic(); + if (work is null || work.NextStart > DateTime.Now) + return null; + + return work.Work; + } + + private WorkPeriodic? GetNextPeriodic() + { + var work = Periodics + .OrderBy(w => w.NextStart) + .FirstOrDefault(); + return work; + } +} diff --git a/AsbCloudInfrastructure/Background/todo.md b/AsbCloudInfrastructure/Background/todo.md new file mode 100644 index 00000000..b8b30852 --- /dev/null +++ b/AsbCloudInfrastructure/Background/todo.md @@ -0,0 +1,12 @@ +# +- . + - , + - , + - , +- . . + - / + - / + +# +- dto +- , . diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index f0eb5f6d..cd913980 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -13,394 +13,452 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Repository +namespace AsbCloudInfrastructure.Repository; + + +/// +/// репозиторий операций по скважине +/// +public class WellOperationRepository : IWellOperationRepository { + private const string KeyCacheSections = "OperationsBySectionSummarties"; + private readonly IAsbCloudDbContext db; + private readonly IMemoryCache memoryCache; + private readonly IWellService wellService; - /// - /// репозиторий операций по скважине - /// - public class WellOperationRepository : IWellOperationRepository + public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService) { - private readonly IAsbCloudDbContext db; - private readonly IMemoryCache memoryCache; - private readonly IWellService wellService; - private static Dictionary? firstOperationsCache = null; - - public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService) - { - this.db = db; - this.memoryCache = memoryCache; - this.wellService = wellService; - } - - /// - 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; - } - - /// - public IEnumerable GetSectionTypes() => - memoryCache - .GetOrCreateBasic(db.Set()) - .OrderBy(s => s.Order) - .Select(s => s.Adapt()); - - - - public async Task GetOperationsPlanAsync(int idWell, DateTime? currentDate, CancellationToken token) - { - var timezone = wellService.GetTimezone(idWell); - var request = new WellOperationRequest() - { - IdWell = idWell, - OperationType = WellOperation.IdOperationTypePlan, - }; - - var entities = await BuildQuery(request) - .AsNoTracking() - .ToArrayAsync(token) - .ConfigureAwait(false); - - var dateLastAssosiatedPlanOperation = await GetDateLastAssosiatedPlanOperationAsync(idWell, currentDate, timezone.Hours, token); - - var result = new WellOperationPlanDto() - { - WellOperationsPlan = entities, - DateLastAssosiatedPlanOperation = dateLastAssosiatedPlanOperation - }; - - return result; - } - - private async Task GetDateLastAssosiatedPlanOperationAsync( - int idWell, - DateTime? lessThenDate, - double timeZoneHours, - CancellationToken token) - { - if (lessThenDate is null) - return null; - - var currentDateOffset = lessThenDate.Value.ToUtcDateTimeOffset(timeZoneHours); - var timeZoneOffset = TimeSpan.FromHours(timeZoneHours); - - var lastFactOperation = await db.WellOperations - .Where(o => o.IdWell == idWell) - .Where(o => o.IdType == WellOperation.IdOperationTypeFact) - .Where(o => o.IdPlan != null) - .Where(o => o.DateStart < currentDateOffset) - .Include(x => x.OperationPlan) - .OrderByDescending(x => x.DateStart) - .FirstOrDefaultAsync(token) - .ConfigureAwait(false); - - if (lastFactOperation is not null) - return DateTime.SpecifyKind(lastFactOperation.OperationPlan.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified); - return null; - } - - /// - public DateTimeOffset? FirstOperationDate(int idWell) - { - if (firstOperationsCache is null) - { - var query = db.WellOperations - .GroupBy(o => o.IdWell) - .Select(g => new Tuple - ( - g.Key, - g.Where(o => o.IdType == WellOperation.IdOperationTypePlan).Min(o => o.DateStart), - g.Where(o => o.IdType == WellOperation.IdOperationTypeFact).Min(o => o.DateStart) - )); - - firstOperationsCache = query - .ToDictionary(f => f.Item1, f => f.Item3 ?? f.Item2); - } - - return firstOperationsCache?.GetValueOrDefault(idWell); - } - - /// - public async Task> GetAsync( - WellOperationRequest request, - CancellationToken token) - { - var query = BuildQuery(request) - .AsNoTracking(); - var result = await query.ToArrayAsync(token); - return result; - } - - /// - public async Task> GetPageAsync( - WellOperationRequest request, - CancellationToken token) - { - var query = BuildQuery(request) - .AsNoTracking(); - - var result = new PaginationContainer - { - Skip = request.Skip ?? 0, - Take = request.Take ?? 32, - Count = await query.CountAsync(token).ConfigureAwait(false), - }; - - query = query - .Skip(result.Skip) - .Take(result.Take); - - result.Items = await query.ToArrayAsync(token); - return result; - } - - /// - public async Task GetOrDefaultAsync(int id, - CancellationToken token) - { - var entity = await db.WellOperations - .Include(s => s.WellSectionType) - .Include(s => s.OperationCategory) - .FirstOrDefaultAsync(e => e.Id == id, token) - .ConfigureAwait(false); - - if (entity is null) - return null; - - var timezone = wellService.GetTimezone(entity.IdWell); - - var dto = entity.Adapt(); - dto.WellSectionTypeName = entity.WellSectionType.Caption; - dto.DateStart = entity.DateStart.ToRemoteDateTime(timezone.Hours); - dto.CategoryName = entity.OperationCategory.Name; - return dto; - } - - /// - public async Task> GetGroupOperationsStatAsync( - WellOperationRequest request, - CancellationToken token) - { - // TODO: Rename controller method - request.OperationType = WellOperation.IdOperationTypeFact; - var query = BuildQuery(request); - var entities = await query - .Select(o => new - { - o.IdCategory, - DurationMinutes = o.DurationHours * 60, - DurationDepth = o.DepthEnd - o.DepthStart - }) - .ToListAsync(token); - var parentRelationDictionary = GetCategories(true) - .ToDictionary(c => c.Id, c => new - { - c.Name, - c.IdParent - }); - - var dtos = entities - .GroupBy(o => o.IdCategory) - .Select(g => new WellGroupOpertionDto - { - IdCategory = g.Key, - Category = parentRelationDictionary[g.Key].Name, - Count = g.Count(), - MinutesAverage = g.Average(o => o.DurationMinutes), - MinutesMin = g.Min(o => o.DurationMinutes), - MinutesMax = g.Max(o => o.DurationMinutes), - TotalMinutes = g.Sum(o => o.DurationMinutes), - DeltaDepth = g.Sum(o => o.DurationDepth), - IdParent = parentRelationDictionary[g.Key].IdParent - }); - - while (dtos.All(x => x.IdParent != null)) - { - dtos = dtos - .GroupBy(o => o.IdParent!) - .Select(g => { - var idCategory = g.Key ?? int.MinValue; - var category = parentRelationDictionary.GetValueOrDefault(idCategory); - var newDto = new WellGroupOpertionDto - { - IdCategory = idCategory, - Category = category?.Name ?? "unknown", - Count = g.Sum(o => o.Count), - DeltaDepth = g.Sum(o => o.DeltaDepth), - TotalMinutes = g.Sum(o => o.TotalMinutes), - Items = g.ToList(), - IdParent = category?.IdParent, - }; - return newDto; - }); - } - return dtos; - } - - /// - public async Task InsertRangeAsync( - IEnumerable wellOperationDtos, - CancellationToken token) - { - var firstOperation = wellOperationDtos - .FirstOrDefault(); - if (firstOperation is null) - return 0; - - var idWell = firstOperation.IdWell; - - var timezone = wellService.GetTimezone(idWell); - foreach (var dto in wellOperationDtos) - { - var entity = dto.Adapt(); - entity.Id = default; - entity.DateStart = dto.DateStart.ToUtcDateTimeOffset(timezone.Hours); - entity.IdWell = idWell; - db.WellOperations.Add(entity); - } - - return await db.SaveChangesAsync(token) - .ConfigureAwait(false); - } - - /// - public async Task UpdateAsync( - WellOperationDto dto, CancellationToken token) - { - var timezone = wellService.GetTimezone(dto.IdWell); - var entity = dto.Adapt(); - entity.DateStart = dto.DateStart.ToUtcDateTimeOffset(timezone.Hours); - db.WellOperations.Update(entity); - return await db.SaveChangesAsync(token) - .ConfigureAwait(false); - } - - /// - public async Task DeleteAsync(IEnumerable ids, - CancellationToken token) - { - var query = db.WellOperations.Where(e => ids.Contains(e.Id)); - db.WellOperations.RemoveRange(query); - return await db.SaveChangesAsync(token) - .ConfigureAwait(false); - } - - /// - /// В результате попрежнему требуется конвертировать дату - /// - /// - /// - private IQueryable BuildQuery(WellOperationRequest request) - { - var timezone = wellService.GetTimezone(request.IdWell); - var timeZoneOffset = TimeSpan.FromHours(timezone.Hours); - - var query = db.WellOperations - .Include(s => s.WellSectionType) - .Include(s => s.OperationCategory) - .Where(o => o.IdWell == request.IdWell); - - - if (request.OperationType.HasValue) - query = query.Where(e => e.IdType == request.OperationType.Value); - - if (request.SectionTypeIds?.Any() == true) - query = query.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); - - if (request.OperationCategoryIds?.Any() == true) - query = query.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); - - if (request.GeDepth.HasValue) - query = query.Where(e => e.DepthEnd >= request.GeDepth.Value); - - if (request.LeDepth.HasValue) - query = query.Where(e => e.DepthEnd <= request.LeDepth.Value); - - if (request.GeDate.HasValue) - { - var geDateOffset = request.GeDate.Value.ToUtcDateTimeOffset(timezone.Hours); - query = query.Where(e => e.DateStart >= geDateOffset); - } - - if (request.LtDate.HasValue) - { - var ltDateOffset = request.LtDate.Value.ToUtcDateTimeOffset(timezone.Hours); - query = query.Where(e => e.DateStart < ltDateOffset); - } - - var currentWellOperations = db.WellOperations - .Where(subOp => subOp.IdWell == request.IdWell); - - var wellOperationsWithCategoryNPT = currentWellOperations - .Where(subOp => subOp.IdType == 1) - .Where(subOp => WellOperationCategory.NonProductiveTimeSubIds.Contains(subOp.IdCategory)); - - var result = query.Select(o => new WellOperationDto - { - Id = o.Id, - IdPlan = o.IdPlan, - IdType = o.IdType, - IdWell = o.IdWell, - IdWellSectionType = o.IdWellSectionType, - IdCategory = o.IdCategory, - IdParentCategory = o.OperationCategory.IdParent, - - CategoryName = o.OperationCategory.Name, - WellSectionTypeName = o.WellSectionType.Caption, - - DateStart = DateTime.SpecifyKind(o.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified), - DepthStart = o.DepthStart, - DepthEnd = o.DepthEnd, - DurationHours = o.DurationHours, - CategoryInfo = o.CategoryInfo, - Comment = o.Comment, - - NptHours = wellOperationsWithCategoryNPT - .Where(subOp => subOp.DateStart <= o.DateStart) - .Select(subOp => subOp.DurationHours) - .Sum(), - - Day = (o.DateStart - currentWellOperations - .Where(subOp => subOp.IdType == o.IdType) - .Where(subOp => subOp.DateStart <= o.DateStart) - .Min(subOp => subOp.DateStart)) - .TotalDays, - IdUser = o.IdUser, - LastUpdateDate = o.LastUpdateDate.ToOffset(TimeSpan.FromHours(timezone.Hours)) - }); - - if (request.SortFields?.Any() == true) - { - result = result.SortBy(request.SortFields); - } - else - { - result = result - .OrderBy(e => e.DateStart) - .ThenBy(e => e.DepthEnd) - .ThenBy(e => e.Id); - }; - - return result; - } + this.db = db; + this.memoryCache = memoryCache; + this.wellService = wellService; } + /// + 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; + } + + /// + public IEnumerable GetSectionTypes() => + memoryCache + .GetOrCreateBasic(db.Set()) + .OrderBy(s => s.Order) + .Select(s => s.Adapt()); + + public async Task GetOperationsPlanAsync(int idWell, DateTime? currentDate, CancellationToken token) + { + var timezone = wellService.GetTimezone(idWell); + var request = new WellOperationRequest() + { + IdWell = idWell, + OperationType = WellOperation.IdOperationTypePlan, + }; + + var entities = await BuildQuery(request) + .AsNoTracking() + .ToArrayAsync(token) + .ConfigureAwait(false); + + var dateLastAssosiatedPlanOperation = await GetDateLastAssosiatedPlanOperationAsync(idWell, currentDate, timezone.Hours, token); + + var result = new WellOperationPlanDto() + { + WellOperationsPlan = entities, + DateLastAssosiatedPlanOperation = dateLastAssosiatedPlanOperation + }; + + return result; + } + + private async Task GetDateLastAssosiatedPlanOperationAsync( + int idWell, + DateTime? lessThenDate, + double timeZoneHours, + CancellationToken token) + { + if (lessThenDate is null) + return null; + + var currentDateOffset = lessThenDate.Value.ToUtcDateTimeOffset(timeZoneHours); + var timeZoneOffset = TimeSpan.FromHours(timeZoneHours); + + var lastFactOperation = await db.WellOperations + .Where(o => o.IdWell == idWell) + .Where(o => o.IdType == WellOperation.IdOperationTypeFact) + .Where(o => o.IdPlan != null) + .Where(o => o.DateStart < currentDateOffset) + .Include(x => x.OperationPlan) + .OrderByDescending(x => x.DateStart) + .FirstOrDefaultAsync(token) + .ConfigureAwait(false); + + if (lastFactOperation is not null) + return DateTime.SpecifyKind(lastFactOperation.OperationPlan.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified); + return null; + } + + /// + public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) + { + var cache = await memoryCache.GetOrCreateAsync(KeyCacheSections, async (entry) => + { + entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); + + var query = db.Set() + .GroupBy(operation => new + { + operation.IdWell, + operation.IdType, + operation.IdWellSectionType, + }) + .Select(group => new + { + group.Key.IdWell, + group.Key.IdType, + group.Key.IdWellSectionType, + + First = group + .OrderBy(operation => operation.DateStart) + .Select(operation => new + { + operation.DateStart, + operation.DepthStart, + }) + .First(), + + Last = group + .OrderByDescending(operation => operation.DateStart) + .Select(operation => new + { + operation.DateStart, + operation.DurationHours, + operation.DepthEnd, + }) + .First(), + }); + var dbData = await query.ToArrayAsync(token); + var sections = dbData.Select( + item => new SectionByOperationsDto + { + IdWell = item.IdWell, + IdType = item.IdType, + IdWellSectionType = item.IdWellSectionType, + + DateStart = item.First.DateStart, + DepthStart = item.First.DepthStart, + + DateEnd = item.Last.DateStart.AddHours(item.Last.DurationHours), + DepthEnd = item.Last.DepthEnd, + }) + .ToArray() + .AsEnumerable(); + + entry.Value = sections; + return sections; + }); + + var sections = cache.Where(s => idsWells.Contains(s.IdWell)); + return sections; + } + + /// + public DateTimeOffset? FirstOperationDate(int idWell) + { + var sections = GetSectionsAsync(new[] { idWell }, CancellationToken.None).Result; + var first = sections.FirstOrDefault(section => section.IdType == WellOperation.IdOperationTypeFact) + ?? sections.FirstOrDefault(section => section.IdType == WellOperation.IdOperationTypePlan); + + return first?.DateStart; + } + + /// + public async Task> GetAsync( + WellOperationRequest request, + CancellationToken token) + { + var query = BuildQuery(request) + .AsNoTracking(); + var result = await query.ToArrayAsync(token); + return result; + } + + /// + public async Task> GetPageAsync( + WellOperationRequest request, + CancellationToken token) + { + var query = BuildQuery(request) + .AsNoTracking(); + + var result = new PaginationContainer + { + Skip = request.Skip ?? 0, + Take = request.Take ?? 32, + Count = await query.CountAsync(token).ConfigureAwait(false), + }; + + query = query + .Skip(result.Skip) + .Take(result.Take); + + result.Items = await query.ToArrayAsync(token); + return result; + } + + /// + public async Task GetOrDefaultAsync(int id, + CancellationToken token) + { + var entity = await db.WellOperations + .Include(s => s.WellSectionType) + .Include(s => s.OperationCategory) + .FirstOrDefaultAsync(e => e.Id == id, token) + .ConfigureAwait(false); + + if (entity is null) + return null; + + var timezone = wellService.GetTimezone(entity.IdWell); + + var dto = entity.Adapt(); + dto.WellSectionTypeName = entity.WellSectionType.Caption; + dto.DateStart = entity.DateStart.ToRemoteDateTime(timezone.Hours); + dto.CategoryName = entity.OperationCategory.Name; + return dto; + } + + /// + public async Task> GetGroupOperationsStatAsync( + WellOperationRequest request, + CancellationToken token) + { + // TODO: Rename controller method + request.OperationType = WellOperation.IdOperationTypeFact; + var query = BuildQuery(request); + var entities = await query + .Select(o => new + { + o.IdCategory, + DurationMinutes = o.DurationHours * 60, + DurationDepth = o.DepthEnd - o.DepthStart + }) + .ToListAsync(token); + var parentRelationDictionary = GetCategories(true) + .ToDictionary(c => c.Id, c => new + { + c.Name, + c.IdParent + }); + + var dtos = entities + .GroupBy(o => o.IdCategory) + .Select(g => new WellGroupOpertionDto + { + IdCategory = g.Key, + Category = parentRelationDictionary[g.Key].Name, + Count = g.Count(), + MinutesAverage = g.Average(o => o.DurationMinutes), + MinutesMin = g.Min(o => o.DurationMinutes), + MinutesMax = g.Max(o => o.DurationMinutes), + TotalMinutes = g.Sum(o => o.DurationMinutes), + DeltaDepth = g.Sum(o => o.DurationDepth), + IdParent = parentRelationDictionary[g.Key].IdParent + }); + + while (dtos.All(x => x.IdParent != null)) + { + dtos = dtos + .GroupBy(o => o.IdParent!) + .Select(g => { + var idCategory = g.Key ?? int.MinValue; + var category = parentRelationDictionary.GetValueOrDefault(idCategory); + var newDto = new WellGroupOpertionDto + { + IdCategory = idCategory, + Category = category?.Name ?? "unknown", + Count = g.Sum(o => o.Count), + DeltaDepth = g.Sum(o => o.DeltaDepth), + TotalMinutes = g.Sum(o => o.TotalMinutes), + Items = g.ToList(), + IdParent = category?.IdParent, + }; + return newDto; + }); + } + return dtos; + } + + /// + public async Task InsertRangeAsync( + IEnumerable wellOperationDtos, + CancellationToken token) + { + var firstOperation = wellOperationDtos + .FirstOrDefault(); + if (firstOperation is null) + return 0; + + var idWell = firstOperation.IdWell; + + var timezone = wellService.GetTimezone(idWell); + foreach (var dto in wellOperationDtos) + { + var entity = dto.Adapt(); + entity.Id = default; + entity.DateStart = dto.DateStart.ToUtcDateTimeOffset(timezone.Hours); + entity.IdWell = idWell; + db.WellOperations.Add(entity); + } + + var result = await db.SaveChangesAsync(token); + if (result > 0) + memoryCache.Remove(KeyCacheSections); + return result; + + } + + /// + public async Task UpdateAsync( + WellOperationDto dto, CancellationToken token) + { + var timezone = wellService.GetTimezone(dto.IdWell); + var entity = dto.Adapt(); + entity.DateStart = dto.DateStart.ToUtcDateTimeOffset(timezone.Hours); + db.WellOperations.Update(entity); + + var result = await db.SaveChangesAsync(token); + if (result > 0) + memoryCache.Remove(KeyCacheSections); + return result; + } + + /// + public async Task DeleteAsync(IEnumerable ids, + CancellationToken token) + { + var query = db.WellOperations.Where(e => ids.Contains(e.Id)); + db.WellOperations.RemoveRange(query); + + var result = await db.SaveChangesAsync(token); + if (result > 0) + memoryCache.Remove(KeyCacheSections); + return result; + } + + /// + /// В результате попрежнему требуется конвертировать дату + /// + /// + /// + private IQueryable BuildQuery(WellOperationRequest request) + { + var timezone = wellService.GetTimezone(request.IdWell); + var timeZoneOffset = TimeSpan.FromHours(timezone.Hours); + + var query = db.WellOperations + .Include(s => s.WellSectionType) + .Include(s => s.OperationCategory) + .Where(o => o.IdWell == request.IdWell); + + + if (request.OperationType.HasValue) + query = query.Where(e => e.IdType == request.OperationType.Value); + + if (request.SectionTypeIds?.Any() == true) + query = query.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); + + if (request.OperationCategoryIds?.Any() == true) + query = query.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); + + if (request.GeDepth.HasValue) + query = query.Where(e => e.DepthEnd >= request.GeDepth.Value); + + if (request.LeDepth.HasValue) + query = query.Where(e => e.DepthEnd <= request.LeDepth.Value); + + if (request.GeDate.HasValue) + { + var geDateOffset = request.GeDate.Value.ToUtcDateTimeOffset(timezone.Hours); + query = query.Where(e => e.DateStart >= geDateOffset); + } + + if (request.LtDate.HasValue) + { + var ltDateOffset = request.LtDate.Value.ToUtcDateTimeOffset(timezone.Hours); + query = query.Where(e => e.DateStart < ltDateOffset); + } + + var currentWellOperations = db.WellOperations + .Where(subOp => subOp.IdWell == request.IdWell); + + var wellOperationsWithCategoryNPT = currentWellOperations + .Where(subOp => subOp.IdType == 1) + .Where(subOp => WellOperationCategory.NonProductiveTimeSubIds.Contains(subOp.IdCategory)); + + var result = query.Select(o => new WellOperationDto + { + Id = o.Id, + IdPlan = o.IdPlan, + IdType = o.IdType, + IdWell = o.IdWell, + IdWellSectionType = o.IdWellSectionType, + IdCategory = o.IdCategory, + IdParentCategory = o.OperationCategory.IdParent, + + CategoryName = o.OperationCategory.Name, + WellSectionTypeName = o.WellSectionType.Caption, + + DateStart = DateTime.SpecifyKind(o.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified), + DepthStart = o.DepthStart, + DepthEnd = o.DepthEnd, + DurationHours = o.DurationHours, + CategoryInfo = o.CategoryInfo, + Comment = o.Comment, + + NptHours = wellOperationsWithCategoryNPT + .Where(subOp => subOp.DateStart <= o.DateStart) + .Select(subOp => subOp.DurationHours) + .Sum(), + + Day = (o.DateStart - currentWellOperations + .Where(subOp => subOp.IdType == o.IdType) + .Where(subOp => subOp.DateStart <= o.DateStart) + .Min(subOp => subOp.DateStart)) + .TotalDays, + IdUser = o.IdUser, + LastUpdateDate = o.LastUpdateDate.ToOffset(TimeSpan.FromHours(timezone.Hours)) + }); + + if (request.SortFields?.Any() == true) + { + result = result.SortBy(request.SortFields); + } + else + { + result = result + .OrderBy(e => e.DateStart) + .ThenBy(e => e.DepthEnd) + .ThenBy(e => e.Id); + }; + + return result; + } } diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs index 2f402285..03ed6e07 100644 --- a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs +++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs @@ -226,7 +226,7 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService .OrderBy(w => w.DateStart); } - private Task?> GetSubsystemStatsAsync(int idWell, DateTime startDate, + private Task> GetSubsystemStatsAsync(int idWell, DateTime startDate, DateTime finishDate, CancellationToken cancellationToken) { var request = new SubsystemOperationTimeRequest diff --git a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs deleted file mode 100644 index da171f13..00000000 --- a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs +++ /dev/null @@ -1,163 +0,0 @@ -using AsbCloudDb.Model; -using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AsbCloudInfrastructure.Services.DetectOperations.Detectors; -using AsbCloudInfrastructure.Background; -using Microsoft.Extensions.DependencyInjection; - -namespace AsbCloudInfrastructure.Services.DetectOperations -{ - - public static class OperationDetectionWorkFactory - { - private const string workId = "Operation detection"; - private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); - private static string progress = "no progress"; - - private static readonly DetectorAbstract[] detectors = new DetectorAbstract[] - { - new DetectorRotor(), - new DetectorSlide(), - //new DetectorDevelopment(), - //new DetectorTemplating(), - new DetectorSlipsTime(), - //new DetectorStaticSurveying(), - //new DetectorFlashingBeforeConnection(), - //new DetectorFlashing(), - //new DetectorTemplatingWhileDrilling(), - }; - - public static WorkPeriodic MakeWork() - { - var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod); - workPeriodic.Timeout = TimeSpan.FromSeconds(15 * 60); - workPeriodic.OnErrorAsync = (id, exception, token) => - { - var text = $"work {id}, when {progress}, throw error:{exception.Message}"; - Trace.TraceWarning(text); - return Task.CompletedTask; - }; - return workPeriodic; - } - - // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) - { - using var db = serviceProvider.GetRequiredService(); - - var lastDetectedDates = await db.DetectedOperations - .GroupBy(o => o.IdTelemetry) - .Select(g => new - { - IdTelemetry = g.Key, - LastDate = g.Max(o => o.DateEnd) - }) - .ToListAsync(token); - - var telemetryIds = await db.Telemetries - .Where(t => t.Info != null && t.TimeZone != null) - .Select(t => t.Id) - .ToListAsync(token); - - var joinedlastDetectedDates = telemetryIds - .GroupJoin(lastDetectedDates, - t => t, - o => o.IdTelemetry, - (outer, inner) => new - { - IdTelemetry = outer, - inner.SingleOrDefault()?.LastDate, - }); - - var affected = 0; - foreach (var item in joinedlastDetectedDates) - { - var stopwatch = Stopwatch.StartNew(); - var newOperations = await DetectOperationsAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); - stopwatch.Stop(); - if (newOperations.Any()) - { - db.DetectedOperations.AddRange(newOperations); - affected += await db.SaveChangesAsync(token); - } - } - } - - private static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) - { - var query = db.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 ?? float.NaN, - Pressure = d.Pressure ?? float.NaN, - HookWeight = d.HookWeight ?? float.NaN, - BlockPosition = d.BlockPosition ?? float.NaN, - BitDepth = d.BitDepth ?? float.NaN, - RotorSpeed = d.RotorSpeed ?? float.NaN, - }) - .OrderBy(d => d.DateTime); - - var take = 4 * 86_400; // 4 дня - var startDate = begin; - var detectedOperations = 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) - .Take(take) - .ToArrayAsync(token); - - if (data.Length < gap) - break; - - var isDetected = false; - var positionBegin = 0; - var positionEnd = data.Length - gap; - var step = 10; - while (positionEnd > positionBegin) - { - step ++; - for (int i = 0; i < detectors.Length; i++) - { - progress = $"telemetry:{idTelemetry}, date:{startDate}, pos:{positionBegin}, detector:{detectors[i]}"; - if (detectors[i].TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out OperationDetectorResult? result)) - { - detectedOperations.Add(result!.Operation); - lastDetectedOperation = result.Operation; - isDetected = true; - step = 1; - positionBegin = result.TelemetryEnd; - break; - } - } - if (step > 20) - step = 10; - positionBegin += step; - } - - if (isDetected) - startDate = lastDetectedOperation!.DateEnd; - else - startDate = data[positionEnd].DateTime; - } - - return detectedOperations; - } - } - -} diff --git a/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs new file mode 100644 index 00000000..eef423b9 --- /dev/null +++ b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs @@ -0,0 +1,157 @@ +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudInfrastructure.Services.DetectOperations.Detectors; +using AsbCloudInfrastructure.Background; +using Microsoft.Extensions.DependencyInjection; + +namespace AsbCloudInfrastructure.Services.DetectOperations; + +public class WorkOperationDetection: Work +{ + private static readonly DetectorAbstract[] detectors = new DetectorAbstract[] + { + new DetectorRotor(), + new DetectorSlide(), + //new DetectorDevelopment(), + //new DetectorTemplating(), + new DetectorSlipsTime(), + //new DetectorStaticSurveying(), + //new DetectorFlashingBeforeConnection(), + //new DetectorFlashing(), + //new DetectorTemplatingWhileDrilling(), + }; + + public WorkOperationDetection() + :base("Operation detection") + { + Timeout = TimeSpan.FromMinutes(20); + OnErrorAsync = (id, exception, token) => + { + var text = $"work {id}, when {CurrentState?.State}, throw error:{exception.Message}"; + Trace.TraceWarning(text); + return Task.CompletedTask; + }; + } + + protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) + { + using var db = services.GetRequiredService(); + + var lastDetectedDates = await db.DetectedOperations + .GroupBy(o => o.IdTelemetry) + .Select(g => new + { + IdTelemetry = g.Key, + LastDate = g.Max(o => o.DateEnd) + }) + .ToListAsync(token); + + var telemetryIds = await db.Telemetries + .Where(t => t.Info != null && t.TimeZone != null) + .Select(t => t.Id) + .ToListAsync(token); + + var joinedlastDetectedDates = telemetryIds + .GroupJoin(lastDetectedDates, + t => t, + o => o.IdTelemetry, + (outer, inner) => new + { + IdTelemetry = outer, + inner.SingleOrDefault()?.LastDate, + }); + + var affected = 0; + var count = joinedlastDetectedDates.Count(); + var i = 0d; + foreach (var item in joinedlastDetectedDates) + { + var stopwatch = Stopwatch.StartNew(); + var startDate = item.LastDate ?? DateTimeOffset.MinValue; + onProgressCallback($"start detecting telemetry: {item.IdTelemetry} from {startDate}", i++ / count); + var newOperations = await DetectOperationsAsync(item.IdTelemetry, startDate, db, token); + stopwatch.Stop(); + if (newOperations.Any()) + { + db.DetectedOperations.AddRange(newOperations); + affected += await db.SaveChangesAsync(token); + } + } + } + + private static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + { + var query = db.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 ?? float.NaN, + Pressure = d.Pressure ?? float.NaN, + HookWeight = d.HookWeight ?? float.NaN, + BlockPosition = d.BlockPosition ?? float.NaN, + BitDepth = d.BitDepth ?? float.NaN, + RotorSpeed = d.RotorSpeed ?? float.NaN, + }) + .OrderBy(d => d.DateTime); + + var take = 4 * 86_400; // 4 дня + var startDate = begin; + var detectedOperations = 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) + .Take(take) + .ToArrayAsync(token); + + if (data.Length < gap) + break; + + var isDetected = false; + var positionBegin = 0; + var positionEnd = data.Length - gap; + var step = 10; + while (positionEnd > positionBegin) + { + step ++; + for (int i = 0; i < detectors.Length; i++) + { + if (detectors[i].TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out OperationDetectorResult? result)) + { + detectedOperations.Add(result!.Operation); + lastDetectedOperation = result.Operation; + isDetected = true; + step = 1; + positionBegin = result.TelemetryEnd; + break; + } + } + if (step > 20) + step = 10; + positionBegin += step; + } + + if (isDetected) + startDate = lastDetectedOperation!.DateEnd; + else + startDate = data[positionEnd].DateTime; + } + + return detectedOperations; + } +} diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index adb410d6..228a3564 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -513,17 +513,18 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram if (state.IdState == idStateCreating) { var workId = MakeWorkId(idWell); - if (!backgroundWorker.Contains(workId)) + if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId)) { var well = (await wellService.GetOrDefaultAsync(idWell, token))!; var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf"; var convertedFilesDir = Path.Combine(Path.GetTempPath(), "drillingProgram", $"{well.Cluster}_{well.Caption}"); var tempResultFilePath = Path.Combine(convertedFilesDir, resultFileName); - var workAction = async (string workId, IServiceProvider serviceProvider, CancellationToken token) => + var workAction = async (string workId, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) => { var context = serviceProvider.GetRequiredService(); var fileService = serviceProvider.GetRequiredService(); var files = state.Parts.Select(p => fileService.GetUrl(p.File!)); + onProgress($"Start converting {files.Count()} files to PDF.", null); await ConvertToPdf.GetConverteAndMergedFileAsync(files, tempResultFilePath, convertedFilesDir, token); await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token); }; @@ -539,13 +540,9 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return Task.CompletedTask; }; - var work = new WorkBase(workId, workAction) - { - ExecutionTime = TimeSpan.FromMinutes(1), - OnErrorAsync = onErrorAction - }; - - backgroundWorker.Push(work); + var work = Work.CreateByDelegate(workId, workAction); + work.OnErrorAsync = onErrorAction; + backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); } } } @@ -559,7 +556,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token) { var workId = MakeWorkId(idWell); - backgroundWorker.Delete(workId); + backgroundWorker.WorkStore.TryRemoveFromRunOnceQueue(workId); var filesIds = await context.Files .Where(f => f.IdWell == idWell && diff --git a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs index 26eed2be..bbbc8196 100644 --- a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs +++ b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs @@ -10,6 +10,7 @@ using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Services.Notifications; using AsbCloudInfrastructure.Background; +using DocumentFormat.OpenXml.Presentation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -51,12 +52,12 @@ namespace AsbCloudInfrastructure.Services.Email } var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message); - if (!backgroundWorker.Contains(workId)) + if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w=>w.Id==workId)) { var workAction = MakeEmailSendWorkAction(notification); - var work = new WorkBase(workId, workAction); - backgroundWorker.Push(work); + var work = Work.CreateByDelegate(workId, workAction); + backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); } return Task.CompletedTask; @@ -70,9 +71,9 @@ namespace AsbCloudInfrastructure.Services.Email return Task.WhenAll(tasks); } - private Func MakeEmailSendWorkAction(NotificationDto notification) + private Func, CancellationToken, Task> MakeEmailSendWorkAction(NotificationDto notification) { - return async (_, serviceProvider, token) => + return async (_, serviceProvider, onProgress, token) => { var notificationRepository = serviceProvider.GetRequiredService(); var userRepository = serviceProvider.GetRequiredService(); diff --git a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs deleted file mode 100644 index 7c875e70..00000000 --- a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs +++ /dev/null @@ -1,151 +0,0 @@ -using AsbCloudDb.Model; -using Microsoft.EntityFrameworkCore; -using System; -using System.Data.Common; -using System.Data; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using AsbCloudInfrastructure.Background; -using Microsoft.Extensions.DependencyInjection; - -namespace AsbCloudInfrastructure.Services -{ - - internal static class LimitingParameterCalcWorkFactory - { - private const string workId = "Limiting parameter calc"; - private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); - - public static WorkPeriodic MakeWork() - { - var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) - { - Timeout = TimeSpan.FromMinutes(30) - }; - return workPeriodic; - } - - // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) - { - using var db = serviceProvider.GetRequiredService(); - var lastDetectedDates = await db.LimitingParameter - .GroupBy(o => o.IdTelemetry) - .Select(g => new - { - IdTelemetry = g.Key, - LastDate = g.Max(o => o.DateEnd) - }) - .ToListAsync(token); - - var telemetryIds = await db.Telemetries - .Where(t => t.Info != null && t.TimeZone != null) - .Select(t => t.Id) - .ToListAsync(token); - - var telemetryLastDetectedDates = telemetryIds - .GroupJoin(lastDetectedDates, - t => t, - o => o.IdTelemetry, - (outer, inner) => new - { - IdTelemetry = outer, - inner.SingleOrDefault()?.LastDate, - }); - - foreach (var item in telemetryLastDetectedDates) - { - var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); - if (newLimitingParameters?.Any() == true) - { - db.LimitingParameter.AddRange(newLimitingParameters); - await db.SaveChangesAsync(token); - } - } - } - - private static async Task> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) - { - var query = - $"select " + - $"limiting_parameters.date, limiting_parameters.id_feed_regulator, limiting_parameters.well_depth " + - $"from ( " + - $"select " + - $"date, id_feed_regulator, well_depth, " + - $"lag(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lag, " + - $"lead(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lead " + - $"from t_telemetry_data_saub " + - $"where id_feed_regulator is not null " + - $"and id_telemetry = {idTelemetry}" + - $"and date >= '{begin:u}'" + - $"order by date) as limiting_parameters " + - $"where id_feed_regulator_lag is null " + - $"or (id_feed_regulator != id_feed_regulator_lag and id_feed_regulator_lead != id_feed_regulator_lag) " + - $"order by date;"; - - var limitingParameters = new List(32); - using (var result = await ExecuteReaderAsync(db, query, token)) - { - LimitingParameter? limitingLast = null; - while (result.Read()) - { - var date = result.GetFieldValue(0); - var idLimiting = result.GetFieldValue(1); - var wellDepth = result.GetFieldValue(2); - - if (limitingLast is null) - { - limitingLast = new LimitingParameter - { - DateStart = date, - DepthStart = wellDepth, - IdFeedRegulator = idLimiting - }; - } - - if (limitingLast.IdFeedRegulator != idLimiting || limitingLast.DepthStart < wellDepth) - { - limitingParameters.Add(new LimitingParameter { - IdTelemetry = idTelemetry, - IdFeedRegulator = limitingLast.IdFeedRegulator, - DateStart = limitingLast.DateStart, - DateEnd = date, - DepthStart = limitingLast.DepthStart, - DepthEnd = wellDepth - }); - - limitingLast = new LimitingParameter - { - DateStart = date, - DepthStart = wellDepth, - IdFeedRegulator = idLimiting - }; - } - } - } - - return limitingParameters; - } - - private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) - { - var connection = db.Database.GetDbConnection(); - if ( - connection?.State is null || - connection.State == ConnectionState.Broken || - connection.State == ConnectionState.Closed) - { - await db.Database.OpenConnectionAsync(token); - connection = db.Database.GetDbConnection(); - } - using var command = connection.CreateCommand(); - command.CommandText = query; - - var result = await command.ExecuteReaderAsync(token); - return result; - } - } - -} diff --git a/AsbCloudInfrastructure/Services/ReportService.cs b/AsbCloudInfrastructure/Services/ReportService.cs index eca8f84e..6a66c30e 100644 --- a/AsbCloudInfrastructure/Services/ReportService.cs +++ b/AsbCloudInfrastructure/Services/ReportService.cs @@ -51,7 +51,7 @@ namespace AsbCloudInfrastructure.Services var workId = $"create report by wellid:{idWell} for userid:{idUser} requested at {DateTime.Now}"; - var workAction = async (string id, IServiceProvider serviceProvider, CancellationToken token) => + var workAction = async (string id, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) => { using var context = serviceProvider.GetRequiredService(); var fileService = serviceProvider.GetRequiredService(); @@ -64,7 +64,9 @@ namespace AsbCloudInfrastructure.Services generator.OnProgress += (s, e) => { - progressHandler.Invoke(e.Adapt(), id); + var arg = e.Adapt(); + onProgress(arg.Operation?? string.Empty, arg.Progress); + progressHandler.Invoke(arg, id); }; generator.Make(reportFileName); @@ -92,8 +94,8 @@ namespace AsbCloudInfrastructure.Services context.SaveChanges(); }; - var work = new WorkBase(workId, workAction); - backgroundWorkerService.Push(work); + var work = Work.CreateByDelegate(workId, workAction); + backgroundWorkerService.WorkStore.RunOnceQueue.Enqueue(work); progressHandler.Invoke(new ReportProgressDto { diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs index d1a7fea1..9bb2dc79 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs @@ -48,12 +48,12 @@ namespace AsbCloudInfrastructure.Services.SAUB instance = new TelemetryDataCache(); var worker = provider.GetRequiredService(); var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}"; - var work = new WorkBase(workId, async (workId, provider, token) => { + var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) => { var db = provider.GetRequiredService(); - await instance.InitializeCacheFromDBAsync(db, token); + await instance.InitializeCacheFromDBAsync(db, onProgress, token); }); - worker.Push(work); + worker.WorkStore.RunOnceQueue.Enqueue(work); } instance.provider = provider; return instance; @@ -150,7 +150,7 @@ namespace AsbCloudInfrastructure.Services.SAUB return new DatesRangeDto { From = from.Value, To = to }; } - private async Task InitializeCacheFromDBAsync(IAsbCloudDbContext db, CancellationToken token) + private async Task InitializeCacheFromDBAsync(IAsbCloudDbContext db, Action onProgress, CancellationToken token) where TEntity : class, AsbCloudDb.Model.ITelemetryData { if (isLoading) @@ -159,7 +159,6 @@ namespace AsbCloudInfrastructure.Services.SAUB isLoading = true; var defaultTimeout = db.Database.GetCommandTimeout(); - System.Diagnostics.Trace.TraceInformation($"cache loading starting. Setting CommandTimeout 90s ({defaultTimeout})"); db.Database.SetCommandTimeout(TimeSpan.FromSeconds(90)); Well[] wells = await db.Set() @@ -168,6 +167,8 @@ namespace AsbCloudInfrastructure.Services.SAUB .Where(well => well.IdTelemetry != null) .ToArrayAsync(token); + var count = wells.Length; + var i = 0d; foreach (Well well in wells) { var capacity = well.IdState == 1 @@ -176,21 +177,13 @@ namespace AsbCloudInfrastructure.Services.SAUB var idTelemetry = well.IdTelemetry!.Value; var hoursOffset = well.Timezone.Hours; - - System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}>: Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}"); + + onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++/count); var cacheItem = await GetOrDefaultCacheDataFromDbAsync(db, idTelemetry, capacity, hoursOffset, token); if(cacheItem is not null) - { caches.TryAdd(idTelemetry, cacheItem); - System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> for well: {well.Cluster?.Caption}/{well.Caption} loaded"); - } - else - { - System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> for well: {well.Cluster?.Caption}/{well.Caption} has no data"); - } } - System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> load complete"); isLoading = false; db.Database.SetCommandTimeout(defaultTimeout); } diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs deleted file mode 100644 index d5b86803..00000000 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs +++ /dev/null @@ -1,300 +0,0 @@ -using AsbCloudDb.Model; -using AsbCloudDb.Model.Subsystems; -using AsbCloudInfrastructure.Background; -using AsbCloudInfrastructure.Services.Subsystems.Utils; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Services.Subsystems -{ - internal static class SubsystemOperationTimeCalcWorkFactory - { - private const string workId = "Subsystem operation time calc"; - private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); - - private const int idSubsytemTorqueMaster = 65537; - private const int idSubsytemSpinMaster = 65536; - private const int idSubsystemAPDRotor = 11; - private const int idSubsystemAPDSlide = 12; - private const int idSubsytemMse = 2; - - public static WorkPeriodic MakeWork() - { - var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) - { - Timeout = TimeSpan.FromMinutes(30) - }; - return workPeriodic; - } - - // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) - { - using var db = serviceProvider.GetRequiredService(); - - var lastDetectedDates = await db.SubsystemOperationTimes - .GroupBy(o => o.IdTelemetry) - .Select(g => new - { - IdTelemetry = g.Key, - LastDate = g.Max(o => o.DateEnd) - }) - .ToListAsync(token); - - var telemetryIds = await db.Telemetries - .Where(t => t.Info != null && t.TimeZone != null) - .Select(t => t.Id) - .ToListAsync(token); - - var telemetryLastDetectedDates = telemetryIds - .GroupJoin(lastDetectedDates, - t => t, - o => o.IdTelemetry, - (outer, inner) => new - { - IdTelemetry = outer, - inner.SingleOrDefault()?.LastDate, - }); - - foreach (var item in telemetryLastDetectedDates) - { - var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); - if (newOperationsSaub?.Any() == true) - { - db.SubsystemOperationTimes.AddRange(newOperationsSaub); - await db.SaveChangesAsync(token); - } - var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); - if (newOperationsSpin?.Any() == true) - { - db.SubsystemOperationTimes.AddRange(newOperationsSpin); - await db.SaveChangesAsync(token); - } - } - } - - private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) - { - var connection = db.Database.GetDbConnection(); - if ( - connection?.State is null || - connection.State == ConnectionState.Broken || - connection.State == ConnectionState.Closed) - { - await db.Database.OpenConnectionAsync(token); - connection = db.Database.GetDbConnection(); - } - using var command = connection.CreateCommand(); - command.CommandText = query; - - var result = await command.ExecuteReaderAsync(token); - return result; - } - - private static async Task> OperationTimeSaubAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) - { - static bool isSubsytemAkbRotor(short? mode) => mode == 1; - - static bool isSubsytemAkbSlide(short? mode) => mode == 3; - - static bool IsSubsystemMse(short? state) => (state & 1) > 0; - - var query = - $"select tt.date, tt.mode, tt.well_depth, tt.mse_state " + - $"from ( " + - $" select " + - $" date, " + - $" mode, " + - $" mse_state, " + - $" well_depth, " + - $" lag(mode,1) over (order by date) as mode_lag, " + - $" lead(mode,1) over (order by date) as mode_lead " + - $" from t_telemetry_data_saub " + - $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0" + - $" order by date ) as tt " + - $"where (tt.mode_lag is null or (tt.mode != tt.mode_lag and tt.mode_lead != tt.mode_lag)) and tt.date >= '{begin:u}' " + - $"order by tt.date;"; - - using var result = await ExecuteReaderAsync(db, query, token); - - var subsystemsOperationTimes = new List(); - var detectorRotor = new SubsystemDetector(idTelemetry, idSubsystemAPDRotor, isSubsytemAkbRotor, IsValid); - var detectorSlide = new SubsystemDetector(idTelemetry, idSubsystemAPDSlide, isSubsytemAkbSlide, IsValid); - var detectorMse = new SubsystemDetector(idTelemetry, idSubsytemMse, IsSubsystemMse, IsValid); - - while (result.Read()) - { - var mode = result.GetFieldValue(1); - var state = result.GetFieldValue(3); - - var isAkbRotorEnable = isSubsytemAkbRotor(mode); - var isAkbSlideEnable = isSubsytemAkbSlide(mode); - var isMseEnable = IsSubsystemMse(state); - var date = result.GetFieldValue(0); - var depth = result.GetFieldValue(2); - - if (detectorRotor.TryDetect(mode, date, depth, out var detectedRotor)) - subsystemsOperationTimes.Add(detectedRotor!); - - if (detectorSlide.TryDetect(mode, date, depth, out var detectedSlide)) - subsystemsOperationTimes.Add(detectedSlide!); - - if (detectorMse.TryDetect(mode, date, depth, out var detectedMse)) - subsystemsOperationTimes.Add(detectedMse!); - } - - return subsystemsOperationTimes; - } - - private static async Task> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) - { - static int? GetSubsytemId(short? mode, int? state) - { - // При изменении следующего кода сообщи в Vladimir.Sobolev@nedra.digital - if (state == 7 && (mode & 2) > 0) - return idSubsytemTorqueMaster;// демпфер - - if (state != 0 && state != 5 && state != 6 && state != 7) - return idSubsytemSpinMaster;// осцилляция - - return null; - } - - var querySpin = - $"select " + - $" tspin.date, " + - $" tspin.mode, " + - $" tspin.state " + - $"from ( " + - $" select " + - $" date, " + - $" mode, " + - $" lag(mode, 1) over (order by date) as mode_lag, " + - $" lead(mode, 1) over (order by date) as mode_lead, " + - $" state, " + - $" lag(state, 1) over (order by date) as state_lag " + - $" from t_telemetry_data_spin " + - $" where id_telemetry = {idTelemetry} and date >= '{begin:u}'" + - $" order by date ) as tspin " + - $"where mode_lag is null or state_lag is null or (mode != mode_lag and mode_lead != mode_lag) or state != state_lag " + - $"order by date;"; - - var rows = new List<(int? IdSubsystem, DateTimeOffset Date)>(32); - - using var resultSpin = await ExecuteReaderAsync(db, querySpin, token); - int? idSubsystemLast = null; - while (resultSpin.Read()) - { - var mode = resultSpin.GetFieldValue(1); - var state = resultSpin.GetFieldValue(2); - var idSubsystem = GetSubsytemId(mode, state); - if (idSubsystemLast != idSubsystem) - { - idSubsystemLast = idSubsystem; - var date = resultSpin.GetFieldValue(0); - rows.Add((idSubsystem, date)); - } - } - await resultSpin.DisposeAsync(); - - if (rows.Count < 2) - return Enumerable.Empty(); - - var minSpinDate = rows.Min(i => i.Date); - var maxSpinDate = rows.Max(i => i.Date); - var depthInterpolation = await GetInterpolation(db, idTelemetry, minSpinDate, maxSpinDate, token); - - if (depthInterpolation is null) - return Enumerable.Empty(); - - var subsystemsOperationTimes = new List(32); - - for (int i = 1; i < rows.Count; i++) - { - var r0 = rows[i - 1]; - var r1 = rows[i]; - if (r0.IdSubsystem is not null && r0.IdSubsystem != r1.IdSubsystem) - { - var subsystemOperationTime = new SubsystemOperationTime() - { - IdTelemetry = idTelemetry, - IdSubsystem = r0.IdSubsystem.Value, - DateStart = r0.Date, - DateEnd = r1.Date, - DepthStart = depthInterpolation.GetDepth(r0.Date), - DepthEnd = depthInterpolation.GetDepth(r1.Date), - }; - - if (IsValid(subsystemOperationTime)) - subsystemsOperationTimes.Add(subsystemOperationTime); - } - } - - return subsystemsOperationTimes; - } - - private static bool IsValid(SubsystemOperationTime item) - { - var validateCode = GetValidateErrorCode(item); - if (validateCode != 0) - { - var str = System.Text.Json.JsonSerializer.Serialize(item); - Trace.TraceWarning($"Wrong({validateCode}) SubsystemOperationTime: {str}"); - } - return validateCode == 0; - } - - private static int GetValidateErrorCode(SubsystemOperationTime item) - { - if (item.DateStart > item.DateEnd) - return -1; - if ((item.DateEnd - item.DateStart).TotalHours > 48) - return -2; - if (item.DepthEnd < item.DepthStart) - return -3; - if (item.DepthEnd - item.DepthStart > 2000d) - return -4; - if (item.DepthEnd < 0d) - return -5; - if (item.DepthStart < 0d) - return -6; - if (item.DepthEnd > 24_0000d) - return -7; - if (item.DepthStart > 24_0000d) - return -8; - return 0; - } - - private static async Task GetInterpolation(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) - { - var dataDepthFromSaub = await db.TelemetryDataSaub - .Where(d => d.IdTelemetry == idTelemetry) - .Where(d => d.DateTime >= dateBegin) - .Where(d => d.DateTime <= dateEnd) - .Where(d => d.WellDepth != null) - .Where(d => d.WellDepth > 0) - .GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10)) - .Select(g => new { - DateMin = g.Min(d => d.DateTime), - DepthMin = g.Min(d => d.WellDepth) ?? 0, - }) - .OrderBy(i => i.DateMin) - .ToArrayAsync(token); - - if (!dataDepthFromSaub.Any()) - return null; - - var depthInterpolation = new DepthInterpolation(dataDepthFromSaub.Select(i => (i.DateMin, i.DepthMin))); - return depthInterpolation; - } - } - -} diff --git a/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs b/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs new file mode 100644 index 00000000..bda4bbe4 --- /dev/null +++ b/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs @@ -0,0 +1,294 @@ +using AsbCloudDb.Model; +using AsbCloudDb.Model.Subsystems; +using AsbCloudInfrastructure.Background; +using AsbCloudInfrastructure.Services.Subsystems.Utils; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services.Subsystems; + +public class WorkSubsystemOperationTimeCalc: Work +{ + private const int idSubsytemTorqueMaster = 65537; + private const int idSubsytemSpinMaster = 65536; + private const int idSubsystemAPDRotor = 11; + private const int idSubsystemAPDSlide = 12; + private const int idSubsytemMse = 2; + + public WorkSubsystemOperationTimeCalc() + : base("Subsystem operation time calc") + { + Timeout = TimeSpan.FromMinutes(20); + } + + protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) + { + using var db = services.GetRequiredService(); + + var lastDetectedDates = await db.SubsystemOperationTimes + .GroupBy(o => o.IdTelemetry) + .Select(g => new + { + IdTelemetry = g.Key, + LastDate = g.Max(o => o.DateEnd) + }) + .ToListAsync(token); + + var telemetryIds = await db.Telemetries + .Where(t => t.Info != null && t.TimeZone != null) + .Select(t => t.Id) + .ToListAsync(token); + + var telemetryLastDetectedDates = telemetryIds + .GroupJoin(lastDetectedDates, + t => t, + o => o.IdTelemetry, + (outer, inner) => new + { + IdTelemetry = outer, + inner.SingleOrDefault()?.LastDate, + }); + + var count = telemetryLastDetectedDates.Count(); + var i = 0d; + foreach (var item in telemetryLastDetectedDates) + { + onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count); + var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); + if (newOperationsSaub?.Any() == true) + { + db.SubsystemOperationTimes.AddRange(newOperationsSaub); + await db.SaveChangesAsync(token); + } + var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); + if (newOperationsSpin?.Any() == true) + { + db.SubsystemOperationTimes.AddRange(newOperationsSpin); + await db.SaveChangesAsync(token); + } + } + } + + private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) + { + var connection = db.Database.GetDbConnection(); + if ( + connection?.State is null || + connection.State == ConnectionState.Broken || + connection.State == ConnectionState.Closed) + { + await db.Database.OpenConnectionAsync(token); + connection = db.Database.GetDbConnection(); + } + using var command = connection.CreateCommand(); + command.CommandText = query; + + var result = await command.ExecuteReaderAsync(token); + return result; + } + + private static async Task> OperationTimeSaubAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + { + static bool isSubsytemAkbRotor(short? mode) => mode == 1; + + static bool isSubsytemAkbSlide(short? mode) => mode == 3; + + static bool IsSubsystemMse(short? state) => (state & 1) > 0; + + var query = + $"select tt.date, tt.mode, tt.well_depth, tt.mse_state " + + $"from ( " + + $" select " + + $" date, " + + $" mode, " + + $" mse_state, " + + $" well_depth, " + + $" lag(mode,1) over (order by date) as mode_lag, " + + $" lead(mode,1) over (order by date) as mode_lead " + + $" from t_telemetry_data_saub " + + $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0" + + $" order by date ) as tt " + + $"where (tt.mode_lag is null or (tt.mode != tt.mode_lag and tt.mode_lead != tt.mode_lag)) and tt.date >= '{begin:u}' " + + $"order by tt.date;"; + + using var result = await ExecuteReaderAsync(db, query, token); + + var subsystemsOperationTimes = new List(); + var detectorRotor = new SubsystemDetector(idTelemetry, idSubsystemAPDRotor, isSubsytemAkbRotor, IsValid); + var detectorSlide = new SubsystemDetector(idTelemetry, idSubsystemAPDSlide, isSubsytemAkbSlide, IsValid); + var detectorMse = new SubsystemDetector(idTelemetry, idSubsytemMse, IsSubsystemMse, IsValid); + + while (result.Read()) + { + var mode = result.GetFieldValue(1); + var state = result.GetFieldValue(3); + + var isAkbRotorEnable = isSubsytemAkbRotor(mode); + var isAkbSlideEnable = isSubsytemAkbSlide(mode); + var isMseEnable = IsSubsystemMse(state); + var date = result.GetFieldValue(0); + var depth = result.GetFieldValue(2); + + if (detectorRotor.TryDetect(mode, date, depth, out var detectedRotor)) + subsystemsOperationTimes.Add(detectedRotor!); + + if (detectorSlide.TryDetect(mode, date, depth, out var detectedSlide)) + subsystemsOperationTimes.Add(detectedSlide!); + + if (detectorMse.TryDetect(mode, date, depth, out var detectedMse)) + subsystemsOperationTimes.Add(detectedMse!); + } + + return subsystemsOperationTimes; + } + + private static async Task> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + { + static int? GetSubsytemId(short? mode, int? state) + { + // При изменении следующего кода сообщи в Vladimir.Sobolev@nedra.digital + if (state == 7 && (mode & 2) > 0) + return idSubsytemTorqueMaster;// демпфер + + if (state != 0 && state != 5 && state != 6 && state != 7) + return idSubsytemSpinMaster;// осцилляция + + return null; + } + + var querySpin = + $"select " + + $" tspin.date, " + + $" tspin.mode, " + + $" tspin.state " + + $"from ( " + + $" select " + + $" date, " + + $" mode, " + + $" lag(mode, 1) over (order by date) as mode_lag, " + + $" lead(mode, 1) over (order by date) as mode_lead, " + + $" state, " + + $" lag(state, 1) over (order by date) as state_lag " + + $" from t_telemetry_data_spin " + + $" where id_telemetry = {idTelemetry} and date >= '{begin:u}'" + + $" order by date ) as tspin " + + $"where mode_lag is null or state_lag is null or (mode != mode_lag and mode_lead != mode_lag) or state != state_lag " + + $"order by date;"; + + var rows = new List<(int? IdSubsystem, DateTimeOffset Date)>(32); + + using var resultSpin = await ExecuteReaderAsync(db, querySpin, token); + int? idSubsystemLast = null; + while (resultSpin.Read()) + { + var mode = resultSpin.GetFieldValue(1); + var state = resultSpin.GetFieldValue(2); + var idSubsystem = GetSubsytemId(mode, state); + if (idSubsystemLast != idSubsystem) + { + idSubsystemLast = idSubsystem; + var date = resultSpin.GetFieldValue(0); + rows.Add((idSubsystem, date)); + } + } + await resultSpin.DisposeAsync(); + + if (rows.Count < 2) + return Enumerable.Empty(); + + var minSpinDate = rows.Min(i => i.Date); + var maxSpinDate = rows.Max(i => i.Date); + var depthInterpolation = await GetInterpolation(db, idTelemetry, minSpinDate, maxSpinDate, token); + + if (depthInterpolation is null) + return Enumerable.Empty(); + + var subsystemsOperationTimes = new List(32); + + for (int i = 1; i < rows.Count; i++) + { + var r0 = rows[i - 1]; + var r1 = rows[i]; + if (r0.IdSubsystem is not null && r0.IdSubsystem != r1.IdSubsystem) + { + var subsystemOperationTime = new SubsystemOperationTime() + { + IdTelemetry = idTelemetry, + IdSubsystem = r0.IdSubsystem.Value, + DateStart = r0.Date, + DateEnd = r1.Date, + DepthStart = depthInterpolation.GetDepth(r0.Date), + DepthEnd = depthInterpolation.GetDepth(r1.Date), + }; + + if (IsValid(subsystemOperationTime)) + subsystemsOperationTimes.Add(subsystemOperationTime); + } + } + + return subsystemsOperationTimes; + } + + private static bool IsValid(SubsystemOperationTime item) + { + var validateCode = GetValidateErrorCode(item); + if (validateCode != 0) + { + var str = System.Text.Json.JsonSerializer.Serialize(item); + Trace.TraceWarning($"Wrong({validateCode}) SubsystemOperationTime: {str}"); + } + return validateCode == 0; + } + + private static int GetValidateErrorCode(SubsystemOperationTime item) + { + if (item.DateStart > item.DateEnd) + return -1; + if ((item.DateEnd - item.DateStart).TotalHours > 48) + return -2; + if (item.DepthEnd < item.DepthStart) + return -3; + if (item.DepthEnd - item.DepthStart > 2000d) + return -4; + if (item.DepthEnd < 0d) + return -5; + if (item.DepthStart < 0d) + return -6; + if (item.DepthEnd > 24_0000d) + return -7; + if (item.DepthStart > 24_0000d) + return -8; + return 0; + } + + private static async Task GetInterpolation(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) + { + var dataDepthFromSaub = await db.TelemetryDataSaub + .Where(d => d.IdTelemetry == idTelemetry) + .Where(d => d.DateTime >= dateBegin) + .Where(d => d.DateTime <= dateEnd) + .Where(d => d.WellDepth != null) + .Where(d => d.WellDepth > 0) + .GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10)) + .Select(g => new { + DateMin = g.Min(d => d.DateTime), + DepthMin = g.Min(d => d.WellDepth) ?? 0, + }) + .OrderBy(i => i.DateMin) + .ToArrayAsync(token); + + if (!dataDepthFromSaub.Any()) + return null; + + var depthInterpolation = new DepthInterpolation(dataDepthFromSaub.Select(i => (i.DateMin, i.DepthMin))); + return depthInterpolation; + } +} diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs index bbe261bf..af1265c3 100644 --- a/AsbCloudInfrastructure/Services/WellInfoService.cs +++ b/AsbCloudInfrastructure/Services/WellInfoService.cs @@ -18,58 +18,26 @@ using AsbCloudApp.Data.ProcessMaps; using AsbCloudApp.IntegrationEvents; using AsbCloudApp.IntegrationEvents.Interfaces; -namespace AsbCloudInfrastructure.Services +namespace AsbCloudInfrastructure.Services; + +public class WellInfoService { - public class WellInfoService + public class WorkWellInfoUpdate : Work { - class WellMapInfoWithComanies : WellMapInfoDto + public WorkWellInfoUpdate() + : base("Well statistics update") { - public int? IdTelemetry { get; set; } - public IEnumerable IdsCompanies { get; set; } = null!; + Timeout = TimeSpan.FromMinutes(20); } - private const string workId = "Well statistics update"; - private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); - - private readonly TelemetryDataCache telemetryDataSaubCache; - private readonly TelemetryDataCache telemetryDataSpinCache; - private readonly IWitsRecordRepository witsRecord7Repository; - private readonly IWitsRecordRepository witsRecord1Repository; - private readonly IGtrRepository gtrRepository; - private static IEnumerable WellMapInfo = Enumerable.Empty(); - - public WellInfoService( - TelemetryDataCache telemetryDataSaubCache, - TelemetryDataCache telemetryDataSpinCache, - IWitsRecordRepository witsRecord7Repository, - IWitsRecordRepository witsRecord1Repository, - IGtrRepository gtrRepository) + protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) { - this.telemetryDataSaubCache = telemetryDataSaubCache; - this.telemetryDataSpinCache = telemetryDataSpinCache; - - this.witsRecord7Repository = witsRecord7Repository; - this.witsRecord1Repository = witsRecord1Repository; - this.gtrRepository = gtrRepository; - } - - public static WorkPeriodic MakeWork() - { - var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) - { - Timeout = TimeSpan.FromMinutes(30) - }; - return workPeriodic; - } - - private static async Task WorkAction(string workName, IServiceProvider serviceProvider, CancellationToken token) - { - var wellService = serviceProvider.GetRequiredService(); - var operationsStatService = serviceProvider.GetRequiredService(); - var wellDrillingProcessMapRepository = serviceProvider.GetRequiredService(); - var subsystemOperationTimeService = serviceProvider.GetRequiredService(); - var telemetryDataSaubCache = serviceProvider.GetRequiredService>(); - var messageHub = serviceProvider.GetRequiredService>(); + var wellService = services.GetRequiredService(); + var operationsStatService = services.GetRequiredService(); + var wellDrillingProcessMapRepository = services.GetRequiredService(); + var subsystemOperationTimeService = services.GetRequiredService(); + var telemetryDataSaubCache = services.GetRequiredService>(); + var messageHub = services.GetRequiredService>(); var wells = await wellService.GetAllAsync(token); @@ -87,29 +55,30 @@ namespace AsbCloudInfrastructure.Services }); var operationsStat = await operationsStatService.GetWellsStatAsync(wellsIds, token); - + var subsystemStat = await subsystemOperationTimeService .GetStatByActiveWells(wellsIds, token); - + var count = wells.Count(); + var i = 0d; WellMapInfo = wells.Select(well => { var wellMapInfo = well.Adapt(); wellMapInfo.IdState = well.IdState; - + onProgressCallback($"Start updating info by well({well.Id}): {well.Caption}", i++ / count); double? currentDepth = null; TelemetryDataSaubDto? lastSaubTelemetry = null; - + if (well.IdTelemetry.HasValue) { wellMapInfo.IdTelemetry = well.IdTelemetry.Value; lastSaubTelemetry = telemetryDataSaubCache.GetLastOrDefault(well.IdTelemetry.Value); - if(lastSaubTelemetry is not null) + if (lastSaubTelemetry is not null) { currentDepth = lastSaubTelemetry.WellDepth; } } - var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id); + var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id); var wellLastFactSection = wellOperationsStat?.Sections.LastOrDefault(s => s.Fact is not null); currentDepth ??= wellLastFactSection?.Fact?.WellDepthEnd; @@ -124,7 +93,7 @@ namespace AsbCloudInfrastructure.Services { wellDrillingProcessMap = wellProcessMaps.FirstOrDefault(p => p.IdWellSectionType == idSection); } - else if(currentDepth.HasValue) + else if (currentDepth.HasValue) { wellDrillingProcessMap = wellProcessMaps.FirstOrDefault(p => p.DepthStart <= currentDepth.Value && p.DepthEnd >= currentDepth.Value); } @@ -134,8 +103,8 @@ namespace AsbCloudInfrastructure.Services planTotalDepth ??= wellOperationsStat?.Total.Plan?.WellDepthEnd; wellMapInfo.Section = wellLastFactSection?.Caption; - - wellMapInfo.FirstFactOperationDateStart = wellOperationsStat?.Total.Fact?.Start + + wellMapInfo.FirstFactOperationDateStart = wellOperationsStat?.Total.Fact?.Start ?? wellOperationsStat?.Total.Plan?.Start; wellMapInfo.LastPredictOperationDateEnd = wellOperationsStat?.Total.Plan?.End; @@ -163,7 +132,7 @@ namespace AsbCloudInfrastructure.Services Plan = wellDrillingProcessMap?.Pressure.Plan, Fact = lastSaubTelemetry?.Pressure }; - + wellMapInfo.PressureSp = lastSaubTelemetry?.PressureSp; wellMapInfo.WellDepth = new() @@ -195,51 +164,79 @@ namespace AsbCloudInfrastructure.Services return wellMapInfo; }).ToArray(); - var updateWellInfoEventTasks = wellsIds.Select(idWell => + var updateWellInfoEventTasks = wellsIds.Select(idWell => messageHub.HandleAsync(new UpdateWellInfoEvent(idWell), token)); - + await Task.WhenAll(updateWellInfoEventTasks); } + } - private WellMapInfoWithTelemetryStat Convert(WellMapInfoWithComanies wellInfo) + class WellMapInfoWithComanies : WellMapInfoDto + { + public int? IdTelemetry { get; set; } + public IEnumerable IdsCompanies { get; set; } = null!; + } + + private readonly TelemetryDataCache telemetryDataSaubCache; + private readonly TelemetryDataCache telemetryDataSpinCache; + private readonly IWitsRecordRepository witsRecord7Repository; + private readonly IWitsRecordRepository witsRecord1Repository; + private readonly IGtrRepository gtrRepository; + private static IEnumerable WellMapInfo = Enumerable.Empty(); + + public WellInfoService( + TelemetryDataCache telemetryDataSaubCache, + TelemetryDataCache telemetryDataSpinCache, + IWitsRecordRepository witsRecord7Repository, + IWitsRecordRepository witsRecord1Repository, + IGtrRepository gtrRepository) + { + this.telemetryDataSaubCache = telemetryDataSaubCache; + this.telemetryDataSpinCache = telemetryDataSpinCache; + + this.witsRecord7Repository = witsRecord7Repository; + this.witsRecord1Repository = witsRecord1Repository; + this.gtrRepository = gtrRepository; + } + + private WellMapInfoWithTelemetryStat Convert(WellMapInfoWithComanies wellInfo) + { + var result = wellInfo.Adapt(); + if (wellInfo.IdTelemetry.HasValue) { - var result = wellInfo.Adapt(); - if (wellInfo.IdTelemetry.HasValue) - { - var idTelemetry = wellInfo.IdTelemetry.Value; - result.LastDataSaub = telemetryDataSaubCache.GetLastOrDefault(idTelemetry); - result.LastDataSpin = telemetryDataSpinCache.GetLastOrDefault(idTelemetry); - result.LastDataDdsDate = GetLastOrDefaultDdsTelemetry(idTelemetry); - result.LastDataGtrDate = gtrRepository.GetLastData(wellInfo.Id) - .MaxOrDefault(item => item.Date); - result.LastDataDpcsDate = null; - result.LastDataDpcsDate = null; - } - - return result; + var idTelemetry = wellInfo.IdTelemetry.Value; + result.LastDataSaub = telemetryDataSaubCache.GetLastOrDefault(idTelemetry); + result.LastDataSpin = telemetryDataSpinCache.GetLastOrDefault(idTelemetry); + result.LastDataDdsDate = GetLastOrDefaultDdsTelemetry(idTelemetry); + result.LastDataGtrDate = gtrRepository.GetLastData(wellInfo.Id) + .MaxOrDefault(item => item.Date); + result.LastDataDpcsDate = null; + result.LastDataDpcsDate = null; } - private DateTime? GetLastOrDefaultDdsTelemetry(int idTelemetry) - { - var lastDdsRecord1Date = witsRecord1Repository.GetLastOrDefault(idTelemetry)?.DateTime; - var lastDdsRecord7Date = witsRecord7Repository.GetLastOrDefault(idTelemetry)?.DateTime; + return result; + } - if (lastDdsRecord1Date.HasValue && lastDdsRecord7Date.HasValue) - if (lastDdsRecord1Date.Value > lastDdsRecord7Date.Value) - return lastDdsRecord1Date.Value; - else - return lastDdsRecord7Date.Value; + private DateTime? GetLastOrDefaultDdsTelemetry(int idTelemetry) + { + var lastDdsRecord1Date = witsRecord1Repository.GetLastOrDefault(idTelemetry)?.DateTime; + var lastDdsRecord7Date = witsRecord7Repository.GetLastOrDefault(idTelemetry)?.DateTime; - return lastDdsRecord1Date ?? lastDdsRecord7Date; - } + if (lastDdsRecord1Date.HasValue && lastDdsRecord7Date.HasValue) + if (lastDdsRecord1Date.Value > lastDdsRecord7Date.Value) + return lastDdsRecord1Date.Value; + else + return lastDdsRecord7Date.Value; - public WellMapInfoWithTelemetryStat? FirstOrDefault(Func predicate) - { - var first = WellMapInfo.FirstOrDefault(predicate); - if (first is WellMapInfoWithComanies wellMapInfoWithComanies) - return Convert(wellMapInfoWithComanies); + return lastDdsRecord1Date ?? lastDdsRecord7Date; + } - return null; - } + public WellMapInfoWithTelemetryStat? FirstOrDefault(Func predicate) + { + var first = WellMapInfo.FirstOrDefault(predicate); + if (first is WellMapInfoWithComanies wellMapInfoWithComanies) + return Convert(wellMapInfoWithComanies); + + return null; } } diff --git a/AsbCloudInfrastructure/Services/WellboreService.cs b/AsbCloudInfrastructure/Services/WellboreService.cs index 5e0e439e..396243d6 100644 --- a/AsbCloudInfrastructure/Services/WellboreService.cs +++ b/AsbCloudInfrastructure/Services/WellboreService.cs @@ -35,7 +35,7 @@ public class WellboreService : IWellboreService } public async Task> GetWellboresAsync(WellboreRequest request, - CancellationToken cancellationToken) + CancellationToken token) { var wellbores = new List(request.Ids.Count()); var skip = request.Skip ?? 0; @@ -44,26 +44,43 @@ public class WellboreService : IWellboreService var sections = wellOperationRepository.GetSectionTypes() .ToDictionary(w => w.Id, w => w); - var ids = request.Ids.GroupBy(i => i.idWell); + var ids = request.Ids.GroupBy(i => i.idWell, i => i.idSection); + + var idsWells = request.Ids.Select(i => i.idWell); + + var allSections = await wellOperationRepository.GetSectionsAsync(idsWells, token); foreach (var id in ids) { - var well = await wellService.GetOrDefaultAsync(id.Key, cancellationToken); + var well = await wellService.GetOrDefaultAsync(id.Key, token); if (well is null) continue; - var wellOperations = await GetFactOperationsAsync(well.Id, id.Select(i => i.idSection), cancellationToken); - var groupedOperations = wellOperations.GroupBy(o => o.IdWellSectionType); - var wellWellbores = groupedOperations.Select(group => new WellboreDto { - Id = group.Key, - Name = sections[group.Key].Caption, + var wellTimezoneOffset = TimeSpan.FromHours(well.Timezone.Hours); + + var wellFactSections = allSections + .Where(section => section.IdWell == id.Key) + .Where(section => section.IdType == WellOperation.IdOperationTypeFact); + + var idsSections = id + .Where(i => i.HasValue) + .Select(i => i!.Value); + + if (idsSections.Any()) + wellFactSections = wellFactSections + .Where(section => idsSections.Contains(section.IdWellSectionType)); + + var wellWellbores = wellFactSections.Select(section => new WellboreDto { + Id = section.IdWellSectionType, + Name = sections[section.IdWellSectionType].Caption, Well = well.Adapt(), - DateStart = group.Min(operation => operation.DateStart).ToUtcDateTimeOffset(well.Timezone.Hours).ToOffset(TimeSpan.FromHours(well.Timezone.Hours)), - DateEnd = group.Max(operation => operation.DateStart.AddHours(operation.DurationHours)).ToUtcDateTimeOffset(well.Timezone.Hours).ToOffset(TimeSpan.FromHours(well.Timezone.Hours)), - DepthStart = group.Min(operation => operation.DepthStart), - DepthEnd = group.Max(operation => operation.DepthEnd), + DateStart = section.DateStart.ToOffset(wellTimezoneOffset), + DateEnd = section.DateEnd.ToOffset(wellTimezoneOffset), + DepthStart = section.DepthStart, + DepthEnd = section.DepthEnd, }); + wellbores.AddRange(wellWellbores); } @@ -71,22 +88,4 @@ public class WellboreService : IWellboreService .OrderBy(w => w.Well.Id).ThenBy(w => w.Id) .Skip(skip).Take(take); } - - private async Task> GetFactOperationsAsync(int idWell, IEnumerable idsSections, - CancellationToken cancellationToken) - { - var request = new WellOperationRequest - { - IdWell = idWell, - OperationType = WellOperation.IdOperationTypeFact, - SortFields = new[] { "DateStart asc" }, - }; - - request.SectionTypeIds = idsSections.All(i => i.HasValue) - ? idsSections.Select(i => i!.Value) - : null; - - return (await wellOperationRepository.GetAsync(request, cancellationToken)) - .OrderBy(o => o.DateStart); - } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs b/AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs new file mode 100644 index 00000000..3ab159e0 --- /dev/null +++ b/AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs @@ -0,0 +1,144 @@ +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using System; +using System.Data.Common; +using System.Data; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using AsbCloudInfrastructure.Background; +using Microsoft.Extensions.DependencyInjection; + +namespace AsbCloudInfrastructure.Services; + +public class WorkLimitingParameterCalc : Work +{ + public WorkLimitingParameterCalc() + : base("Limiting parameter calc") + { + Timeout = TimeSpan.FromMinutes(30); + } + + protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) + { + using var db = services.GetRequiredService(); + var lastDetectedDates = await db.LimitingParameter + .GroupBy(o => o.IdTelemetry) + .Select(g => new + { + IdTelemetry = g.Key, + LastDate = g.Max(o => o.DateEnd) + }) + .ToListAsync(token); + + var telemetryIds = await db.Telemetries + .Where(t => t.Info != null && t.TimeZone != null) + .Select(t => t.Id) + .ToListAsync(token); + + var telemetryLastDetectedDates = telemetryIds + .GroupJoin(lastDetectedDates, + t => t, + o => o.IdTelemetry, + (outer, inner) => new + { + IdTelemetry = outer, + inner.SingleOrDefault()?.LastDate, + }); + + var count = telemetryLastDetectedDates.Count(); + var i = 0d; + foreach (var item in telemetryLastDetectedDates) + { + onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++/count); + var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); + if (newLimitingParameters?.Any() == true) + { + db.LimitingParameter.AddRange(newLimitingParameters); + await db.SaveChangesAsync(token); + } + } + } + + private static async Task> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + { + var query = + $"select " + + $"limiting_parameters.date, limiting_parameters.id_feed_regulator, limiting_parameters.well_depth " + + $"from ( " + + $"select " + + $"date, id_feed_regulator, well_depth, " + + $"lag(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lag, " + + $"lead(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lead " + + $"from t_telemetry_data_saub " + + $"where id_feed_regulator is not null " + + $"and id_telemetry = {idTelemetry}" + + $"and date >= '{begin:u}'" + + $"order by date) as limiting_parameters " + + $"where id_feed_regulator_lag is null " + + $"or (id_feed_regulator != id_feed_regulator_lag and id_feed_regulator_lead != id_feed_regulator_lag) " + + $"order by date;"; + + var limitingParameters = new List(32); + using (var result = await ExecuteReaderAsync(db, query, token)) + { + LimitingParameter? limitingLast = null; + while (result.Read()) + { + var date = result.GetFieldValue(0); + var idLimiting = result.GetFieldValue(1); + var wellDepth = result.GetFieldValue(2); + + if (limitingLast is null) + { + limitingLast = new LimitingParameter + { + DateStart = date, + DepthStart = wellDepth, + IdFeedRegulator = idLimiting + }; + } + + if (limitingLast.IdFeedRegulator != idLimiting || limitingLast.DepthStart < wellDepth) + { + limitingParameters.Add(new LimitingParameter { + IdTelemetry = idTelemetry, + IdFeedRegulator = limitingLast.IdFeedRegulator, + DateStart = limitingLast.DateStart, + DateEnd = date, + DepthStart = limitingLast.DepthStart, + DepthEnd = wellDepth + }); + + limitingLast = new LimitingParameter + { + DateStart = date, + DepthStart = wellDepth, + IdFeedRegulator = idLimiting + }; + } + } + } + + return limitingParameters; + } + + private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) + { + var connection = db.Database.GetDbConnection(); + if ( + connection?.State is null || + connection.State == ConnectionState.Broken || + connection.State == ConnectionState.Closed) + { + await db.Database.OpenConnectionAsync(token); + connection = db.Database.GetDbConnection(); + } + using var command = connection.CreateCommand(); + command.CommandText = query; + + var result = await command.ExecuteReaderAsync(token); + return result; + } +} diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index c1c3cc8f..d00de488 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -15,7 +15,6 @@ using AsbCloudInfrastructure.Services.SAUB; namespace AsbCloudInfrastructure { - public class Startup { public static void BeforeRunHandler(IHost host) @@ -32,11 +31,11 @@ namespace AsbCloudInfrastructure _ = provider.GetRequiredService>(); var backgroundWorker = provider.GetRequiredService(); - backgroundWorker.Push(WellInfoService.MakeWork()); - backgroundWorker.Push(OperationDetectionWorkFactory.MakeWork()); - backgroundWorker.Push(SubsystemOperationTimeCalcWorkFactory.MakeWork()); - backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork()); - backgroundWorker.Push(MakeMemoryMonitoringWork()); + backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(15)); + backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1)); var notificationBackgroundWorker = provider.GetRequiredService(); @@ -48,17 +47,15 @@ namespace AsbCloudInfrastructure }); } - static WorkPeriodic MakeMemoryMonitoringWork() + static Work MakeMemoryMonitoringWork() { - var workId = "Memory monitoring"; - var workAction = (string _, IServiceProvider _, CancellationToken _) => { + var workAction = (string _, IServiceProvider _, Action _, CancellationToken _) => { var bytes = GC.GetTotalMemory(false); var bytesString = FromatBytes(bytes); System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDbContext.ReferenceCount}"); return Task.CompletedTask; }; - var workPeriod = TimeSpan.FromMinutes(1); - var work = new WorkPeriodic(workId, workAction, workPeriod); + var work = Work.CreateByDelegate("Memory monitoring", workAction); return work; } diff --git a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs index 96556845..b118e179 100644 --- a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs +++ b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs @@ -1,4 +1,6 @@ -using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Data; +using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Requests; using AsbCloudApp.Services; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -35,12 +37,22 @@ namespace AsbCloudWebApi.Tests.Middlware public class TelemetryDataSaubService : ITelemetryDataSaubService { - public async Task?> GetAsync(int idWell, DateTime dateBegin = default, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default) + public async Task> GetAsync(int idWell, DateTime dateBegin = default, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default) { await Task.Delay(1000, token); return Enumerable.Empty(); } + public Task> GetAsync(int idWell, TelemetryDataRequest request, CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task GetRangeAsync(int idWell, DateTimeOffset start, DateTimeOffset end, CancellationToken token) + { + throw new NotImplementedException(); + } + public Task> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token) => throw new NotImplementedException(); public Task GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token) diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs index 133f6b36..f4a03f2f 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs @@ -88,7 +88,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(10); - Assert.True(work.ExecutionTime > TimeSpan.Zero); + Assert.True(work.LastExecutionTime > TimeSpan.Zero); } [Fact] diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs index 12bc81a2..54f5d19c 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs @@ -366,7 +366,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); Assert.Equal(2, state.IdState); - backgroundWorkerMock.Verify(s => s.Push(It.IsAny())); + backgroundWorkerMock.Verify(s => s.Push(It.IsAny())); } [Fact] diff --git a/AsbCloudWebApi/Controllers/BackgroundWork.cs b/AsbCloudWebApi/Controllers/BackgroundWork.cs new file mode 100644 index 00000000..316d796e --- /dev/null +++ b/AsbCloudWebApi/Controllers/BackgroundWork.cs @@ -0,0 +1,34 @@ +using AsbCloudApp.Data; +using AsbCloudInfrastructure.Background; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Linq; + +namespace AsbCloudWebApi.Controllers +{ + [Route("api/[controller]")] + [Authorize] + [ApiController] + public class BackgroundWork : ControllerBase + { + private readonly BackgroundWorker backgroundWorker; + + public BackgroundWork(BackgroundWorker backgroundWorker) + { + this.backgroundWorker = backgroundWorker; + } + + [HttpGet] + //[ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + public IActionResult GetAll() + { + var result = new { + CurrentWork = (BackgroundWorkDto?)backgroundWorker.CurrentWork, + RunOnceQueue = backgroundWorker.WorkStore.RunOnceQueue.Select(work => (BackgroundWorkDto)work), + Periodics = backgroundWorker.WorkStore.Periodics.Select(work => (BackgroundWorkDto)work.Work), + Felled = backgroundWorker.WorkStore.Felled.Select(work => (BackgroundWorkDto)work), + }; + return Ok(result); + } + } +} diff --git a/AsbCloudWebApi/Controllers/SlipsStatController.cs b/AsbCloudWebApi/Controllers/SlipsStatController.cs index 124622a3..1049749d 100644 --- a/AsbCloudWebApi/Controllers/SlipsStatController.cs +++ b/AsbCloudWebApi/Controllers/SlipsStatController.cs @@ -34,7 +34,6 @@ namespace AsbCloudWebApi.Controllers /// Токен отмены задачи /// Список бурильщиков [HttpGet] - [Permission] [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] public async Task GetAllAsync( [FromQuery] OperationStatRequest request, diff --git a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs index b418c0b5..962e5e0f 100644 --- a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs +++ b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs @@ -29,19 +29,19 @@ public class SignalRNotificationTransportService : INotificationTransportService { var workId = HashCode.Combine(notifications.Select(n => n.Id)).ToString("x"); - if (backgroundWorker.Contains(workId)) + if (backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId)) return Task.CompletedTask; var workAction = MakeSignalRSendWorkAction(notifications); - var work = new WorkBase(workId, workAction); - backgroundWorker.Push(work); + var work = Work.CreateByDelegate(workId, workAction); + backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); return Task.CompletedTask; } - private Func MakeSignalRSendWorkAction(IEnumerable notifications) + private Func, CancellationToken, Task> MakeSignalRSendWorkAction(IEnumerable notifications) { - return async (_, serviceProvider, cancellationToken) => + return async (_, serviceProvider, onProgress, cancellationToken) => { var notificationPublisher = serviceProvider.GetRequiredService(); diff --git a/ConsoleApp1/DebugWellOperationsStatService.cs b/ConsoleApp1/DebugWellOperationsStatService.cs deleted file mode 100644 index acb6452c..00000000 --- a/ConsoleApp1/DebugWellOperationsStatService.cs +++ /dev/null @@ -1,40 +0,0 @@ -using AsbCloudApp.Data; -using System; -using System.Collections.Generic; - -namespace ConsoleApp1 -{ - public static class DebugWellOperationsStatService - { - public static void Main(/*string[] args*/) - { - //var options = new DbContextOptionsBuilder() - // .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True") - // .Options; - //using var db = new AsbCloudDbContext(options); - //var cacheDb = new CacheDb(); - //var telemetryService = new TelemetryService(db, new TelemetryTracker(cacheDb), cacheDb); - //var wellService = new WellService(db, telemetryService, cacheDb); - //var wellOptsStat = new OperationsStatService(db, cacheDb, wellService); - //var tvd = wellOptsStat.GetTvdAsync(1, default).Result; - //Print(tvd); - } - - private static void Print(IEnumerable> tvd) - { - Console.WriteLine("|\tplan\t|\tfact\t|\tprog\t|"); - Console.WriteLine("|:-------------:|:-------------:|:-------------:|"); - foreach (var item in tvd) - Print(item); - } - - private static void Print(PlanFactPredictBase item) - { - static string GetText(WellOperationDto item) - => (item is null) - ? " --------- " - : $"{item.IdCategory} d:{item.DepthStart} "; - Console.WriteLine($"|\t{GetText(item.Plan)}\t|\t{GetText(item.Fact)}\t|\t{GetText(item.Predict)}\t|"); - } - } -}