Merge branch 'dev' into feature/28518041-rtk-report

This commit is contained in:
on.nemtina 2024-02-12 17:02:36 +05:00
commit 13c545a7b9
30 changed files with 1065 additions and 732 deletions

View File

@ -1,100 +1,85 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Data.DetectedOperation
namespace AsbCloudApp.Data.DetectedOperation;
/// <summary>
/// Автоматически определенная операция
/// </summary>
public class DetectedOperationDto: IId
{
/// <inheritdoc/>
[Required]
public int Id { get; set; }
/// <summary>
/// Автоматически определяемая операция
/// Id телеметрии
/// </summary>
public class DetectedOperationDto : IId, IWellRelated
{
/// <inheritdoc/>
[Required]
public int Id { get; set; }
[Required]
public int IdTelemetry { get; set; }
/// <inheritdoc/>
[Required]
public int IdWell { get; set; }
/// <summary>
/// Id названия/описания операции
/// </summary>
[Required]
public int IdCategory { get; set; }
/// <summary>
/// Id телеметрии
/// </summary>
[Required]
public int IdTelemetry { get; set; }
/// <summary>
/// Id пользователя панели на момент начала операции
/// </summary>
[Required]
public int IdUserAtStart { get; set; }
/// <summary>
/// Id названия/описания операции
/// </summary>
[Required]
public int IdCategory { get; set; }
/// <summary>
/// Дата завершения операции в часовом поясе скважины
/// </summary>
[Required]
public DateTimeOffset DateEnd { get; set; }
/// <summary>
/// Id пользователя панели
/// </summary>
[Required]
public int IdUsersAtStart { get; set; }
/// <summary>
/// Дата начала операции в часовом поясе скважины
/// </summary>
[Required]
public DateTimeOffset DateStart { get; set; }
/// <summary>
/// Дата начала операции в часовом поясе скважины
/// </summary>
[Required]
public DateTime DateStart { get; set; }
/// <summary>
/// глубина на завершения операции, м
/// </summary>
[Required]
public double DepthEnd { get; set; }
/// <summary>
/// Дата завершения операции в часовом поясе скважины
/// </summary>
[Required]
public DateTime DateEnd { get; set; }
/// <summary>
/// глубина на начало операции, м
/// </summary>
[Required]
public double DepthStart { get; set; }
/// <summary>
/// Продолжительность операции в минутах
/// </summary>
[Required]
public double DurationMinutes => (DateEnd - DateStart).TotalMinutes;
/// <summary>
/// Продолжительность операции в минутах
/// </summary>
[Required]
public double DurationMinutes => (DateEnd - DateStart).TotalMinutes;
/// <summary>
/// глубина на начало операции, м
/// </summary>
[Required]
public double DepthStart { get; set; }
/// <summary>
/// Флаг включенной подсистемы
/// </summary>
[Required]
public int EnabledSubsystems { get; set; }
/// <summary>
/// глубина на завершения операции, м
/// </summary>
[Required]
public double DepthEnd { get; set; }
/// <summary>
/// название/описание операции
/// </summary>
[Required]
public WellOperationCategoryDto OperationCategory { get; set; } = null!;
/// <summary>
/// название/описание операции
/// </summary>
[Required]
public WellOperationCategoryDto OperationCategory { get; set; } = null!;
/// <summary>
/// Пользователь панели оператора
/// </summary>
public string? TelemetryUserName { get; set; }
/// <summary>
/// Пользователь панели оператора
/// </summary>
public string? TelemetryUserName { get; set; }
/// <summary>
/// Бурильщик
/// </summary>
public DrillerDto? Driller { get; set; }
/// <summary>
/// Целевые/нормативные показатели
/// </summary>
public OperationValueDto? OperationValue { get; set; }
/// <summary>
/// Ключевой параметр операции
/// </summary>
[Required]
public double Value { get; set; }
/// <summary>
/// Флаг включенной подсистемы
/// </summary>
[Required]
public int EnabledSubsystems { get; set; }
}
}
/// <summary>
/// Ключевой параметр операции
/// </summary>
[Required]
public double Value { get; set; }
}

View File

@ -13,7 +13,7 @@ namespace AsbCloudApp.Data.DetectedOperation
/// Список всех операций
/// </summary>
[Required]
public IEnumerable<DetectedOperationDto> Operations { get; set; } = Enumerable.Empty<DetectedOperationDto>();
public IEnumerable<DetectedOperationWithDrillerDto> Operations { get; set; } = Enumerable.Empty<DetectedOperationWithDrillerDto>();
/// <summary>
/// Статистика по бурильщикам

View File

@ -0,0 +1,18 @@
namespace AsbCloudApp.Data.DetectedOperation
{
/// <summary>
/// Автоматически определяемая операция
/// </summary>
public class DetectedOperationWithDrillerDto : DetectedOperationDto
{
/// <summary>
/// Бурильщик
/// </summary>
public DrillerDto? Driller { get; set; }
/// <summary>
/// Целевые/нормативные показатели
/// </summary>
public OperationValueDto? OperationValue { get; set; }
}
}

View File

