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|");
- }
- }
-}