@ -1,25 +1,32 @@
using System;
namespace AsbCloudApp.Data
{
/// <summary>
/// временная зона
/// временная зона
/// </summary>
public class SimpleTimezoneDto
{
/// <summary>
/// смещение в часах относительно UTC
/// смещение в часах относительно UTC
/// </summary>
public double Hours { get; set; }
/// <summary>
/// идентификатор часовой зоны
/// идентификатор часовой зоны
/// </summary>
public string? TimezoneId { get; set; }
/// <summary>
/// запрет на переопределение
/// запрет на переопределение
/// </summary>
public bool IsOverride { get; set; }
/// <summary>
/// Смещение часового пояса
/// </summary>
public TimeSpan Offset => TimeSpan.FromHours(Hours);
/// <inheritdoc/>
public override bool Equals(object? obj)
{

View File

@ -86,6 +86,14 @@ namespace AsbCloudApp.Data
second = fullDate.Second;
}
/// <inheritdoc/>
public TimeDto(DateTimeOffset fullDate)
{
hour = fullDate.Hour;
minute = fullDate.Minute;
second = fullDate.Second;
}
/// <summary>
/// Makes System.TimeOnly
/// </summary>

View File

@ -14,6 +14,7 @@ namespace AsbCloudApp.Exceptions
/// </summary>
public IDictionary<string, string[]> ErrorState { get; } = null!;
// TODO: swap arguments, inherit from ArgumentException
/// <summary>
/// конструктор
/// </summary>

View File

@ -0,0 +1,66 @@
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Requests;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace AsbCloudApp.Repositories;
/// <summary>
/// Таблица автоматически определенных операций
/// </summary>
public interface IDetectedOperationRepository
{
/// <summary>
/// Добавление записей
/// </summary>
/// <param name="idUser"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> Insert(int? idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
/// <summary>
/// Получить автоматически определенные операции по телеметрии
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<DetectedOperationDto>> Get(DetectedOperationByTelemetryRequest request, CancellationToken token);
/// <summary>
/// Редактирование записей
/// </summary>
/// <param name="idUser"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> Update(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
/// <summary>
/// Добавляет Dto у которых id == 0, изменяет dto у которых id != 0
/// </summary>
/// <param name="idUser"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> UpdateOrInsert(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
/// <summary>
/// Удалить операции
/// </summary>
/// <param name="idUser"></param>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> Delete(int idUser, DetectedOperationByTelemetryRequest request, CancellationToken token);
/// <summary>
/// Удаление записей
/// </summary>
/// <param name="idUser"></param>
/// <param name="ids"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token);
}

View File

@ -0,0 +1,17 @@
using AsbCloudApp.Data;
using System.Collections.Generic;
namespace AsbCloudApp.Repositories
{
/// <summary>
/// сервис операций по скважине
/// </summary>
public interface IWellOperationCategoryRepository
{
/// <summary>
/// список названий операций
/// </summary>
/// <returns></returns>
IEnumerable<WellOperationCategoryDto> Get(bool includeParents);
}
}

View File

@ -13,10 +13,12 @@ namespace AsbCloudApp.Repositories
/// </summary>
public interface IWellOperationRepository
{
//TODO: replace all references
/// <summary>
/// список названий операций
/// </summary>
/// <returns></returns>
[Obsolete("use IWellOperationCategoryRepository.GetCategories(bool includeParents)")]
IEnumerable<WellOperationCategoryDto> GetCategories(bool includeParents);
/// <summary>

View File

@ -1,56 +1,114 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace AsbCloudApp.Requests
namespace AsbCloudApp.Requests;
/// <summary>
/// Запрос на получение операций определенных по телеметрии
/// </summary>
public class DetectedOperationByTelemetryRequest : DetectedOperationRequest
{
/// <summary>
/// Параметры запроса на получение операций определенных по телеметрии
/// id телеметрии
/// </summary>
public class DetectedOperationRequest : RequestBase
[Required]
public int IdTelemetry { get; set; }
/// <summary>
/// Запрос на получение операций определенных по id телеметрии
/// </summary>
public DetectedOperationByTelemetryRequest()
{}
/// <summary>
/// Запрос на получение операций определенных по id телеметрии. Copy
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="request"></param>
public DetectedOperationByTelemetryRequest(int idTelemetry, DetectedOperationRequest request)
:base(request)
{
/// <summary>
/// категория операций
/// </summary>
[Required]
public int IdWell { get; set; }
/// <summary>
/// Список id телеметрий
/// пустой список - нет фильтрации
/// </summary>
public IEnumerable<int> IdsTelemetries { get; set; } = Array.Empty<int>();
/// <summary>
/// категории операций
/// </summary>
public IEnumerable<int> IdsCategories { get; set; } = Array.Empty<int>();
/// <summary>
/// Больше или равно дате
/// </summary>
public DateTimeOffset? GeDateStart { get; set; }
/// <summary>
/// Меньше или равно дате
/// </summary>
public DateTimeOffset? LeDateEnd { get; set; }
/// <summary>
/// Больше или равно глубины забоя
/// </summary>
public double? GeDepth { get; set; }
/// <summary>
/// Меньше или равно глубины забоя
/// </summary>
public double? LeDepth { get; set; }
/// <summary>
/// Фильтр по пользователю панели
/// </summary>
public int? IdTelemetryUser { get; set; }
IdTelemetry = idTelemetry;
}
}
/// <summary>
/// Запрос на получение операций определенных по id скважины
/// </summary>
public class DetectedOperationByWellRequest : DetectedOperationRequest
{
/// <summary>
/// id скважины
/// </summary>
[Required]
public int IdWell { get; set; }
/// <summary>
/// Запрос на получение операций определенных по id скважины
/// </summary>
public DetectedOperationByWellRequest()
{}
/// <summary>
/// Запрос на получение операций определенных по id скважины. Copy
/// </summary>
public DetectedOperationByWellRequest(int idWell, DetectedOperationRequest request)
: base(request)
{
IdWell = idWell;
}
}
/// <summary>
/// Запрос на получение операций определенных по телеметрии
/// </summary>
public class DetectedOperationRequest : RequestBase
{
/// <summary>
/// категории операций
/// </summary>
public IEnumerable<int> IdsCategories { get; set; }
/// <summary>
/// Больше или равно дате
/// </summary>
public DateTimeOffset? GeDateStart { get; set; }
/// <summary>
/// Меньше или равно дате
/// </summary>
public DateTimeOffset? LeDateEnd { get; set; }
/// <summary>
/// Больше или равно глубины забоя
/// </summary>
public double? GeDepthStart { get; set; }
/// <summary>
/// Меньше или равно глубины забоя
/// </summary>
public double? LeDepthEnd { get; set; }
/// <summary>
/// Запрос на получение операций определенных по телеметрии
/// </summary>
public DetectedOperationRequest()
{
IdsCategories = new List<int>();
}
/// <summary>
/// Запрос на получение операций определенных по телеметрии. Copy
/// </summary>
/// <param name="request"></param>
public DetectedOperationRequest(DetectedOperationRequest request)
: base(request)
{
IdsCategories = request.IdsCategories;
GeDateStart = request.GeDateStart;
LeDateEnd = request.LeDateEnd;
GeDepthStart = request.GeDepthStart;
LeDepthEnd = request.LeDepthEnd;
}
}

View File

@ -23,5 +23,23 @@ namespace AsbCloudApp.Requests
/// Указать направление сортировки можно через пробел "asc" или "desc"
/// </summary>
public IEnumerable<string>? SortFields { get; set; }
/// <summary>
/// Базовые параметры запроса
/// </summary>
public RequestBase()
{
}
/// <summary>
/// Базовые параметры запроса. Копирующий конструктор
/// </summary>
/// <param name="request"></param>
public RequestBase(RequestBase request)
{
Skip = request.Skip;
Take = request.Take;
SortFields = request.SortFields;
}
}
}

View File

@ -19,7 +19,7 @@ namespace AsbCloudApp.Services
/// <param name="idWell"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<WellOperationCategoryDto>?> GetCategoriesAsync(int? idWell, CancellationToken token);
Task<IEnumerable<WellOperationCategoryDto>> GetCategoriesAsync(int? idWell, CancellationToken token);
/// <summary>
/// Получить автоматически определенные по телеметрии операции с анализом по бурильщикам
@ -27,7 +27,7 @@ namespace AsbCloudApp.Services
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<DetectedOperationListDto?> GetAsync(DetectedOperationRequest request, CancellationToken token);
Task<DetectedOperationListDto> GetAsync(DetectedOperationByWellRequest request, CancellationToken token);
/// <summary>
/// Получить автоматически определенные по телеметрии операции
@ -35,7 +35,7 @@ namespace AsbCloudApp.Services
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<DetectedOperationDto>> GetOperationsAsync(DetectedOperationRequest request, CancellationToken token);
Task<IEnumerable<DetectedOperationWithDrillerDto>> GetOperationsAsync(DetectedOperationByWellRequest request, CancellationToken token);
/// <summary>
/// Удалить операции
@ -43,7 +43,7 @@ namespace AsbCloudApp.Services
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> DeleteAsync(DetectedOperationRequest request, CancellationToken token);
Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token);
/// <summary>
/// Статистика по операциям
@ -51,6 +51,6 @@ namespace AsbCloudApp.Services
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<DetectedOperationStatDto>?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token);
Task<IEnumerable<DetectedOperationStatDto>> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token);
}
}

View File

@ -12,6 +12,18 @@ namespace AsbCloudApp.Services
/// </summary>
public interface ITelemetryDataSaubService : ITelemetryDataService<TelemetryDataSaubDto>
{
/// <summary>
/// Получение телеметрии для РТК статистики
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="isBitOnBottom"></param>
/// <param name="geDate"></param>
/// <param name="leDate"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TelemetryDataSaubDto>> Get(int idTelemetry, bool isBitOnBottom, DateTimeOffset geDate, DateTimeOffset leDate, int take, CancellationToken token);
/// <summary>
/// усредненная статистика по 1м за весь период
/// <para>

View File

@ -0,0 +1,34 @@
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudDb
{
public static class EFExtensionsExceptionHandling
{
public static async Task<int> SaveChangesWithExceptionHandling(this IAsbCloudDbContext db, CancellationToken token)
{
try
{
var result = await db.SaveChangesAsync(token);
return result;
}
catch (DbUpdateException ex)
{
if (ex.InnerException is PostgresException pgException)
TryConvertPostgresExceptionToValidateException(pgException);
throw;
}
}
private static void TryConvertPostgresExceptionToValidateException(PostgresException pgException)
{
if (pgException.SqlState == PostgresErrorCodes.ForeignKeyViolation)
// TODO: replace ArgumentException by new Exception
throw new ArgumentException(pgException.Message + "\r\n" + pgException.Detail, "dtos");
}
}
}

View File

@ -7,7 +7,7 @@ using System.Reflection;
namespace AsbCloudDb
{
public static class EFExtentionsSortBy
public static class EFExtensionsSortBy
{
struct TypeAcessor
{

View File

@ -1,10 +1,11 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
using Microsoft.EntityFrameworkCore;
using AsbCloudInfrastructure.Services.DetectOperations;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
@ -19,7 +20,6 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
/// </summary>
internal class WorkDataSaubStat : Work
{
private int MechanicalDrillingCategoryId = 4001;
private int Gap = 60;
public WorkDataSaubStat() : base("Generate DataSaubStat entries and save them into Db")
@ -29,7 +29,6 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
{
using var db = services.GetRequiredService<IAsbCloudDbContext>();
var telemetryDataCache = services.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
@ -43,96 +42,102 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
return;
var dataSaubStatRepo = services.GetRequiredService<IDataSaubStatRepository>();
var dataSaubService = services.GetRequiredService<ITelemetryDataSaubService>();
var detectedOperationRepository = services.GetRequiredService<IDetectedOperationRepository>();
var stats = await dataSaubStatRepo.GetLastsAsync(idTelemetries, token);
for( var i =0; i < idTelemetries.Length; i++)
{
var idTelemetry = idTelemetries[i];
var lastDate = stats.FirstOrDefault(s => s.IdTelemetry == idTelemetry)?.DateEnd ?? DateTimeOffset.UnixEpoch;
var statsCount = await CreateStatForTelemetryFromDate(db, idTelemetry, lastDate, dataSaubStatRepo, token);
var lastDate = stats.FirstOrDefault(s => s.IdTelemetry == idTelemetry)?.DateEnd.ToUniversalTime() ?? DateTimeOffset.UnixEpoch;
var statsCount = await CreateStatForTelemetryFromDate(idTelemetry, lastDate, dataSaubService, dataSaubStatRepo, detectedOperationRepository, token);
onProgressCallback($"Calculate stat for telemetry: {idTelemetry}; from {lastDate}; results count: {statsCount};", 100*i / idTelemetries.Length);
}
}
private async Task<int> CreateStatForTelemetryFromDate(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset begin, IDataSaubStatRepository dataSaubStatRepo, CancellationToken token)
private static async Task<int> CreateStatForTelemetryFromDate(
int idTelemetry,
DateTimeOffset begin,
ITelemetryDataSaubService dataSaubService,
IDataSaubStatRepository dataSaubStatRepo,
IDetectedOperationRepository detectedOperationRepository,
CancellationToken token)
{
var detectedOperations = await db.Set<DetectedOperation>()
.Where(o => o.IdTelemetry == idTelemetry)
.Where(o => o.DateStart > begin)
.Where(o => o.OperationCategory.IdParent == MechanicalDrillingCategoryId)
.OrderBy(o => o.DateStart)
.Take(250)
.ToArrayAsync(token);
var detectedOperationRequest = new DetectedOperationByTelemetryRequest {
GeDateStart = begin,
IdTelemetry = idTelemetry,
IdsCategories = WellOperationCategory.MechanicalDrillingSubIds,
SortFields = new[] {nameof(DetectedOperation.DateStart) },
Take = 250,
};
var detectedOperations = await detectedOperationRepository.Get(detectedOperationRequest, token);
if (!detectedOperations.Any())
return 0;
var minDate = detectedOperations.First().DateStart;
var maxDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd;
var geDate = detectedOperations.First().DateStart;
var leDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd;
var telemetryDataSaub = await db.Set<TelemetryDataSaub>()
.Where(t => t.IdTelemetry == idTelemetry)
.Where(t => t.DateTime >= minDate)
.Where(t => t.DateTime <= maxDate)
.Where(t => Math.Abs(t.BitDepth - t.WellDepth) < 0.0001)
.OrderBy(t => t.DateTime)
.Take(100_000)
.ToArrayAsync(token);
var dataSaub = await dataSaubService.Get(idTelemetry, true, geDate, leDate, 100_000, token);
if (!telemetryDataSaub.Any())
if (!dataSaub.Any())
return 0;
var dataSaubStats = CreateDataSaubStat(detectedOperations, telemetryDataSaub);
if(dataSaub is not TelemetryDataSaubDto[] dataSaubArray)
dataSaubArray = dataSaub.ToArray();
var dataSaubStats = CreateDataSaubStat(detectedOperations, dataSaubArray);
return await dataSaubStatRepo.InsertRangeAsync(dataSaubStats, token);
}
private static IEnumerable<DataSaubStatDto> CreateDataSaubStat(IEnumerable<DetectedOperation> detectedOperations, TelemetryDataSaub[] telemetryDataSaub)
private static IEnumerable<DataSaubStatDto> CreateDataSaubStat(IEnumerable<DetectedOperationDto> detectedOperations, TelemetryDataSaubDto[] dataSaub)
{
var indexStart = 0;
var indexEnd = 0;
var result = new List<DataSaubStatDto>();
if (!telemetryDataSaub.Any())
if (!dataSaub.Any())
return result;
foreach (var operation in detectedOperations)
{
indexStart = Array.FindIndex(telemetryDataSaub, indexEnd, t => t.DateTime >= operation.DateStart);
indexStart = Array.FindIndex(dataSaub, indexEnd, t => t.DateTime >= operation.DateStart);
if (indexStart < 0)
break;
indexEnd = Array.FindIndex(telemetryDataSaub, indexStart, t => t.DateTime > operation.DateEnd);
indexEnd = Array.FindIndex(dataSaub, indexStart, t => t.DateTime > operation.DateEnd);
if (indexEnd < 0)
indexEnd = telemetryDataSaub.Length - 1;
indexEnd = dataSaub.Length - 1;
if (indexEnd == indexStart)
continue;
var length = indexEnd - indexStart;
var subset = telemetryDataSaub.AsSpan(indexStart, length);
var subset = dataSaub.AsSpan(indexStart, length);
var stats = CalcStats(operation, subset);
result.AddRange(stats);
}
return result;
}
private static IEnumerable<DataSaubStatDto> CalcStats(DetectedOperation operation, Span<TelemetryDataSaub> telemetryDataSaub)
private static IEnumerable<DataSaubStatDto> CalcStats(DetectedOperationDto operation, Span<TelemetryDataSaubDto> dataSaub)
{
var result = new List<DataSaubStatDto>();
var indexStart = 0;
for (var i = 1; i < telemetryDataSaub.Length; i++)
for (var i = 1; i < dataSaub.Length; i++)
{
var previous = telemetryDataSaub[i - 1];
var current = telemetryDataSaub[i];
var previous = dataSaub[i - 1];
var current = dataSaub[i];
if (IsNewCacheItem(previous, current) || i == telemetryDataSaub.Length - 1)
if (IsNewCacheItem(previous, current) || i == dataSaub.Length - 1)
{
var length = i - indexStart;
var span = telemetryDataSaub.Slice(indexStart, length);
var span = dataSaub.Slice(indexStart, length);
indexStart = i;
if (length <= 2 || (span[^1].WellDepth - span[0].WellDepth) < 0.001)
continue; // мелкие выборки не учитываем.
@ -144,10 +149,9 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
return result;
}
private static DataSaubStatDto CalcStat(DetectedOperation operation, Span<TelemetryDataSaub> span)
private static DataSaubStatDto CalcStat(DetectedOperationDto operation, Span<TelemetryDataSaubDto> span)
{
var hasOscillation = operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyHasOscillation, out object? hasOscillationObject)
&& hasOscillationObject is true;
var hasOscillation = EnabledSubsystemsFlags.AutoOscillation.HasEnabledSubsystems(operation.EnabledSubsystems);
var aggregatedValues = CalcAggregate(span);
var processMapDrillingCacheItem = new DataSaubStatDto
@ -184,7 +188,7 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
double RotorTorque,
double RotorSpeed,
double Flow
) CalcAggregate(Span<TelemetryDataSaub> span)
) CalcAggregate(Span<TelemetryDataSaubDto> span)
{
var sumPressure = 0.0;
var sumAxialLoad = 0.0;
@ -210,7 +214,7 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
);
}
private static bool IsNewCacheItem(TelemetryDataSaub previous, TelemetryDataSaub current)
private static bool IsNewCacheItem(TelemetryDataSaubDto previous, TelemetryDataSaubDto current)
{
return !(current.Mode == previous.Mode)
|| !(current.WellDepth >= previous.WellDepth)

View File

@ -331,6 +331,8 @@ namespace AsbCloudInfrastructure
services.AddTransient<IProcessMapPlanService<ProcessMapPlanWellReamDto>, ProcessMapPlanService<ProcessMapPlanWellReamDto>>();
services.AddTransient<IWellSectionPlanRepository, WellSectionPlanRepository>();
services.AddTransient<IWellOperationCategoryRepository, WellOperationCategoryRepository>();
services.AddTransient<IDetectedOperationRepository, DetectedOperationRepository>();
services.AddSingleton<ParserServiceFactory>();

View File

@ -18,11 +18,11 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
where TEntity : ChangeLogAbstract
where TRequest : ChangeLogBaseRequest
{
protected readonly IAsbCloudDbContext context;
protected readonly IAsbCloudDbContext db;
public ChangeLogRepositoryAbstract(IAsbCloudDbContext context)
public ChangeLogRepositoryAbstract(IAsbCloudDbContext db)
{
this.context = context;
this.db = db;
}
public async Task<int> InsertRange(int idUser, IEnumerable<TDto> dtos, CancellationToken token)
@ -30,9 +30,10 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
var result = 0;
if (dtos.Any())
{
using var transaction = await db.Database.BeginTransactionAsync(token);
var entities = dtos.Select(Convert);
var creation = DateTimeOffset.UtcNow;
var dbSet = context.Set<TEntity>();
var dbSet = db.Set<TEntity>();
foreach (var entity in entities)
{
entity.Id = default;
@ -46,6 +47,7 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
}
result += await SaveChangesWithExceptionHandling(token);
await transaction.CommitAsync(token);
}
return result;
}
@ -62,9 +64,8 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
var ids = dtos.Select(d => d.Id);
using var transaction = context.Database.BeginTransaction();
var result = 0;
var dbSet = context
var dbSet = db
.Set<TEntity>();
var entitiesToDelete = await dbSet
@ -80,13 +81,14 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
throw new ArgumentInvalidException(nameof(dtos), $"записи с id:[{stringnotFoundIds}] не найдены, или не актуальны.");
}
using var transaction = db.Database.BeginTransaction();
foreach (var entity in entitiesToDelete)
{
entity.IdState = ChangeLogAbstract.IdStateReplaced;
entity.Obsolete = updateTime;
entity.IdEditor = idUser;
}
result += await context.SaveChangesAsync(token);
result += await db.SaveChangesAsync(token);
var entitiesNew = dtos.Select(Convert);
foreach (var entity in entitiesNew)
@ -143,7 +145,7 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
public async Task<int> ClearAndInsertRange(int idUser, TRequest request, IEnumerable<TDto> dtos, CancellationToken token)
{
var result = 0;
var transaction = await context.Database.BeginTransactionAsync(token);
using var transaction = await db.Database.BeginTransactionAsync(token);
result += await Clear(idUser, request, token);
result += await InsertRange(idUser, dtos, token);
await transaction.CommitAsync(token);
@ -153,7 +155,7 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
public async Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token)
{
var updateTime = DateTimeOffset.UtcNow;
var query = context.Set<TEntity>()
var query = db.Set<TEntity>()
.Where(e => ids.Contains(e.Id))
.Where(e => e.Obsolete == null);
@ -269,7 +271,7 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
{
try
{
var result = await context.SaveChangesAsync(token);
var result = await db.SaveChangesAsync(token);
return result;
}
catch (DbUpdateException ex)

View File

@ -0,0 +1,191 @@
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository;
public class DetectedOperationRepository : IDetectedOperationRepository
{
private readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService;
public DetectedOperationRepository(
IAsbCloudDbContext db,
ITelemetryService telemetryService)
{
this.db = db;
this.telemetryService = telemetryService;
}
public async Task<int> Delete(int idUser, DetectedOperationByTelemetryRequest request, CancellationToken token)
{
var query = BuildQuery(request);
db.Set<DetectedOperation>().RemoveRange(query);
return await db.SaveChangesAsync(token);
}
public async Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token)
{
var query = db.Set<DetectedOperation>()
.Where(e => ids.Contains( e.Id));
db.Set<DetectedOperation>()
.RemoveRange(query);
return await db.SaveChangesAsync(token);
}
public async Task<IEnumerable<DetectedOperationDto>> Get(DetectedOperationByTelemetryRequest request, CancellationToken token)
{
var query = BuildQuery(request)
.Include(o => o.OperationCategory);
var entities = await query.ToArrayAsync(token);
var offset = telemetryService.GetTimezone(request.IdTelemetry).Offset;
var dtos = entities.Select(o => Convert(o, offset));
return dtos;
}
public async Task<int> Insert(int? idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token)
{
if(!dtos.Any())
return 0;
var entities = dtos.Select(Convert);
var dbset = db.Set<DetectedOperation>();
foreach(var entity in entities)
{
entity.Id = default;
dbset.Add(entity);
}
return await db.SaveChangesWithExceptionHandling(token);
}
public async Task<int> Update(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token)
{
if (!dtos.Any())
return 0;
var ids = dtos
.Select(o => o.Id)
.Distinct()
.ToArray();
if (ids.Any(id => id == default))
throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь Id");
if (ids.Length != dtos.Count())
throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь уникальные Id");
var dbSet = db.Set<DetectedOperation>();
var existingEntitiesCount = await dbSet
.Where(o => ids.Contains(o.Id))
.CountAsync(token);
if (ids.Length != existingEntitiesCount)
throw new ArgumentInvalidException(nameof(dtos), "Все записи должны существовать в БД");
var entities = dtos
.Select(Convert)
.ToArray();
var entries = new Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<DetectedOperation>[entities.Length];
for(var i = 0; i < entities.Length; i++)
entries[i] = dbSet.Update(entities[i]);
var result = await db.SaveChangesWithExceptionHandling(token);
for (var i = 0; i < entries.Length; i++)
entries[i].State = EntityState.Detached;
return result;
}
public async Task<int> UpdateOrInsert(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token)
{
var result = 0;
var itemsToInsert = dtos.Where(e => e.Id == 0);
if (itemsToInsert.Any())
result += await Insert(idUser, itemsToInsert, token);
var itemsToUpdate = dtos.Where(e => e.Id != 0);
if (itemsToUpdate.Any())
result += await Update(idUser, itemsToUpdate, token);
return result;
}
private IQueryable<DetectedOperation> BuildQuery(DetectedOperationByTelemetryRequest request)
{
var query = db.Set<DetectedOperation>()
.Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.IdsCategories.Any())
query = query.Where(o => request.IdsCategories.Contains(o.IdCategory));
if (request.GeDepthStart is not null)
query = query.Where(o => o.DepthStart >= request.GeDepthStart);
if (request.LeDepthEnd is not null)
query = query.Where(o => o.DepthEnd <= request.LeDepthEnd);
if (request.GeDateStart is not null)
{
var geDate = request.GeDateStart.Value.ToUniversalTime();
query = query.Where(o => o.DateStart >= geDate);
}
if (request.LeDateEnd is not null)
{
var leDate = request.LeDateEnd.Value.ToUniversalTime();
query = query.Where(o => o.DateEnd <= leDate);
}
if (request.SortFields?.Any() == true)
{
query = query.SortBy(request.SortFields);
}
else
query = query
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthStart);
if (request.Skip.HasValue)
query = query.Skip((int)request.Skip);
if (request.Take.HasValue)
query = query.Take((int)request.Take);
return query;
}
private static DetectedOperationDto Convert(DetectedOperation entity, TimeSpan offset)
{
var dto = entity.Adapt<DetectedOperationDto>();
dto.DateStart = entity.DateStart.ToOffset(offset);
dto.DateEnd = entity.DateEnd.ToOffset(offset);
return dto;
}
private static DetectedOperation Convert(DetectedOperationDto dto)
{
var entity = dto.Adapt<DetectedOperation>();
entity.DateStart = dto.DateStart.ToUniversalTime();
entity.DateEnd = dto.DateEnd.ToUniversalTime();
return entity;
}
}

View File

@ -23,7 +23,7 @@ public class ProcessMapPlanBaseRepository<TDto, TEntity> : ChangeLogRepositoryAb
protected override IQueryable<TEntity> BuildQuery(ProcessMapPlanBaseRequestWithWell request)
{
var query = context
var query = db
.Set<TEntity>()
.Include(e => e.Author)
.Include(e => e.Editor)

View File

@ -0,0 +1,44 @@
using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudInfrastructure.Repository;
public class WellOperationCategoryRepository : IWellOperationCategoryRepository
{
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
public WellOperationCategoryRepository(IAsbCloudDbContext db, IMemoryCache memoryCache)
{
this.db = db;
this.memoryCache = memoryCache;
}
public IEnumerable<WellOperationCategoryDto> Get(bool includeParents)
{
var categories = memoryCache
.GetOrCreateBasic(db.Set<WellOperationCategory>());
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<IEnumerable<WellOperationCategoryDto>>();
return result;
}
}

View File

@ -28,35 +28,20 @@ public class WellOperationRepository : IWellOperationRepository
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly IWellService wellService;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService)
public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService, IWellOperationCategoryRepository wellOperationCategoryRepository)
{
this.db = db;
this.memoryCache = memoryCache;
this.wellService = wellService;
this.wellOperationCategoryRepository = wellOperationCategoryRepository;
}
/// <inheritdoc/>
public IEnumerable<WellOperationCategoryDto> GetCategories(bool includeParents)
{
var categories = memoryCache
.GetOrCreateBasic(db.Set<WellOperationCategory>());
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<IEnumerable<WellOperationCategoryDto>>();
return result;
return wellOperationCategoryRepository.Get(includeParents);
}
/// <inheritdoc/>

View File

@ -282,7 +282,7 @@ public class DailyReportService : IDailyReportService
var leDateEnd = dailyReport.Date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified);
dailyReport.TimeBalanceBlock.WellOperationSlipsTimeCount = (await detectedOperationService.GetAsync(
new DetectedOperationRequest
new DetectedOperationByWellRequest
{
IdsCategories = new[] { idWellOperationSlipsTime },
IdWell = dailyReport.IdWell,

View File

@ -1,6 +1,5 @@
using AsbCloudDb.Model;
using ClosedXML.Excel;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
@ -9,22 +8,21 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
using AsbCloudApp.Repositories;
using Microsoft.AspNetCore.Http.Extensions;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Services;
using AsbCloudApp.Data;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
namespace AsbCloudInfrastructure.Services.DetectOperations;
public class DetectedOperationExportService
{
private readonly DetectorAbstract[] detectors =
{
new DetectorDrilling(),
new DetectorSlipsTime()
};
private const int headerRowsCount = 1;
private readonly IAsbCloudDbContext db;
private readonly IWellService wellService;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
private const int headerRowsCount = 1;
private const string cellDepositName = "B1";
private const string cellClusterName = "B2";
@ -42,14 +40,14 @@ public class DetectedOperationExportService
private const int columnIdReasonOfEnd = 9;
private const int columnComment = 10;
//TODO: удалить неиспользуемую зависимость
private readonly IAsbCloudDbContext dbContext;
private readonly IWellOperationRepository wellOperationRepository;
public DetectedOperationExportService(IAsbCloudDbContext dbContext, IWellOperationRepository wellOperationRepository)
public DetectedOperationExportService(
IAsbCloudDbContext db,
IWellService wellService,
IWellOperationCategoryRepository wellOperationCategoryRepository)
{
this.dbContext = dbContext;
this.wellOperationRepository = wellOperationRepository;
this.db = db;
this.wellService = wellService;
this.wellOperationCategoryRepository = wellOperationCategoryRepository;
}
/// <summary>
@ -57,15 +55,12 @@ public class DetectedOperationExportService
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="host">хост</param>
/// <param name="cancellationToken"></param>
/// <param name="token"></param>
/// <returns></returns>
/// <exception cref="ArgumentInvalidException"></exception>
public async Task<Stream> ExportAsync(int idWell, string host, CancellationToken cancellationToken)
public async Task<Stream> ExportAsync(int idWell, string host, CancellationToken token)
{
var well = await dbContext.Set<Well>()
.Include(w => w.Cluster)
.ThenInclude(c => c.Deposit)
.FirstOrDefaultAsync(w => w.Id == idWell, cancellationToken);
var well = await wellService.GetOrDefaultAsync(idWell, token);
if (well is null)
throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} does not exist");
@ -73,19 +68,19 @@ public class DetectedOperationExportService
if (!well.IdTelemetry.HasValue)
throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} has no telemetry");
var operations = await DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, cancellationToken);
var operations = await WorkOperationDetection.DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, db, token);
return await GenerateExcelFileStreamAsync(well, host, operations, cancellationToken);
return await GenerateExcelFileStreamAsync(well, host, operations, token);
}
private async Task<Stream> GenerateExcelFileStreamAsync(Well well, string host, IEnumerable<OperationDetectorResult> operationDetectorResults,
private async Task<Stream> GenerateExcelFileStreamAsync(WellDto well, string host, IEnumerable<DetectedOperation> operationDetectorResults,
CancellationToken cancellationToken)
{
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
using var workbook = new XLWorkbook(excelTemplateStream);
await AddToWorkbookAsync(workbook, well, host, operationDetectorResults, cancellationToken);
AddToWorkbook(workbook, well, host, operationDetectorResults);
MemoryStream memoryStream = new MemoryStream();
workbook.SaveAs(memoryStream, new SaveOptions { });
@ -93,36 +88,34 @@ public class DetectedOperationExportService
return memoryStream;
}
private async Task AddToWorkbookAsync(XLWorkbook workbook, Well well, string host, IEnumerable<OperationDetectorResult> operationDetectorResults,
CancellationToken cancellationToken)
private void AddToWorkbook(XLWorkbook workbook, WellDto well, string host, IEnumerable<DetectedOperation> operations)
{
const string sheetName = "Операции";
if (!operationDetectorResults.Any())
if (!operations.Any())
return;
var sheet = workbook.GetWorksheet(sheetName);
await AddToSheetAsync(sheet, well, host, operationDetectorResults
.OrderBy(x => x.Operation.DateStart).ThenBy(x => x.Operation.DepthStart).ToArray(),
cancellationToken);
var orderedOperations = operations
.OrderBy(x => x.DateStart)
.ThenBy(x => x.DepthStart).ToArray();
AddToSheet(sheet, well, host, orderedOperations);
}
private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, string host, IList<OperationDetectorResult> operationDetectorResults,
CancellationToken cancellationToken)
private void AddToSheet(IXLWorksheet sheet, WellDto well, string host, IList<DetectedOperation> operations)
{
var wellOperationCategories = await dbContext.WellOperationCategories.ToListAsync(cancellationToken);
var wellOperationCategories = wellOperationCategoryRepository.Get(true);
sheet.Cell(cellDepositName).SetCellValue(well.Cluster.Deposit.Caption);
sheet.Cell(cellClusterName).SetCellValue(well.Cluster.Caption);
sheet.Cell(cellDepositName).SetCellValue(well.Deposit);
sheet.Cell(cellClusterName).SetCellValue(well.Cluster);
sheet.Cell(cellWellName).SetCellValue(well.Caption);
sheet.Cell(cellDeltaDate).SetCellValue(operationDetectorResults.Max(o => o.Operation.DateEnd) - operationDetectorResults.Min(o => o.Operation.DateStart));
sheet.Cell(cellDeltaDate).SetCellValue((TimeSpan)(Enumerable.Max<DetectedOperation, DateTimeOffset>(operations, (Func<DetectedOperation, DateTimeOffset>)(o => o.DateEnd)) - Enumerable.Min<DetectedOperation, DateTimeOffset>(operations, (Func<DetectedOperation, DateTimeOffset>)(o => o.DateStart))));
var detectedOperations = operationDetectorResults.Select(o => o.Operation).ToArray();
for (int i = 0; i < operationDetectorResults.Count; i++)
for (int i = 0; i < operations.Count; i++)
{
var current = detectedOperations[i];
var current = operations[i];
var dateStart = current.DateStart.ToRemoteDateTime(well.Timezone.Hours);
var dateEnd = current.DateEnd.ToRemoteDateTime(well.Timezone.Hours);
@ -153,17 +146,18 @@ public class DetectedOperationExportService
var link = $"{host}/well/{well.Id}/telemetry/monitoring{query}";
row.Cell(columnDateStart).SetHyperlink(link);
var deltaDepth = i > 0 && i + 1 < detectedOperations.Length
? current.DepthStart - detectedOperations[i - 1].DepthEnd
var deltaDepth = i > 0 && i + 1 < operations.Count
? current.DepthStart - operations[i - 1].DepthEnd
: 0;
row.Cell(columnDeltaDepth).SetCellValue(deltaDepth);
var comment = CreateComment(operationDetectorResults[i]);
var comment = CreateComment(operations[i]);
row.Cell(columnComment).SetCellValue(comment);
}
}
private static string GetCategoryName(IEnumerable<WellOperationCategory> wellOperationCategories, DetectedOperation current)
private static string GetCategoryName(IEnumerable<WellOperationCategoryDto> wellOperationCategories, DetectedOperation current)
{
var idCategory = current.IdCategory;
if (idCategory == WellOperationCategory.IdSlide &&
@ -204,9 +198,8 @@ public class DetectedOperationExportService
return memoryStream;
}
private static string CreateComment(OperationDetectorResult operationDetectorResult)
private static string CreateComment(DetectedOperation operation)
{
var operation = operationDetectorResult.Operation;
switch (operation.IdCategory)
{
case WellOperationCategory.IdRotor:
@ -224,69 +217,4 @@ public class DetectedOperationExportService
return string.Empty;
}
}
private async Task<IEnumerable<OperationDetectorResult>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin,
CancellationToken token)
{
var query = dbContext.TelemetryDataSaub
.AsNoTracking()
.Where(d => d.IdTelemetry == idTelemetry)
.Where(d => d.BlockPosition >= 0)
.Select(d => new DetectableTelemetry
{
DateTime = d.DateTime,
IdUser = d.IdUser,
WellDepth = d.WellDepth,
Pressure = d.Pressure,
HookWeight = d.HookWeight,
BlockPosition = d.BlockPosition,
BitDepth = d.BitDepth,
RotorSpeed = d.RotorSpeed,
})
.OrderBy(d => d.DateTime);
var startDate = begin;
var detectedOperationResults = new List<OperationDetectorResult>(8);
DetectedOperation? lastDetectedOperation = null;
const int minOperationLength = 5;
const int maxDetectorsInterpolationFrameLength = 30;
const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;
while (true)
{
var data = await query
.Where(d => d.DateTime > startDate)
.ToArrayAsync(token);
if (data.Length < gap)
break;
var isDetected = false;
var positionBegin = 0;
var positionEnd = data.Length - gap;
while (positionEnd > positionBegin)
{
foreach (var detector in detectors)
{
if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result))
continue;
detectedOperationResults.Add(result!);
lastDetectedOperation = result!.Operation;
isDetected = true;
positionBegin = result.TelemetryEnd;
break;
}
positionBegin += 1;
}
if (isDetected)
startDate = lastDetectedOperation!.DateEnd;
else
startDate = data[positionEnd].DateTime;
}
return detectedOperationResults;
}
}

View File

@ -1,5 +1,6 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
@ -11,274 +12,200 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.DetectOperations
namespace AsbCloudInfrastructure.Services.DetectOperations;
public class DetectedOperationService : IDetectedOperationService
{
private readonly IDetectedOperationRepository operationRepository;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
private readonly IWellService wellService;
private readonly IRepositoryWellRelated<OperationValueDto> operationValueService;
private readonly IScheduleRepository scheduleService;
public class DetectedOperationService : IDetectedOperationService
public DetectedOperationService(
IDetectedOperationRepository operationRepository,
IWellOperationCategoryRepository wellOperationCategoryRepository,
IWellService wellService,
IRepositoryWellRelated<OperationValueDto> operationValueRepository,
IScheduleRepository scheduleRepository)
{
private readonly IAsbCloudDbContext db;
private readonly IWellService wellService;
private readonly IRepositoryWellRelated<OperationValueDto> operationValueService;
private readonly IScheduleRepository scheduleService;
this.operationRepository = operationRepository;
this.wellOperationCategoryRepository = wellOperationCategoryRepository;
this.wellService = wellService;
this.operationValueService = operationValueRepository;
this.scheduleService = scheduleRepository;
}
public DetectedOperationService(IAsbCloudDbContext db, IWellService wellService,
IRepositoryWellRelated<OperationValueDto> operationValueService, IScheduleRepository scheduleService)
public async Task<DetectedOperationListDto> GetAsync(DetectedOperationByWellRequest request, CancellationToken token)
{
var dtos = await GetOperationsAsync(request, token);
if (dtos?.Any() != true)
return new DetectedOperationListDto();
var stats = GetOperationsDrillersStat(dtos);
var result = new DetectedOperationListDto
{
this.db = db;
this.wellService = wellService;
this.operationValueService = operationValueService;
this.scheduleService = scheduleService;
Operations = dtos,
Stats = stats
};
return result;
}
public async Task<IEnumerable<DetectedOperationWithDrillerDto>> GetOperationsAsync(DetectedOperationByWellRequest request, CancellationToken token)
{
var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
if (well?.IdTelemetry is null)
return Enumerable.Empty<DetectedOperationWithDrillerDto>();
var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request);
var data = await operationRepository.Get(requestByTelemetry, token);
var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
var schedules = await scheduleService.GetByIdWellAsync(request.IdWell, token);
var dtos = data.Select(o => Convert(o, operationValues, schedules));
return dtos;
}
public async Task<IEnumerable<WellOperationCategoryDto>> GetCategoriesAsync(int? idWell, CancellationToken token)
{
if(idWell is null)
{
return wellOperationCategoryRepository.Get(false);
}
public async Task<DetectedOperationListDto?> GetAsync(DetectedOperationRequest request, CancellationToken token)
else
{
var dtos = await GetOperationsAsync(request, token);
if (dtos?.Any() != true)
return null;
var stats = GetOperationsDrillersStat(dtos);
var result = new DetectedOperationListDto
{
Operations = dtos,
Stats = stats
};
return result;
}
public async Task<IEnumerable<DetectedOperationDto>> GetOperationsAsync(DetectedOperationRequest request, CancellationToken token)
{
var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
var well = await wellService.GetOrDefaultAsync((int )idWell, token);
if (well?.IdTelemetry is null)
return Enumerable.Empty<DetectedOperationDto>();
return Enumerable.Empty<WellOperationCategoryDto>();
var query = BuildQuery(well, request).AsNoTracking();
var data = await query.ToListAsync(token);
var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
var schedules = await scheduleService.GetByIdWellAsync(request.IdWell, token);
var dtos = data.Select(o => Convert(o, well, operationValues, schedules));
return dtos;
}
private static IEnumerable<DetectedOperationDrillersStatDto> GetOperationsDrillersStat(IEnumerable<DetectedOperationDto> operations)
{
var groups = operations.GroupBy(o => o.Driller);
var stats = new List<DetectedOperationDrillersStatDto>(groups.Count());
foreach (var group in groups)
{
var itemsWithTarget = group.Where(i => i.OperationValue is not null);
var stat = new DetectedOperationDrillersStatDto
{
Driller = group.Key,
AverageValue = group.Sum(e => e.Value) / group.Count(),
Count = group.Count(),
};
if (itemsWithTarget.Any())
{
var itemsOutOfTarget = itemsWithTarget.Where(o => !IsTargetOk(o));
stat.AverageTargetValue = itemsWithTarget.Average(e => e.OperationValue?.TargetValue);
stat.Efficiency = 100d * itemsOutOfTarget.Count() / itemsWithTarget.Count();
stat.Loss = itemsOutOfTarget.Sum(DeltaToTarget);
}
stats.Add(stat);
}
return stats;
}
public async Task<IEnumerable<DetectedOperationStatDto>?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token)
{
var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
if (well?.IdTelemetry is null || well.Timezone is null)
return null;
var query = BuildQuery(well, request)
?.AsNoTracking();
if (query is null)
return null;
var entities = await query
.Select(o => new {
o.IdCategory,
DurationMinutes = (o.DateEnd - o.DateStart).TotalMinutes,
o.Value,
})
.ToListAsync(token);
if (!entities.Any())
return null;
var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
var categories = await query
.Select(o => new {o.IdCategory, o.OperationCategory.Name })
.Distinct()
.ToDictionaryAsync(c=>c.IdCategory, c=>c.Name, token);
var dtos = entities
.GroupBy(o => o.IdCategory)
.OrderBy(g => g.Key)
.Select(g => new DetectedOperationStatDto{
IdCategory = g.Key,
Category = categories[g.Key],
Count = g.Count(),
MinutesAverage = g.Average(o => o.DurationMinutes),
MinutesMin = g.Min(o => o.DurationMinutes),
MinutesMax = g.Max(o => o.DurationMinutes),
MinutesTotal = g.Sum(o => o.DurationMinutes),
ValueAverage = g.Average(o => o.Value),
ValueMax = g.Max(o => o.Value),
ValueMin = g.Min(o => o.Value),
});
return dtos;
}
public async Task<int> DeleteAsync(DetectedOperationRequest request, CancellationToken token)
{
var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
if (well?.IdTelemetry is null || well.Timezone is null)
return 0;
var query = BuildQueryBase(well, request);
if (query is null)
return 0;
db.DetectedOperations.RemoveRange(query);
return await db.SaveChangesAsync(token);
}
private static bool IsTargetOk(DetectedOperationDto op)
{
return (op.IdCategory) switch
{
WellOperationCategory.IdRotor => op.Value > op.OperationValue?.TargetValue,
WellOperationCategory.IdSlide => op.Value > op.OperationValue?.TargetValue,
WellOperationCategory.IdSlipsTime => op.Value > op.OperationValue?.TargetValue,
_ => op.Value > op.OperationValue?.TargetValue,
var request = new DetectedOperationByTelemetryRequest()
{
IdTelemetry = well.IdTelemetry.Value
};
}
private static double DeltaToTarget(DetectedOperationDto op)
{
return (op.IdCategory) switch
{
WellOperationCategory.IdRotor => 0,
WellOperationCategory.IdSlide => 0,
WellOperationCategory.IdSlipsTime => op.Value - op.OperationValue?.TargetValue??0,
_ => 0,
};
}
private IQueryable<DetectedOperation> BuildQueryBase(WellDto well, DetectedOperationRequest request)
{
var query = db.Set<DetectedOperation>()
.Where(o => o.IdTelemetry == well.IdTelemetry);
if (request.IdsTelemetries.Any())
{
query = query
.Union(db.Set<DetectedOperation>().Where(o => request.IdsTelemetries.Contains(o.IdTelemetry)));
}
if (request.IdsCategories.Any())
query = query.Where(o => request.IdsCategories.Contains(o.IdCategory));
if (request.GeDateStart is not null)
query = query.Where(o => o.DateStart >= request.GeDateStart.Value.Date.ToUtcDateTimeOffset(well.Timezone.Hours));
if (request.LeDateEnd is not null)
query = query.Where(o => o.DateEnd <= request.LeDateEnd.Value.Date.ToUtcDateTimeOffset(well.Timezone.Hours));
if (request.GeDepth is not null)
query = query.Where(o => o.DepthStart >= request.GeDepth);
if (request.LeDepth is not null)
query = query.Where(o => o.DepthEnd <= request.LeDepth);
if (request.IdTelemetryUser is not null)
query = query.Where(o => o.IdUsersAtStart == request.IdTelemetryUser);
return query;
}
private IQueryable<DetectedOperation> BuildQuery(WellDto well, DetectedOperationRequest request)
{
IQueryable<DetectedOperation> query = BuildQueryBase(well, request)
.Include(o => o.OperationCategory);
if (request.SortFields?.Any() == true)
{
query = query.SortBy(request.SortFields);
}
else
query = query
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthStart);
if (request.Skip.HasValue)
query = query.Skip((int)request.Skip);
if (request.Take.HasValue)
query = query.Take((int)request.Take);
return query;
}
private static DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable<OperationValueDto> operationValues, IEnumerable<ScheduleDto> schedules)
{
var dto = operation.Adapt<DetectedOperationDto>();
dto.IdWell = well.Id;
var dateStart = operation.DateStart.ToRemoteDateTime(well.Timezone.Hours);
dto.DateStart = dateStart;
dto.DateEnd = operation.DateEnd.ToRemoteDateTime(well.Timezone.Hours);
dto.OperationValue = operationValues.FirstOrDefault(v => v.IdOperationCategory == dto.IdCategory
&& v.DepthStart <= dto.DepthStart
&& v.DepthEnd > dto.DepthStart);
var timeStart = new TimeDto(dateStart);
var driller = schedules.FirstOrDefault(s =>
s.DrillStart <= dateStart &&
s.DrillEnd > dateStart && (
s.ShiftStart > s.ShiftEnd
) ^ (s.ShiftStart <= timeStart &&
s.ShiftEnd > timeStart
))
?.Driller;
dto.Driller = driller;
return dto;
}
public async Task<IEnumerable<WellOperationCategoryDto>?> GetCategoriesAsync(int? idWell, CancellationToken token)
{
IQueryable<WellOperationCategory> query;
if(idWell is null)
{
query = db.WellOperationCategories;
}
else
{
var well = await wellService.GetOrDefaultAsync((int )idWell, token);
if (well?.IdTelemetry is null)
return null;
var idTelemetry = (int)well.IdTelemetry;
query = db.DetectedOperations
.Include(o => o.OperationCategory)
.Where(o => o.IdTelemetry == idTelemetry)
.Select(o => o.OperationCategory)
.Distinct();
}
var result = await query
.Where(c => c.Id < 1000)
.AsNoTracking()
.Select(c => c.Adapt<WellOperationCategoryDto>())
.ToArrayAsync(token);
return result;
var operations = await operationRepository.Get(request, token);
var categories = operations
.Select(o => o.OperationCategory)
.Distinct();
return categories;
}
}
public async Task<IEnumerable<DetectedOperationStatDto>> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token)
{
var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
if (well?.IdTelemetry is null || well.Timezone is null)
return Enumerable.Empty<DetectedOperationStatDto>();
var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request);
var operations = await operationRepository.Get(requestByTelemetry, token);
if (!operations.Any())
return Enumerable.Empty<DetectedOperationStatDto>();
var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
var dtos = operations
.GroupBy(o => (o.IdCategory, o.OperationCategory.Name))
.OrderBy(g => g.Key)
.Select(g => new DetectedOperationStatDto
{
IdCategory = g.Key.IdCategory,
Category = g.Key.Name,
Count = g.Count(),
MinutesAverage = g.Average(o => o.DurationMinutes),
MinutesMin = g.Min(o => o.DurationMinutes),
MinutesMax = g.Max(o => o.DurationMinutes),
MinutesTotal = g.Sum(o => o.DurationMinutes),
ValueAverage = g.Average(o => o.Value),
ValueMax = g.Max(o => o.Value),
ValueMin = g.Min(o => o.Value),
});
return dtos;
}
public async Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token)
{
var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
if (well?.IdTelemetry is null || well.Timezone is null)
return 0;
var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request);
var result = await operationRepository.Delete(-1, requestByTelemetry, token);
return result;
}
private static IEnumerable<DetectedOperationDrillersStatDto> GetOperationsDrillersStat(IEnumerable<DetectedOperationWithDrillerDto> operations)
{
var groups = operations.GroupBy(o => o.Driller);
var stats = new List<DetectedOperationDrillersStatDto>(groups.Count());
foreach (var group in groups)
{
var itemsWithTarget = group.Where(i => i.OperationValue is not null);
var stat = new DetectedOperationDrillersStatDto
{
Driller = group.Key,
AverageValue = group.Sum(e => e.Value) / group.Count(),
Count = group.Count(),
};
if (itemsWithTarget.Any())
{
var itemsOutOfTarget = itemsWithTarget.Where(o => !IsTargetOk(o));
stat.AverageTargetValue = itemsWithTarget.Average(e => e.OperationValue?.TargetValue);
stat.Efficiency = 100d * itemsOutOfTarget.Count() / itemsWithTarget.Count();
stat.Loss = itemsOutOfTarget.Sum(DeltaToTarget);
}
stats.Add(stat);
}
return stats;
}
private static bool IsTargetOk(DetectedOperationWithDrillerDto op)
{
return (op.IdCategory) switch
{
WellOperationCategory.IdRotor => op.Value > op.OperationValue?.TargetValue,
WellOperationCategory.IdSlide => op.Value > op.OperationValue?.TargetValue,
WellOperationCategory.IdSlipsTime => op.Value > op.OperationValue?.TargetValue,
_ => op.Value > op.OperationValue?.TargetValue,
};
}
private static double DeltaToTarget(DetectedOperationWithDrillerDto op)
{
return (op.IdCategory) switch
{
WellOperationCategory.IdRotor => 0,
WellOperationCategory.IdSlide => 0,
WellOperationCategory.IdSlipsTime => op.Value - op.OperationValue?.TargetValue??0,
_ => 0,
};
}
private static DetectedOperationWithDrillerDto Convert(DetectedOperationDto operation, IEnumerable<OperationValueDto> operationValues, IEnumerable<ScheduleDto> schedules)
{
var dto = operation.Adapt<DetectedOperationWithDrillerDto>();
dto.OperationValue = operationValues.FirstOrDefault(v => v.IdOperationCategory == dto.IdCategory
&& v.DepthStart <= dto.DepthStart
&& v.DepthEnd > dto.DepthStart);
var dateStart = dto.DateStart;
var timeStart = new TimeDto(dateStart);
var driller = schedules.FirstOrDefault(s =>
s.DrillStart <= dateStart &&
s.DrillEnd > dateStart && (
s.ShiftStart > s.ShiftEnd
) ^ (s.ShiftStart <= timeStart &&
s.ShiftEnd > timeStart
))
?.Driller;
dto.Driller = driller;
return dto;
}
}

View File

@ -86,7 +86,8 @@ public class WorkOperationDetection: Work
}
}
private static async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
//todo: move this logic to DetectedOperationsService
internal static async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
{
var query = db.TelemetryDataSaub
.AsNoTracking()

View File

@ -1,5 +1,4 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
@ -11,142 +10,163 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.Csv;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB
namespace AsbCloudInfrastructure.Services.SAUB;
public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSaubDto, TelemetryDataSaub>, ITelemetryDataSaubService
{
private readonly ITelemetryUserService telemetryUserService;
public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSaubDto, TelemetryDataSaub>, ITelemetryDataSaubService
public TelemetryDataSaubService(
IAsbCloudDbContext db,
ITelemetryService telemetryService,
ITelemetryUserService telemetryUserService,
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
: base(db, telemetryService, telemetryDataCache)
{
private readonly ITelemetryUserService telemetryUserService;
public TelemetryDataSaubService(
IAsbCloudDbContext db,
ITelemetryService telemetryService,
ITelemetryUserService telemetryUserService,
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
: base(db, telemetryService, telemetryDataCache)
{
this.telemetryUserService = telemetryUserService;
}
public async Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token)
{
var timezone = telemetryService.GetTimezone(idTelemetry);
var timezoneOffset = TimeSpan.FromHours(timezone.Hours);
int[] modes = new int[] { 0, 1, 3 };
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(1.5));
var query = db.Set<TelemetryDataSaub>()
.Where(t => t.IdTelemetry == idTelemetry)
.Where(t => t.BlockPosition > 0.0001)
.Where(t => t.WellDepth > 0.0001)
.Where(t => modes.Contains(t.Mode))
.Where(t => t.WellDepth - t.BitDepth < 0.01)
.GroupBy(t => new {
t.DateTime.Hour,
WellDepthX10 = Math.Truncate(t.WellDepth * 10),
t.Mode,
t.IdFeedRegulator})
.Select(g => new TelemetryDataSaubStatDto
{
Count = g.Count(),
IdMode = g.Key.Mode,
IdFeedRegulator = g.Key.IdFeedRegulator,
DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
WellDepthMin = g.Min(t => t.WellDepth),
WellDepthMax = g.Max(t => t.WellDepth),
Pressure = g.Average(t => t.Pressure),
PressureSp = g.Average(t => t.PressureSp!.Value),
PressureIdle = g.Average(t => t.PressureIdle!.Value),
PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value),
PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value),
PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value),
AxialLoad = g.Average(t => t.AxialLoad),
AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value),
AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value),
RotorTorque = g.Average(t => t.RotorTorque),
RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value),
RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value),
BlockSpeed = g.Average(t => t.BlockSpeed!.Value),
BlockSpeedSp = g.Average(t => t.BlockSpeedSp!.Value),
})
.Where(s => s.WellDepthMin != s.WellDepthMax)
.Where(s => s.Count > 3)
.OrderBy(t => t.DateMin);
return await query.ToArrayAsync(token);
}
public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
{
var entity = src.Adapt<TelemetryDataSaub>();
var telemetryUser = telemetryUserService
.GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User))
.FirstOrDefault();
entity.IdUser = telemetryUser?.Id;
entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset);
return entity;
}
public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset)
{
var dto = src.Adapt<TelemetryDataSaubDto>();
var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser??int.MinValue);
dto.User = telemetryUser?.MakeDisplayName();
dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset);
dto.BitDepth = src.BitDepth <= src.WellDepth
? src.BitDepth
: src.WellDepth;
return dto;
}
public async Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)
{
double intervalSec = (endDate - beginDate).TotalSeconds;
if (intervalSec > 60*60*24*3)
throw new ArgumentInvalidException(nameof(endDate), "Слишком большой диапазон");
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина id:{idWell} не содержит телеметрии");
var approxPointsCount = intervalSec switch
{
< 2048 => 2048,
< 8_192 => 4_096,
< 131_072 => 16_384,
_ => 32_768
};
var data = await GetAsync(idWell, beginDate, intervalSec, approxPointsCount, token );
var fileName = $"DataSaub idWell{idWell}";
if (telemetry.Info is not null)
fileName += $" {telemetry.Info?.Cluster}, {telemetry.Info?.Well}";
fileName += $" {beginDate:yyyy-MM-DDTHH-mm} - {endDate:yyyy-MM-DDTHH-mm}.csv";
var outStream = new MemoryStream();
using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
{
var entryFile = archive.CreateEntry(fileName, CompressionLevel.Optimal);
using var entryStream = entryFile.Open();
var serializer = new CsvSerializer<TelemetryDataSaubDto>();
serializer.Serialize(data, entryStream);
}
outStream.Seek(0, SeekOrigin.Begin);
return outStream;
}
this.telemetryUserService = telemetryUserService;
}
public async Task<IEnumerable<TelemetryDataSaubDto>> Get(int idTelemetry, bool isBitOnBottom, DateTimeOffset geDate, DateTimeOffset leDate, int take, CancellationToken token)
{
var offset = telemetryService.GetTimezone(idTelemetry).Offset;
var geDateUtc = geDate.ToUniversalTime();
var leDateUtc = leDate.ToUniversalTime();
var query = db.Set<TelemetryDataSaub>()
.Where(t => t.IdTelemetry == idTelemetry)
.Where(t => t.DateTime >= geDateUtc)
.Where(t => t.DateTime <= leDateUtc);
if (isBitOnBottom)
query = query.Where(t => Math.Abs(t.BitDepth - t.WellDepth) < 0.0001);
query = query
.OrderBy(t => t.DateTime)
.Take(take);
var entities = await query.ToArrayAsync(token);
var dtos = entities.Select(e => Convert(e, offset.TotalHours));
return dtos;
}
public async Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token)
{
var timezone = telemetryService.GetTimezone(idTelemetry);
var timezoneOffset = TimeSpan.FromHours(timezone.Hours);
int[] modes = new int[] { 0, 1, 3 };
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(1.5));
var query = db.Set<TelemetryDataSaub>()
.Where(t => t.IdTelemetry == idTelemetry)
.Where(t => t.BlockPosition > 0.0001)
.Where(t => t.WellDepth > 0.0001)
.Where(t => modes.Contains(t.Mode))
.Where(t => t.WellDepth - t.BitDepth < 0.01)
.GroupBy(t => new
{
t.DateTime.Hour,
WellDepthX10 = Math.Truncate(t.WellDepth * 10),
t.Mode,
t.IdFeedRegulator
})
.Select(g => new TelemetryDataSaubStatDto
{
Count = g.Count(),
IdMode = g.Key.Mode,
IdFeedRegulator = g.Key.IdFeedRegulator,
DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
WellDepthMin = g.Min(t => t.WellDepth),
WellDepthMax = g.Max(t => t.WellDepth),
Pressure = g.Average(t => t.Pressure),
PressureSp = g.Average(t => t.PressureSp!.Value),
PressureIdle = g.Average(t => t.PressureIdle!.Value),
PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value),
PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value),
PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value),
AxialLoad = g.Average(t => t.AxialLoad),
AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value),
AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value),
RotorTorque = g.Average(t => t.RotorTorque),
RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value),
RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value),
BlockSpeed = g.Average(t => t.BlockSpeed!.Value),
BlockSpeedSp = g.Average(t => t.BlockSpeedSp!.Value),
})
.Where(s => s.WellDepthMin != s.WellDepthMax)
.Where(s => s.Count > 3)
.OrderBy(t => t.DateMin);
return await query.ToArrayAsync(token);
}
public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
{
var entity = src.Adapt<TelemetryDataSaub>();
var telemetryUser = telemetryUserService
.GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User))
.FirstOrDefault();
entity.IdUser = telemetryUser?.Id;
entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset);
return entity;
}
public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset)
{
var dto = src.Adapt<TelemetryDataSaubDto>();
var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser ?? int.MinValue);
dto.User = telemetryUser?.MakeDisplayName();
dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset);
dto.BitDepth = src.BitDepth <= src.WellDepth
? src.BitDepth
: src.WellDepth;
return dto;
}
public async Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)
{
double intervalSec = (endDate - beginDate).TotalSeconds;
if (intervalSec > 60 * 60 * 24 * 3)
throw new ArgumentInvalidException(nameof(endDate), "Слишком большой диапазон");
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина id:{idWell} не содержит телеметрии");
var approxPointsCount = intervalSec switch
{
< 2048 => 2048,
< 8_192 => 4_096,
< 131_072 => 16_384,
_ => 32_768
};
var data = await GetAsync(idWell, beginDate, intervalSec, approxPointsCount, token);
var fileName = $"DataSaub idWell{idWell}";
if (telemetry.Info is not null)
fileName += $" {telemetry.Info?.Cluster}, {telemetry.Info?.Well}";
fileName += $" {beginDate:yyyy-MM-DDTHH-mm} - {endDate:yyyy-MM-DDTHH-mm}.csv";
var outStream = new MemoryStream();
using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
{
var entryFile = archive.CreateEntry(fileName, CompressionLevel.Optimal);
using var entryStream = entryFile.Open();
var serializer = new CsvSerializer<TelemetryDataSaubDto>();
serializer.Serialize(data, entryStream);
}
outStream.Seek(0, SeekOrigin.Begin);
return outStream;
}
}

View File

@ -45,17 +45,16 @@ internal class SubsystemService : ISubsystemService
if(!well.IdTelemetry.HasValue)
return Enumerable.Empty<SubsystemStatDto>();
var detectedOperationSummaryRequest = new DetectedOperationRequest
{
var detectedOperationSummaryRequest = new DetectedOperationByWellRequest
{
IdWell = request.IdWell,
IdsTelemetries = new[] { well.IdTelemetry.Value },
IdsCategories = WellOperationCategory.MechanicalDrillingSubIds,
GeDateStart = request.GeDate,
LeDateEnd = request.LeDate,
GeDepth = request.GeDepth,
LeDepth = request.LeDepth,
GeDepthStart = request.GeDepth,
LeDepthEnd = request.LeDepth,
};
var operations = await detectedOperationService.GetOperationsAsync(detectedOperationSummaryRequest,
@ -78,7 +77,7 @@ internal class SubsystemService : ISubsystemService
return result;
}
private async Task<IEnumerable<SubsystemStatDto>> CalcStatAsync(IEnumerable<DetectedOperationDto> operations, CancellationToken token)
private async Task<IEnumerable<SubsystemStatDto>> CalcStatAsync(IEnumerable<DetectedOperationWithDrillerDto> operations, CancellationToken token)
{
if (!subsystems.Any())
subsystems = (await subsystemRepository.GetAllAsync(token)).ToDictionary(s => s.Id, s => s);
@ -92,7 +91,7 @@ internal class SubsystemService : ISubsystemService
return stat;
}
private SubsystemStatDto CalcOscillationStat(IEnumerable<DetectedOperationDto> operations)
private SubsystemStatDto CalcOscillationStat(IEnumerable<DetectedOperationWithDrillerDto> operations)
{
operations = operations.Where(o => o.IdCategory == WellOperationCategory.IdSlide);
@ -114,7 +113,7 @@ internal class SubsystemService : ISubsystemService
return oscillationStat;
}
private IEnumerable<SubsystemStatDto> CalcApdStat(IEnumerable<DetectedOperationDto> operations)
private IEnumerable<SubsystemStatDto> CalcApdStat(IEnumerable<DetectedOperationWithDrillerDto> operations)
{
var apdRotorAndSlide = operations
.Where(o => WellOperationCategory.MechanicalDrillingSubIds.Contains(o.IdCategory))
@ -169,7 +168,7 @@ internal class SubsystemService : ISubsystemService
}
private static (double SumDepthInterval, double UsedTimeHours, int Count) AggregateOperations(int idSubsystem,
IEnumerable<DetectedOperationDto> operations) =>
IEnumerable<DetectedOperationWithDrillerDto> operations) =>
idSubsystem switch
{
IdSubsystemAPDRotor => CalcOperationsByEnableSubsystems(operations, EnabledSubsystemsFlags.AutoRotor),
@ -180,7 +179,7 @@ internal class SubsystemService : ISubsystemService
};
private static (double SumDepthInterval, double UsedTimeHours, int OperationCount) CalcOperationsByEnableSubsystems(
IEnumerable<DetectedOperationDto> operations,
IEnumerable<DetectedOperationWithDrillerDto> operations,
EnabledSubsystemsFlags enabledSubsystems)
{
var filtered = operations.Where(o => enabledSubsystems.HasEnabledSubsystems(o.EnabledSubsystems));
@ -215,7 +214,7 @@ internal class SubsystemService : ISubsystemService
var leDateUtc = leDate?.ToUtcDateTimeOffset(hoursOffset);
var request = new DetectedOperationRequest
var request = new DetectedOperationByWellRequest
{
IdWell = well.Id,
IdsCategories = WellOperationCategory.MechanicalDrillingSubIds,

View File

@ -36,13 +36,18 @@ namespace AsbCloudInfrastructure.Services
.ThenInclude(r => r.Company)
.AsNoTracking();
public WellService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, ITimezoneService timezoneService, WellInfoService wellInfoService)
public WellService(IAsbCloudDbContext db,
IMemoryCache memoryCache,
ITelemetryService telemetryService,
ITimezoneService timezoneService,
WellInfoService wellInfoService,
IWellOperationCategoryRepository wellOperationCategoryRepository)
: base(db, memoryCache, MakeQueryWell)
{
this.telemetryService = telemetryService;
this.timezoneService = timezoneService;
this.wellInfoService = wellInfoService;
this.wellOperationRepository = new WellOperationRepository(db, memoryCache, this);
this.wellOperationRepository = new WellOperationRepository(db, memoryCache, this, wellOperationCategoryRepository);
companyTypesService = new CrudCacheRepositoryBase<CompanyTypeDto, CompanyType>(dbContext, memoryCache);
}

View File

@ -5,7 +5,6 @@ using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudInfrastructure.Services.DetectOperations;
@ -57,7 +56,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
[HttpGet]
[ProducesResponseType(typeof(DetectedOperationListDto), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetAsync(
[FromQuery] DetectedOperationRequest request,
[FromQuery] DetectedOperationByWellRequest request,
CancellationToken token)
{
if (!await UserHasAccessToWellAsync(request.IdWell, token))
@ -76,7 +75,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
[HttpGet("stat")]
[ProducesResponseType(typeof(IEnumerable<DetectedOperationStatDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetStatAsync(
[FromQuery] DetectedOperationRequest request,
[FromQuery] DetectedOperationByWellRequest request,
CancellationToken token)
{
if (!await UserHasAccessToWellAsync(request.IdWell, token))
@ -98,7 +97,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
[Permission]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> DeleteAsync(
[FromQuery] DetectedOperationRequest request,
[FromQuery] DetectedOperationByWellRequest request,
CancellationToken token)
{
if (!await UserHasAccessToWellAsync(request.IdWell, token))