Merge branch 'dev' into fix/process_map

This commit is contained in:
on.nemtina 2024-07-30 10:54:11 +05:00
commit 11662ff2c0
43 changed files with 26387 additions and 1099 deletions

View File

@ -1,4 +1,5 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Services;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@ -9,7 +10,7 @@ namespace AsbCloudApp.Repositories
/// <summary> /// <summary>
/// Репозиторий работы с данными из таблицы t_data_daub_stat /// Репозиторий работы с данными из таблицы t_data_daub_stat
/// </summary> /// </summary>
public interface IDataSaubStatRepository public interface IDataSaubStatRepository : ITelemetryDataEditorService
{ {
/// <summary> /// <summary>
/// Получение записей по ключу телеметрии /// Получение записей по ключу телеметрии

View File

@ -1,18 +1,17 @@
using System; using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation; using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading; using System.Threading;
using AsbCloudApp.Data; using System.Threading.Tasks;
using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Services;
namespace AsbCloudApp.Repositories; namespace AsbCloudApp.Repositories;
/// <summary> /// <summary>
/// Таблица автоматически определенных операций /// Таблица автоматически определенных операций
/// </summary> /// </summary>
public interface IDetectedOperationRepository public interface IDetectedOperationRepository : ITelemetryDataEditorService
{ {
/// <summary> /// <summary>
/// Добавление нескольких записей /// Добавление нескольких записей
@ -53,14 +52,14 @@ public interface IDetectedOperationRepository
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<PaginationContainer<DetectedOperationDto>> GetPageAsync(DetectedOperationByTelemetryRequest request, CancellationToken token); Task<PaginationContainer<DetectedOperationDto>> GetPageAsync(DetectedOperationByTelemetryRequest request, CancellationToken token);
/// <summary> /// <summary>
/// Получение дат последних определённых операций /// Получение последних авто определённых операций
/// </summary> /// </summary>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IDictionary<int, DateTimeOffset>> GetLastDetectedDatesAsync(CancellationToken token); Task<IDictionary<int, DetectedOperationDto>> GetLastDetectedOperationsAsync(CancellationToken token);
/// <summary> /// <summary>
/// Удалить операции /// Удалить операции
/// </summary> /// </summary>

View File

@ -1,5 +1,6 @@
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,7 +10,7 @@ namespace AsbCloudApp.Repositories
/// <summary> /// <summary>
/// репозиторий по работе с данными drill_test /// репозиторий по работе с данными drill_test
/// </summary> /// </summary>
public interface IDrillTestRepository public interface IDrillTestRepository : ITelemetryDataEditorService
{ {
/// <summary> /// <summary>
/// Получить данные drill_test в соответствии с параметрами запроса /// Получить данные drill_test в соответствии с параметрами запроса

View File

@ -5,13 +5,14 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Services;
namespace AsbCloudApp.Repositories namespace AsbCloudApp.Repositories
{ {
/// <summary> /// <summary>
/// данные ГТИ /// данные ГТИ
/// </summary> /// </summary>
public interface IGtrRepository public interface IGtrRepository : ITelemetryDataEditorService
{ {
/// <summary> /// <summary>
/// добавить данные (для панели бурильщика) /// добавить данные (для панели бурильщика)

View File

@ -1,15 +1,16 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Repositories namespace AsbCloudApp.Repositories
{ {
/// <summary> /// <summary>
/// Репозиторий по ограничивающим параметрам с фильтрацией /// Репозиторий по ограничивающим параметрам с фильтрацией
/// </summary> /// </summary>
public interface ILimitingParameterRepository public interface ILimitingParameterRepository : ITelemetryDataEditorService
{ {
/// <summary> /// <summary>
/// Получение списка ограничивающих параметров по идентификатору скважины /// Получение списка ограничивающих параметров по идентификатору скважины

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Requests;
/// <summary>
/// Параметры запроса на удаление куска телеметрии
/// </summary>
public class TelemetryPartDeleteRequest : IValidatableObject
{
/// <summary>
/// ключ телеметрии
/// </summary>
public int IdTelemetry { get; set; }
/// <summary>
/// greater or equal then Date. Must be set one of GeDate or LeDate
/// </summary>
public DateTimeOffset? GeDate { get; set; }
/// <summary>
/// less or equal then Date. Must be set one of GeDate or LeDate
/// </summary>
public DateTimeOffset? LeDate { get; set; }
/// <summary>
/// Валидация входящих данных
/// </summary>
/// <param name="validationContext"></param>
/// <returns></returns>
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IdTelemetry == 0)
yield return new ValidationResult($"IdTelemetry must be defined");
if (!GeDate.HasValue && !LeDate.HasValue)
yield return new ValidationResult($"GeDate or LeDate must be defined");
}
}

View File

@ -55,5 +55,4 @@ public class TelemetryDataRequest
/// </summary> /// </summary>
[Range(1, MaxTake)] [Range(1, MaxTake)]
public int Take { get; set; } = 1024; public int Take { get; set; } = 1024;
} }

View File

@ -6,82 +6,85 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Data.WellOperation;
namespace AsbCloudApp.Services namespace AsbCloudApp.Services;
/// <summary>
/// Сервис автоматически определенных по телеметрии операций
/// </summary>
public interface IDetectedOperationService
{ {
/// <summary> /// <summary>
/// Сервис автоматически определенных по телеметрии операций /// Добавление операций
/// </summary> /// </summary>
public interface IDetectedOperationService /// <param name="idEditor"></param>
{ /// <param name="idWell"></param>
/// <summary> /// <param name="dtos"></param>
/// Добавление операций /// <param name="token"></param>
/// </summary> /// <returns></returns>
/// <param name="idEditor"></param> Task<int> InsertRangeManualAsync(int idEditor, int idWell, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
/// <param name="idWell"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> InsertRangeManualAsync(int idEditor, int idWell, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
/// <summary> /// <summary>
/// Редактирование операций /// Редактирование операций
/// </summary> /// </summary>
/// <param name="idEditor"></param> /// <param name="idEditor"></param>
/// <param name="idWell"></param> /// <param name="idWell"></param>
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> UpdateRangeManualAsync(int idEditor, int idWell, IEnumerable<DetectedOperationDto> dtos, CancellationToken token); Task<int> UpdateRangeManualAsync(int idEditor, int idWell, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
/// <summary> /// <summary>
/// Список названий операций. /// Список названий операций.
/// Если указан idWell, то возвращается список названий операций найденных на указанной скважине. /// Если указан idWell, то возвращается список названий операций найденных на указанной скважине.
/// </summary> /// </summary>
/// <param name="idWell"></param> /// <param name="idWell"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<WellOperationCategoryDto>> GetCategoriesAsync(int? idWell, CancellationToken token); Task<IEnumerable<WellOperationCategoryDto>> GetCategoriesAsync(int? idWell, CancellationToken token);
/// <summary> /// <summary>
/// Получить автоматически определенные по телеметрии операции с анализом по бурильщикам /// Получить автоматически определенные по телеметрии операции с анализом по бурильщикам
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<DetectedOperationListDto> GetAsync(DetectedOperationByWellRequest request, CancellationToken token); Task<DetectedOperationListDto> GetAsync(DetectedOperationByWellRequest request, CancellationToken token);
/// <summary> /// <summary>
/// Получить автоматически определенные по телеметрии операции /// Получить автоматически определенные по телеметрии операции
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<DetectedOperationWithDrillerDto>> GetOperationsAsync(DetectedOperationByWellRequest request, CancellationToken token); Task<IEnumerable<DetectedOperationWithDrillerDto>> GetOperationsAsync(DetectedOperationByWellRequest request, CancellationToken token);
/// <summary> /// <summary>
/// Удалить операции /// Удалить операции
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token); Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token);
/// <summary> /// <summary>
/// Статистика по операциям /// Статистика по операциям
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[Obsolete] [Obsolete]
Task<IEnumerable<DetectedOperationStatDto>> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token); Task<IEnumerable<DetectedOperationStatDto>> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token);
/// <summary> /// <summary>
/// Определение операций /// Определение операций
/// </summary> /// </summary>
/// <param name="idTelemetry"></param> /// <param name="idTelemetry"></param>
/// <param name="beginDate"></param> /// <param name="request"></param>
/// <param name="token"></param> /// <param name="lastDetectedOperation"></param>
/// <returns></returns> /// <param name="token"></param>
Task<IEnumerable<DetectedOperationDto>> DetectOperationsAsync(int idTelemetry, DateTimeOffset? beginDate, CancellationToken token); /// <returns></returns>
} Task<(DateTimeOffset LastDate, IEnumerable<DetectedOperationDto> Items)> DetectOperationsAsync(int idTelemetry,
TelemetryDataRequest request,
DetectedOperationDto? lastDetectedOperation,
CancellationToken token);
} }

View File

@ -10,7 +10,7 @@ namespace AsbCloudApp.Services
/// <summary> /// <summary>
/// Сервис сообщений панели оператора /// Сервис сообщений панели оператора
/// </summary> /// </summary>
public interface IMessageService public interface IMessageService : ITelemetryDataEditorService
{ {
/// <summary> /// <summary>
/// Получить сообщения по параметрам /// Получить сообщения по параметрам

View File

@ -0,0 +1,19 @@
using AsbCloudApp.Requests;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services;
/// <summary>
/// Сервис по работе с данными телеметрии
/// </summary>
public interface ITelemetryDataEditorService
{
/// <summary>
/// Удаление части телеметрии по запросу
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token);
}

View File

@ -5,44 +5,43 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace AsbCloudApp.Services namespace AsbCloudApp.Services;
/// <summary>
/// Телеметрия САУБ
/// </summary>
public interface ITelemetryDataSaubService : ITelemetryDataService<TelemetryDataSaubDto>
{ {
/// <summary> /// <summary>
/// Телеметрия САУБ /// Получение телеметрии для РТК статистики
/// </summary> /// </summary>
public interface ITelemetryDataSaubService : ITelemetryDataService<TelemetryDataSaubDto> /// <param name="idTelemetry"></param>
{ /// <param name="isBitOnBottom"></param>
/// <summary> /// <param name="geDate"></param>
/// Получение телеметрии для РТК статистики /// <param name="leDate"></param>
/// </summary> /// <param name="take"></param>
/// <param name="idTelemetry"></param> /// <param name="token"></param>
/// <param name="isBitOnBottom"></param> /// <returns></returns>
/// <param name="geDate"></param> Task<IEnumerable<TelemetryDataSaubDto>> Get(int idTelemetry, bool isBitOnBottom, DateTimeOffset geDate, DateTimeOffset leDate, int take, CancellationToken token);
/// <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> /// <summary>
/// усредненная статистика по 1м за весь период /// усредненная статистика по 1м за весь период
/// <para> /// <para>
/// МЕДЛЕННЫЙ ЗАПРОС /// МЕДЛЕННЫЙ ЗАПРОС
/// </para> /// </para>
/// </summary> /// </summary>
/// <param name="idTelemetry"></param> /// <param name="idTelemetry"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token); Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token);
/// <summary> /// <summary>
/// Получить упакованый csv файл /// Получить упакованный csv файл
/// </summary> /// </summary>
/// <param name="idWell"></param> /// <param name="idWell"></param>
/// <param name="beginDate"></param> /// <param name="beginDate"></param>
/// <param name="endDate"></param> /// <param name="endDate"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token); Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token);
}
} }

View File

@ -11,7 +11,7 @@ namespace AsbCloudApp.Services
/// сервис данных тех. процесса /// сервис данных тех. процесса
/// </summary> /// </summary>
/// <typeparam name="TDto"></typeparam> /// <typeparam name="TDto"></typeparam>
public interface ITelemetryDataService<TDto> where TDto : ITelemetryData public interface ITelemetryDataService<TDto> : ITelemetryDataEditorService where TDto : ITelemetryData
{ {
/// <summary> /// <summary>

View File

@ -60,5 +60,15 @@ namespace AsbCloudApp.Services
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> DeleteAsync(int idWell, int id, CancellationToken token); Task<int> DeleteAsync(int idWell, int id, CancellationToken token);
/// <summary>
/// Скопировать контакты в другую скважину
/// </summary>
/// <param name="idWell">ключ скважины, откуда копировать контакты</param>
/// <param name="idWellTarget">ключ скважины, куда копировать контакты</param>
/// <param name="contactIds"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> CopyAsync(int idWell, int idWellTarget, IEnumerable<int> contactIds, CancellationToken token);
} }
} }

View File

@ -1,4 +1,5 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Requests;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@ -9,7 +10,7 @@ namespace AsbCloudApp.Services
/// <summary> /// <summary>
/// The wits record repository. /// The wits record repository.
/// </summary> /// </summary>
public interface IWitsRecordRepository<TDto> public interface IWitsRecordRepository<TDto> : ITelemetryDataEditorService
where TDto : ITelemetryData where TDto : ITelemetryData
{ {
/// <summary> /// <summary>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
/// <inheritdoc />
public partial class Update_WellSectionType_Order : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 1,
column: "order",
value: 5f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 5,
column: "order",
value: 4f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 7,
column: "order",
value: 5.1f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 11,
column: "order",
value: 4.1f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 13,
column: "order",
value: 5.2f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 17,
column: "order",
value: 4.2f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 19,
column: "order",
value: 5.3f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 23,
column: "order",
value: 4.3f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 25,
column: "order",
value: 5.4f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 29,
column: "order",
value: 4.4f);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 1,
column: "order",
value: 4f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 5,
column: "order",
value: 5f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 7,
column: "order",
value: 4.1f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 11,
column: "order",
value: 5.1f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 13,
column: "order",
value: 4.2f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 17,
column: "order",
value: 5.2f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 19,
column: "order",
value: 4.3f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 23,
column: "order",
value: 5.3f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 25,
column: "order",
value: 4.4f);
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 29,
column: "order",
value: 5.4f);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,116 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
/// <inheritdoc />
public partial class Update_WellSectionType_Caption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 1,
column: "caption",
value: "Пилотный ствол 1");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 2,
column: "caption",
value: "Направление 1");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 3,
column: "caption",
value: "Кондуктор 1");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 4,
column: "caption",
value: "Эксплуатационная колонна 1");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 5,
column: "caption",
value: "Транспортный ствол 1");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 6,
column: "caption",
value: "Хвостовик 1");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 31,
column: "caption",
value: "Техническая колонна 1");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 1,
column: "caption",
value: "Пилотный ствол");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 2,
column: "caption",
value: "Направление");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 3,
column: "caption",
value: "Кондуктор");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 4,
column: "caption",
value: "Эксплуатационная колонна");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 5,
column: "caption",
value: "Транспортный ствол");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 6,
column: "caption",
value: "Хвостовик");
migrationBuilder.UpdateData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 31,
column: "caption",
value: "Техническая колонна");
}
}
}

View File

@ -19,7 +19,7 @@ namespace AsbCloudDb.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.UseCollation("Russian_Russia.1251") .UseCollation("Russian_Russia.1251")
.HasAnnotation("ProductVersion", "8.0.2") .HasAnnotation("ProductVersion", "8.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
@ -9544,44 +9544,44 @@ namespace AsbCloudDb.Migrations
new new
{ {
Id = 1, Id = 1,
Caption = "Пилотный ствол", Caption = "Пилотный ствол 1",
Order = 4f Order = 5f
}, },
new new
{ {
Id = 2, Id = 2,
Caption = "Направление", Caption = "Направление 1",
Order = 0f Order = 0f
}, },
new new
{ {
Id = 3, Id = 3,
Caption = "Кондуктор", Caption = "Кондуктор 1",
Order = 1f Order = 1f
}, },
new new
{ {
Id = 4, Id = 4,
Caption = "Эксплуатационная колонна", Caption = "Эксплуатационная колонна 1",
Order = 3f Order = 3f
}, },
new new
{ {
Id = 5, Id = 5,
Caption = "Транспортный ствол", Caption = "Транспортный ствол 1",
Order = 5f Order = 4f
}, },
new new
{ {
Id = 6, Id = 6,
Caption = "Хвостовик", Caption = "Хвостовик 1",
Order = 6f Order = 6f
}, },
new new
{ {
Id = 7, Id = 7,
Caption = "Пилотный ствол 2", Caption = "Пилотный ствол 2",
Order = 4.1f Order = 5.1f
}, },
new new
{ {
@ -9605,7 +9605,7 @@ namespace AsbCloudDb.Migrations
{ {
Id = 11, Id = 11,
Caption = "Транспортный ствол 2", Caption = "Транспортный ствол 2",
Order = 5.1f Order = 4.1f
}, },
new new
{ {
@ -9617,7 +9617,7 @@ namespace AsbCloudDb.Migrations
{ {
Id = 13, Id = 13,
Caption = "Пилотный ствол 3", Caption = "Пилотный ствол 3",
Order = 4.2f Order = 5.2f
}, },
new new
{ {
@ -9641,7 +9641,7 @@ namespace AsbCloudDb.Migrations
{ {
Id = 17, Id = 17,
Caption = "Транспортный ствол 3", Caption = "Транспортный ствол 3",
Order = 5.2f Order = 4.2f
}, },
new new
{ {
@ -9653,7 +9653,7 @@ namespace AsbCloudDb.Migrations
{ {
Id = 19, Id = 19,
Caption = "Пилотный ствол 4", Caption = "Пилотный ствол 4",
Order = 4.3f Order = 5.3f
}, },
new new
{ {
@ -9677,7 +9677,7 @@ namespace AsbCloudDb.Migrations
{ {
Id = 23, Id = 23,
Caption = "Транспортный ствол 4", Caption = "Транспортный ствол 4",
Order = 5.3f Order = 4.3f
}, },
new new
{ {
@ -9689,7 +9689,7 @@ namespace AsbCloudDb.Migrations
{ {
Id = 25, Id = 25,
Caption = "Пилотный ствол 5", Caption = "Пилотный ствол 5",
Order = 4.4f Order = 5.4f
}, },
new new
{ {
@ -9713,7 +9713,7 @@ namespace AsbCloudDb.Migrations
{ {
Id = 29, Id = 29,
Caption = "Транспортный ствол 5", Caption = "Транспортный ствол 5",
Order = 5.4f Order = 4.4f
}, },
new new
{ {
@ -9724,7 +9724,7 @@ namespace AsbCloudDb.Migrations
new new
{ {
Id = 31, Id = 31,
Caption = "Техническая колонна", Caption = "Техническая колонна 1",
Order = 2f Order = 2f
}, },
new new

View File

@ -4,42 +4,42 @@ namespace AsbCloudDb.Model.DefaultData
{ {
public override WellSectionType[] GetData() => new WellSectionType[] public override WellSectionType[] GetData() => new WellSectionType[]
{ {
new (){ Id = 1, Caption = "Пилотный ствол", Order = 4}, new (){ Id = 1, Caption = "Пилотный ствол 1", Order = 5},
new (){ Id = 2, Caption = "Направление", Order = 0}, new (){ Id = 2, Caption = "Направление 1", Order = 0},
new (){ Id = 3, Caption = "Кондуктор", Order = 1}, new (){ Id = 3, Caption = "Кондуктор 1", Order = 1},
new (){ Id = 4, Caption = "Эксплуатационная колонна", Order = 3}, new (){ Id = 4, Caption = "Эксплуатационная колонна 1", Order = 3},
new (){ Id = 5, Caption = "Транспортный ствол", Order = 5}, new (){ Id = 5, Caption = "Транспортный ствол 1", Order = 4},
new (){ Id = 6, Caption = "Хвостовик", Order = 6}, new (){ Id = 6, Caption = "Хвостовик 1", Order = 6},
new (){ Id = 7, Caption = "Пилотный ствол 2", Order = 4.1f}, new (){ Id = 7, Caption = "Пилотный ствол 2", Order = 5.1f},
new (){ Id = 8, Caption = "Направление 2", Order = 0.1f}, new (){ Id = 8, Caption = "Направление 2", Order = 0.1f},
new (){ Id = 9, Caption = "Кондуктор 2", Order = 1.1f}, new (){ Id = 9, Caption = "Кондуктор 2", Order = 1.1f},
new (){ Id = 10, Caption = "Эксплуатационная колонна 2", Order = 3.1f}, new (){ Id = 10, Caption = "Эксплуатационная колонна 2", Order = 3.1f},
new (){ Id = 11, Caption = "Транспортный ствол 2", Order = 5.1f}, new (){ Id = 11, Caption = "Транспортный ствол 2", Order = 4.1f},
new (){ Id = 12, Caption = "Хвостовик 2", Order = 6.1f}, new (){ Id = 12, Caption = "Хвостовик 2", Order = 6.1f},
new (){ Id = 13, Caption = "Пилотный ствол 3", Order = 4.2f}, new (){ Id = 13, Caption = "Пилотный ствол 3", Order = 5.2f},
new (){ Id = 14, Caption = "Направление 3", Order = 0.2f}, new (){ Id = 14, Caption = "Направление 3", Order = 0.2f},
new (){ Id = 15, Caption = "Кондуктор 3", Order = 1.2f}, new (){ Id = 15, Caption = "Кондуктор 3", Order = 1.2f},
new (){ Id = 16, Caption = "Эксплуатационная колонна 3", Order = 3.2f}, new (){ Id = 16, Caption = "Эксплуатационная колонна 3", Order = 3.2f},
new (){ Id = 17, Caption = "Транспортный ствол 3", Order = 5.2f}, new (){ Id = 17, Caption = "Транспортный ствол 3", Order = 4.2f},
new (){ Id = 18, Caption = "Хвостовик 3", Order = 6.2f}, new (){ Id = 18, Caption = "Хвостовик 3", Order = 6.2f},
new (){ Id = 19, Caption = "Пилотный ствол 4", Order = 4.3f}, new (){ Id = 19, Caption = "Пилотный ствол 4", Order = 5.3f},
new (){ Id = 20, Caption = "Направление 4", Order = 0.3f}, new (){ Id = 20, Caption = "Направление 4", Order = 0.3f},
new (){ Id = 21, Caption = "Кондуктор 4", Order = 1.3f}, new (){ Id = 21, Caption = "Кондуктор 4", Order = 1.3f},
new (){ Id = 22, Caption = "Эксплуатационная колонна 4", Order = 3.3f}, new (){ Id = 22, Caption = "Эксплуатационная колонна 4", Order = 3.3f},
new (){ Id = 23, Caption = "Транспортный ствол 4", Order = 5.3f}, new (){ Id = 23, Caption = "Транспортный ствол 4", Order = 4.3f},
new (){ Id = 24, Caption = "Хвостовик 4", Order = 6.3f}, new (){ Id = 24, Caption = "Хвостовик 4", Order = 6.3f},
new (){ Id = 25, Caption = "Пилотный ствол 5", Order = 4.4f}, new (){ Id = 25, Caption = "Пилотный ствол 5", Order = 5.4f},
new (){ Id = 26, Caption = "Направление 5", Order = 0.4f}, new (){ Id = 26, Caption = "Направление 5", Order = 0.4f},
new (){ Id = 27, Caption = "Кондуктор 5", Order = 1.4f}, new (){ Id = 27, Caption = "Кондуктор 5", Order = 1.4f},
new (){ Id = 28, Caption = "Эксплуатационная колонна 5", Order = 3.4f}, new (){ Id = 28, Caption = "Эксплуатационная колонна 5", Order = 3.4f},
new (){ Id = 29, Caption = "Транспортный ствол 5", Order = 5.4f}, new (){ Id = 29, Caption = "Транспортный ствол 5", Order = 4.4f},
new (){ Id = 30, Caption = "Хвостовик 5", Order = 6.4f}, new (){ Id = 30, Caption = "Хвостовик 5", Order = 6.4f},
new (){ Id = 31, Caption = "Техническая колонна", Order = 2}, new (){ Id = 31, Caption = "Техническая колонна 1", Order = 2},
new (){ Id = 32, Caption = "Техническая колонна 2", Order = 2.1f}, new (){ Id = 32, Caption = "Техническая колонна 2", Order = 2.1f},
new (){ Id = 33, Caption = "Техническая колонна 3", Order = 2.2f}, new (){ Id = 33, Caption = "Техническая колонна 3", Order = 2.2f},

View File

@ -493,6 +493,8 @@ namespace AsbCloudInfrastructure
services.AddTransient<IWellboreService, WellboreService>(); services.AddTransient<IWellboreService, WellboreService>();
services.AddTransient<ITelemetryDataEditorService, TelemetryDataEditorService>();
services.AddTransient<DetectedOperationExportService>(); services.AddTransient<DetectedOperationExportService>();
services.AddTransient<IDailyReportService, DailyReportService>(); services.AddTransient<IDailyReportService, DailyReportService>();

View File

@ -1,5 +1,6 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Mapster; using Mapster;
@ -81,5 +82,32 @@ namespace AsbCloudInfrastructure.Repository
return entity; return entity;
} }
private IQueryable<DataSaubStat> BuildQuery(TelemetryPartDeleteRequest request)
{
var query = db.Set<DataSaubStat>()
.Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.LeDate is not null)
{
var leDate = request.LeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateStart <= leDate);
}
if (request.GeDate is not null)
{
var geDate = request.GeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateEnd >= geDate);
}
return query;
}
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var query = BuildQuery(request);
db.Set<DataSaubStat>().RemoveRange(query);
return await db.SaveChangesAsync(token);
}
} }
} }

View File

@ -15,7 +15,8 @@ using AsbCloudApp.Data;
namespace AsbCloudInfrastructure.Repository; namespace AsbCloudInfrastructure.Repository;
public class DetectedOperationRepository : CrudRepositoryBase<DetectedOperationDto, DetectedOperation>, IDetectedOperationRepository public class DetectedOperationRepository
: CrudRepositoryBase<DetectedOperationDto, DetectedOperation>, IDetectedOperationRepository
{ {
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
@ -25,7 +26,24 @@ public class DetectedOperationRepository : CrudRepositoryBase<DetectedOperationD
{ {
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
} }
public async Task<IDictionary<int, DetectedOperationDto>> GetLastDetectedOperationsAsync(CancellationToken token)
{
var entities = await dbContext.Set<DetectedOperation>()
.GroupBy(o => o.IdTelemetry)
.Select(g => new
{
IdTelemetry = g.Key,
LastDetectedOperation = g.OrderBy(o => o.DateEnd).Last()
})
.ToArrayAsync(token);
var dtos = entities.ToDictionary(x => x.IdTelemetry,
x => Convert(x.LastDetectedOperation));
return dtos;
}
public async Task<int> DeleteAsync(DetectedOperationByTelemetryRequest request, CancellationToken token) public async Task<int> DeleteAsync(DetectedOperationByTelemetryRequest request, CancellationToken token)
{ {
var query = BuildQuery(request); var query = BuildQuery(request);
@ -33,6 +51,13 @@ public class DetectedOperationRepository : CrudRepositoryBase<DetectedOperationD
return await dbContext.SaveChangesAsync(token); return await dbContext.SaveChangesAsync(token);
} }
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var query = BuildQuery(request);
dbContext.Set<DetectedOperation>().RemoveRange(query);
return await dbContext.SaveChangesAsync(token);
}
public async Task<PaginationContainer<DetectedOperationDto>> GetPageAsync(DetectedOperationByTelemetryRequest request, CancellationToken token) public async Task<PaginationContainer<DetectedOperationDto>> GetPageAsync(DetectedOperationByTelemetryRequest request, CancellationToken token)
{ {
var skip = request.Skip ?? 0; var skip = request.Skip ?? 0;
@ -57,17 +82,7 @@ public class DetectedOperationRepository : CrudRepositoryBase<DetectedOperationD
return paginationContainer; return paginationContainer;
} }
public async Task<IDictionary<int, DateTimeOffset>> GetLastDetectedDatesAsync(CancellationToken token) =>
await dbContext.Set<DetectedOperation>()
.GroupBy(o => o.IdTelemetry)
.Select(g => new
{
IdTelemetry = g.Key,
LastDate = g.Max(o => o.DateEnd)
})
.ToDictionaryAsync(x => x.IdTelemetry, x => x.LastDate, token);
public async Task<IEnumerable<DetectedOperationDto>> Get(DetectedOperationByTelemetryRequest request, CancellationToken token) public async Task<IEnumerable<DetectedOperationDto>> Get(DetectedOperationByTelemetryRequest request, CancellationToken token)
{ {
var query = BuildQuery(request) var query = BuildQuery(request)
@ -112,7 +127,27 @@ public class DetectedOperationRepository : CrudRepositoryBase<DetectedOperationD
return query; return query;
} }
private IQueryable<DetectedOperation> BuildQuery(TelemetryPartDeleteRequest request)
{
var query = dbContext.Set<DetectedOperation>()
.Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.LeDate is not null)
{
var leDate = request.LeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateStart <= leDate);
}
if (request.GeDate is not null)
{
var geDate = request.GeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateEnd >= geDate);
}
return query;
}
private static DetectedOperationDto Convert(DetectedOperation src, TimeSpan offset) private static DetectedOperationDto Convert(DetectedOperation src, TimeSpan offset)
{ {
var dto = src.Adapt<DetectedOperationDto>(); var dto = src.Adapt<DetectedOperationDto>();

View File

@ -84,5 +84,33 @@ namespace AsbCloudInfrastructure.Repository
dto.TimeStampStart = dto.TimeStampStart.ToRemoteDateTime(dto.Telemetry?.TimeZone?.Hours ?? 0); dto.TimeStampStart = dto.TimeStampStart.ToRemoteDateTime(dto.Telemetry?.TimeZone?.Hours ?? 0);
return dto; return dto;
} }
//TODO: Унифицировать модели данных телеметрии.Чтобы в будущем унифицировать репозитории данных телеметрии.
private IQueryable<DrillTest> BuildQuery(TelemetryPartDeleteRequest request)
{
var query = db.Set<DrillTest>()
.Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.LeDate is not null)
{
var leDate = request.LeDate.Value.ToUniversalTime();
query = query.Where(o => o.TimeStampStart <= leDate);
}
if (request.GeDate is not null)
{
var geDate = request.GeDate.Value.ToUniversalTime();
query = query.Where(o => o.TimeStampStart >= geDate);
}
return query;
}
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var query = BuildQuery(request);
db.Set<DrillTest>().RemoveRange(query);
return await db.SaveChangesAsync(token);
}
} }
} }

View File

@ -1,9 +1,13 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.GTR; using AsbCloudApp.Data.GTR;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudDb.Model.GTR; using AsbCloudDb.Model.GTR;
using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -12,11 +16,6 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Requests;
using Mapster;
using AsbCloudApp.Data;
using System.ComponentModel.DataAnnotations;
namespace AsbCloudInfrastructure.Repository namespace AsbCloudInfrastructure.Repository
{ {
@ -87,7 +86,7 @@ namespace AsbCloudInfrastructure.Repository
public async Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset? geDate, DateTimeOffset? leDate, CancellationToken token) public async Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset? geDate, DateTimeOffset? leDate, CancellationToken token)
{ {
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell); var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null) if (telemetry is null)
return null; return null;
@ -109,7 +108,7 @@ namespace AsbCloudInfrastructure.Repository
}); });
var range = await groupedQuery.FirstOrDefaultAsync(token); var range = await groupedQuery.FirstOrDefaultAsync(token);
if (range is null) if (range is null)
return null; return null;
var result = new DatesRangeDto var result = new DatesRangeDto
@ -121,16 +120,16 @@ namespace AsbCloudInfrastructure.Repository
} }
private async Task<IEnumerable<GtrWitsDto>> GetAsync<TEntity, TType>(int idWell, GtrRequest request, CancellationToken token) private async Task<IEnumerable<GtrWitsDto>> GetAsync<TEntity, TType>(int idWell, GtrRequest request, CancellationToken token)
where TEntity : WitsItemBase<TType> where TEntity : WitsItemBase<TType>
where TType : notnull where TType : notnull
{ {
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell); var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null) if (telemetry is null)
return Enumerable.Empty<GtrWitsDto>(); return Enumerable.Empty<GtrWitsDto>();
if (telemetry.TimeZone is null) if (telemetry.TimeZone is null)
throw new ArgumentInvalidException(nameof(idWell),$"Telemetry id: {telemetry.Id} can't find timezone"); throw new ArgumentInvalidException(nameof(idWell), $"Telemetry id: {telemetry.Id} can't find timezone");
var query = BuildQuery<TEntity, TType>(telemetry.Id, request); var query = BuildQuery<TEntity, TType>(telemetry.Id, request);
@ -147,7 +146,7 @@ namespace AsbCloudInfrastructure.Repository
var interval = TimeSpan.FromSeconds(10); var interval = TimeSpan.FromSeconds(10);
var timezoneOffset = TimeSpan.FromHours(telemetry.TimeZone.Hours); var timezoneOffset = TimeSpan.FromHours(telemetry.TimeZone.Hours);
var dtos = entities var dtos = entities
.GroupBy(e => e.DateTime.Ticks / interval.Ticks) .GroupBy(e => e.DateTime.Ticks / interval.Ticks)
.Select(groupByInterval => .Select(groupByInterval =>
@ -166,9 +165,9 @@ namespace AsbCloudInfrastructure.Repository
} }
private IQueryable<TEntity> BuildQuery<TEntity, TType>(int idTelemetry, GtrRequest request) private IQueryable<TEntity> BuildQuery<TEntity, TType>(int idTelemetry, GtrRequest request)
where TEntity : WitsItemBase<TType> where TEntity : WitsItemBase<TType>
where TType : notnull where TType : notnull
{ {
var query = db.Set<TEntity>() var query = db.Set<TEntity>()
.Where(e => e.IdTelemetry == idTelemetry); .Where(e => e.IdTelemetry == idTelemetry);
@ -183,9 +182,9 @@ namespace AsbCloudInfrastructure.Repository
else else
{ {
var lastDate = query var lastDate = query
.OrderBy(e=>e.DateTime) .OrderBy(e => e.DateTime)
.LastOrDefault() .LastOrDefault()
?.DateTime ?.DateTime
?? DateTimeOffset.UtcNow; ?? DateTimeOffset.UtcNow;
var dateBegin = lastDate.AddSeconds(-request.IntervalSec); var dateBegin = lastDate.AddSeconds(-request.IntervalSec);
var dateEnd = lastDate; var dateEnd = lastDate;
@ -329,10 +328,10 @@ namespace AsbCloudInfrastructure.Repository
var dateTime = record.Date.ToUtcDateTimeOffset(timezoneHours); var dateTime = record.Date.ToUtcDateTimeOffset(timezoneHours);
foreach (var item in record.Items) foreach (var item in record.Items)
{ {
if (cacheTelemetryItems?.TryGetValue((record.Id, item.Key), out var cacheItem) == true) if (cacheTelemetryItems?.TryGetValue((record.Id, item.Key), out var cacheItem) == true)
if (Math.Abs((dateTime - cacheItem.Date).TotalSeconds) < 1) if (Math.Abs((dateTime - cacheItem.Date).TotalSeconds) < 1)
continue; continue;
if (item.Value.Value is string valueString) if (item.Value.Value is string valueString)
{ {
var entity = MakeEntity<WitsItemString, string>(record.Id, item.Key, idTelemetry, dateTime, valueString); var entity = MakeEntity<WitsItemString, string>(record.Id, item.Key, idTelemetry, dateTime, valueString);
@ -362,14 +361,15 @@ namespace AsbCloudInfrastructure.Repository
if (ints.Any()) if (ints.Any())
await db.Database.ExecInsertOrIgnoreAsync(db.Set<WitsItemInt>(), ints, token); await db.Database.ExecInsertOrIgnoreAsync(db.Set<WitsItemInt>(), ints, token);
} }
catch(Exception ex) catch (Exception ex)
{ {
Trace.TraceError("Exception while saving GTR Wits data", ex); Trace.TraceError("Exception while saving GTR Wits data", ex);
} }
cache.AddOrUpdate(idTelemetry, cache.AddOrUpdate(idTelemetry,
(_) => MakeNewCache(dtos), (_) => MakeNewCache(dtos),
(_, oldItemsDictionary) => { (_, oldItemsDictionary) =>
{
foreach (var record in dtos) foreach (var record in dtos)
foreach (var item in record.Items) foreach (var item in record.Items)
{ {
@ -398,7 +398,8 @@ namespace AsbCloudInfrastructure.Repository
{ {
var items = dtos.SelectMany(record => var items = dtos.SelectMany(record =>
record.Items.Select( record.Items.Select(
item => new WitsItemRecordDto { item => new WitsItemRecordDto
{
IdItem = item.Key, IdItem = item.Key,
IdRecord = record.Id, IdRecord = record.Id,
Date = record.Date, Date = record.Date,
@ -409,7 +410,7 @@ namespace AsbCloudInfrastructure.Repository
.GroupBy(item => (item.IdRecord, item.IdItem)); .GroupBy(item => (item.IdRecord, item.IdItem));
var pairs = groups.Select(group => new KeyValuePair<(int, int), WitsItemRecordDto>( var pairs = groups.Select(group => new KeyValuePair<(int, int), WitsItemRecordDto>(
group.Key, group.Key,
group.OrderByDescending(item => item.Date).First())); group.OrderByDescending(item => item.Date).First()));
return new ConcurrentDictionary<(int, int), WitsItemRecordDto>(pairs); return new ConcurrentDictionary<(int, int), WitsItemRecordDto>(pairs);
@ -439,6 +440,47 @@ namespace AsbCloudInfrastructure.Repository
Value = (TValue)dto.Value.Value, Value = (TValue)dto.Value.Value,
}; };
private IQueryable<TEntity> BuildQuery<TEntity, TValue>(TelemetryPartDeleteRequest request)
where TEntity : WitsItemBase<TValue>
where TValue : notnull
{
var query = db.Set<TEntity>().Where(i => i.IdTelemetry == request.IdTelemetry);
if (request.LeDate is not null)
{
var leDate = request.LeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateTime <= leDate);
}
if (request.GeDate is not null)
{
var geDate = request.GeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateTime >= geDate);
}
return query;
}
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var result = 0;
result += await DeleteAsync<WitsItemFloat, float>(request, token);
result += await DeleteAsync<WitsItemInt, int>(request, token);
result += await DeleteAsync<WitsItemString, string>(request, token);
return result;
}
private async Task<int> DeleteAsync<TEntity, TType>(TelemetryPartDeleteRequest request, CancellationToken token)
where TEntity : WitsItemBase<TType>
where TType : notnull
{
var query = BuildQuery<TEntity, TType>(request);
db.Set<TEntity>().RemoveRange(query);
return await db.SaveChangesAsync(token);
}
private class WitsRequest private class WitsRequest
{ {
public int IdTelemetry { get; set; } public int IdTelemetry { get; set; }

View File

@ -17,10 +17,17 @@ namespace AsbCloudInfrastructure.Repository
private readonly IAsbCloudDbContext context; private readonly IAsbCloudDbContext context;
public LimitingParameterRepository(IAsbCloudDbContext context) public LimitingParameterRepository(IAsbCloudDbContext context)
{ {
this.context = context; this.context = context;
} }
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var query = BuildQuery(request);
context.Set<LimitingParameter>().RemoveRange(query);
return await context.SaveChangesAsync(token);
}
public async Task<IEnumerable<LimitingParameterDataDto>> GetLimitingParametersAsync(LimitingParameterRequest request, WellDto wellDto, CancellationToken token) public async Task<IEnumerable<LimitingParameterDataDto>> GetLimitingParametersAsync(LimitingParameterRequest request, WellDto wellDto, CancellationToken token)
{ {
var timezoneOffset = wellDto.Timezone.Hours; var timezoneOffset = wellDto.Timezone.Hours;
@ -74,6 +81,26 @@ namespace AsbCloudInfrastructure.Repository
return query; return query;
} }
private IQueryable<LimitingParameter> BuildQuery(TelemetryPartDeleteRequest request)
{
var query = context.Set<LimitingParameter>()
.Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.LeDate is not null)
{
var leDate = request.LeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateStart <= leDate);
}
if (request.GeDate is not null)
{
var geDate = request.GeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateEnd >= geDate);
}
return query;
}
} }
} }

View File

@ -1,3 +1,4 @@
using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Mapster; using Mapster;
@ -20,7 +21,7 @@ namespace AsbCloudInfrastructure.Repository
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
private static readonly ConcurrentDictionary<int, TDto> cache = new (); private static readonly ConcurrentDictionary<int, TDto> cache = new();
public WitsRecordRepository(IAsbCloudDbContext db, ITelemetryService telemetryService) public WitsRecordRepository(IAsbCloudDbContext db, ITelemetryService telemetryService)
{ {
@ -58,6 +59,33 @@ namespace AsbCloudInfrastructure.Repository
return data.Select(d => Convert(d, timezoneHours)); return data.Select(d => Convert(d, timezoneHours));
} }
private IQueryable<TEntity> BuildQuery(TelemetryPartDeleteRequest request)
{
var query = db.Set<TEntity>()
.Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.LeDate is not null)
{
var leDate = request.LeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateTime <= leDate);
}
if (request.GeDate is not null)
{
var geDate = request.GeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateTime >= geDate);
}
return query;
}
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var query = BuildQuery(request);
dbset.RemoveRange(query);
return await db.SaveChangesAsync(token);
}
public TDto? GetLastOrDefault(int idTelemetry) public TDto? GetLastOrDefault(int idTelemetry)
=> cache.GetValueOrDefault(idTelemetry); => cache.GetValueOrDefault(idTelemetry);
@ -66,7 +94,7 @@ namespace AsbCloudInfrastructure.Repository
if (!dtos.Any()) if (!dtos.Any())
return; return;
cache.AddOrUpdate(idTelemetry, dtos.Last(), (_,_) => dtos.OrderBy(r => r.DateTime).Last()); cache.AddOrUpdate(idTelemetry, dtos.Last(), (_, _) => dtos.OrderBy(r => r.DateTime).Last());
var timezoneHours = telemetryService.GetTimezone(idTelemetry).Hours; var timezoneHours = telemetryService.GetTimezone(idTelemetry).Hours;
var entities = dtos var entities = dtos
@ -81,7 +109,7 @@ namespace AsbCloudInfrastructure.Repository
.Select(e => e.DateTime) .Select(e => e.DateTime)
.OrderBy(d => d) .OrderBy(d => d)
.ToArrayAsync(token); .ToArrayAsync(token);
foreach (var entity in entities) foreach (var entity in entities)
{ {
if (!existingEntities.Any(e => e == entity.DateTime)) if (!existingEntities.Any(e => e == entity.DateTime))
@ -101,9 +129,9 @@ namespace AsbCloudInfrastructure.Repository
(dt.Millisecond + random.Next(1, 283)) % 1000, (dt.Millisecond + random.Next(1, 283)) % 1000,
dt.Offset); dt.Offset);
dbset.Add(entity); dbset.Add(entity);
} }
} }
await db.SaveChangesAsync(token); await db.SaveChangesAsync(token);
} }
@ -126,8 +154,8 @@ namespace AsbCloudInfrastructure.Repository
{ {
var entity = dto.Adapt<TEntity>(); var entity = dto.Adapt<TEntity>();
entity.Recid = GetRecId(dto); entity.Recid = GetRecId(dto);
entity.IdTelemetry = idTelemetry; entity.IdTelemetry = idTelemetry;
entity.DateTime = dto.DateTime.ToUtcDateTimeOffset(timezoneHours); entity.DateTime = dto.DateTime.ToUtcDateTimeOffset(timezoneHours);
return entity; return entity;
} }

View File

@ -1,6 +1,5 @@
using AsbCloudDb.Model; using AsbCloudDb.Model;
using ClosedXML.Excel; using ClosedXML.Excel;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -14,210 +13,219 @@ using AsbCloudApp.Exceptions;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Data.WellOperation;
using AsbCloudApp.Requests;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors; using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
namespace AsbCloudInfrastructure.Services.DetectOperations; namespace AsbCloudInfrastructure.Services.DetectOperations;
public class DetectedOperationExportService public class DetectedOperationExportService
{ {
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
private readonly IDetectedOperationService detectedOperationService; private readonly IDetectedOperationRepository detectedOperationRepository;
private const int headerRowsCount = 1; private const int headerRowsCount = 1;
private const string cellDepositName = "B1"; private const string cellDepositName = "B1";
private const string cellClusterName = "B2"; private const string cellClusterName = "B2";
private const string cellWellName = "B3"; private const string cellWellName = "B3";
private const string cellDeltaDate = "H2"; private const string cellDeltaDate = "H2";
private const int columnOperationName = 1; private const int columnOperationName = 1;
private const int columnDateStart = 2; private const int columnDateStart = 2;
private const int columnDateEnd = 3; private const int columnDateEnd = 3;
private const int columnDuration = 4; private const int columnDuration = 4;
private const int columnDepthStart = 5; private const int columnDepthStart = 5;
private const int columnDepthEnd = 6; private const int columnDepthEnd = 6;
private const int columnDeltaDepth = 7; private const int columnDeltaDepth = 7;
private const int columnDepth = 8; private const int columnDepth = 8;
private const int columnIdReasonOfEnd = 9; private const int columnIdReasonOfEnd = 9;
private const int columnComment = 10; private const int columnComment = 10;
public DetectedOperationExportService(IWellService wellService, public DetectedOperationExportService(IWellService wellService,
IWellOperationCategoryRepository wellOperationCategoryRepository, IWellOperationCategoryRepository wellOperationCategoryRepository,
IDetectedOperationService detectedOperationService) IDetectedOperationRepository detectedOperationRepository)
{ {
this.wellService = wellService; this.wellService = wellService;
this.wellOperationCategoryRepository = wellOperationCategoryRepository; this.wellOperationCategoryRepository = wellOperationCategoryRepository;
this.detectedOperationService = detectedOperationService; this.detectedOperationRepository = detectedOperationRepository;
} }
/// <summary> /// <summary>
/// Экспорт excel файла с операциями по скважине /// Экспорт excel файла с операциями по скважине
/// </summary> /// </summary>
/// <param name="idWell">ключ скважины</param> /// <param name="idWell">ключ скважины</param>
/// <param name="host">хост</param> /// <param name="host">хост</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="ArgumentInvalidException"></exception> /// <exception cref="ArgumentInvalidException"></exception>
public async Task<Stream> ExportAsync(int idWell, string host, CancellationToken token) public async Task<Stream> ExportAsync(int idWell, string host, CancellationToken token)
{ {
var well = await wellService.GetOrDefaultAsync(idWell, token); var well = await wellService.GetOrDefaultAsync(idWell, token);
if (well is null) if (well is null)
throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} does not exist"); throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} does not exist");
if (!well.IdTelemetry.HasValue) if (!well.IdTelemetry.HasValue)
throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} has no telemetry"); throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} has no telemetry");
var operations = await detectedOperationService.DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, token); var request = new DetectedOperationByTelemetryRequest
{
IdTelemetry = well.IdTelemetry.Value
};
return await GenerateExcelFileStreamAsync(well, host, operations, token); var operations = await detectedOperationRepository.Get(request, token);
}
private async Task<Stream> GenerateExcelFileStreamAsync(WellDto well, string host, IEnumerable<DetectedOperationDto> operationDetectorResults, return await GenerateExcelFileStreamAsync(well, host, operations, token);
CancellationToken cancellationToken) }
{
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
using var workbook = new XLWorkbook(excelTemplateStream); private async Task<Stream> GenerateExcelFileStreamAsync(WellDto well,
string host,
IEnumerable<DetectedOperationDto> operationDetectorResults,
CancellationToken cancellationToken)
{
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
AddToWorkbook(workbook, well, host, operationDetectorResults); using var workbook = new XLWorkbook(excelTemplateStream);
MemoryStream memoryStream = new MemoryStream(); AddToWorkbook(workbook, well, host, operationDetectorResults);
workbook.SaveAs(memoryStream, new SaveOptions { });
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
private void AddToWorkbook(XLWorkbook workbook, WellDto well, string host, IEnumerable<DetectedOperationDto> operations) MemoryStream memoryStream = new MemoryStream();
{ workbook.SaveAs(memoryStream, new SaveOptions { });
const string sheetName = "Операции"; memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
if (!operations.Any()) private void AddToWorkbook(XLWorkbook workbook, WellDto well, string host, IEnumerable<DetectedOperationDto> operations)
return; {
const string sheetName = "Операции";
var sheet = workbook.GetWorksheet(sheetName); if (!operations.Any())
return;
var orderedOperations = operations var sheet = workbook.GetWorksheet(sheetName);
.OrderBy(x => x.DateStart)
.ThenBy(x => x.DepthStart).ToArray();
AddToSheet(sheet, well, host, orderedOperations); var orderedOperations = operations
} .OrderBy(x => x.DateStart)
.ThenBy(x => x.DepthStart).ToArray();
private void AddToSheet(IXLWorksheet sheet, WellDto well, string host, IList<DetectedOperationDto> operations) AddToSheet(sheet, well, host, orderedOperations);
{ }
var wellOperationCategories = wellOperationCategoryRepository.Get(true);
sheet.Cell(cellDepositName).SetCellValue(well.Deposit); private void AddToSheet(IXLWorksheet sheet, WellDto well, string host, IList<DetectedOperationDto> operations)
sheet.Cell(cellClusterName).SetCellValue(well.Cluster); {
sheet.Cell(cellWellName).SetCellValue(well.Caption); var wellOperationCategories = wellOperationCategoryRepository.Get(true);
var deltaDate = operations.Max(o => o.DateEnd - o.DateStart); sheet.Cell(cellDepositName).SetCellValue(well.Deposit);
sheet.Cell(cellClusterName).SetCellValue(well.Cluster);
sheet.Cell(cellDeltaDate).SetCellValue(deltaDate); sheet.Cell(cellWellName).SetCellValue(well.Caption);
for (int i = 0; i < operations.Count; i++) var deltaDate = operations.Max(o => o.DateEnd - o.DateStart);
{
var current = operations[i];
var dateStart = current.DateStart.DateTime;
var dateEnd = current.DateEnd.DateTime;
var row = sheet.Row(5 + i + headerRowsCount); sheet.Cell(cellDeltaDate).SetCellValue(deltaDate);
var categoryName = GetCategoryName(wellOperationCategories, current); for (int i = 0; i < operations.Count; i++)
{
row.Cell(columnDateStart).SetCellValue(dateStart); var current = operations[i];
row.Cell(columnOperationName).SetCellValue(categoryName); var dateStart = current.DateStart.DateTime;
row.Cell(columnDateEnd).SetCellValue(dateEnd); var dateEnd = current.DateEnd.DateTime;
row.Cell(columnDuration).SetCellValue((dateEnd - dateStart).TotalMinutes);
row.Cell(columnDepthStart).SetCellValue(current.DepthStart);
row.Cell(columnDepthEnd).SetCellValue(current.DepthEnd);
row.Cell(columnDepth).SetCellValue(current.DepthEnd - current.DepthStart);
if (current.ExtraData.TryGetValue("IdReasonOfEnd", out object? idReasonOfEndObject) var row = sheet.Row(5 + i + headerRowsCount);
&& idReasonOfEndObject is int idReasonOfEnd)
{
var reasonOfEnd = GetIdReasonOfEnd(idReasonOfEnd);
row.Cell(columnIdReasonOfEnd).SetCellValue(reasonOfEnd);
}
var query = new QueryBuilder(); var categoryName = GetCategoryName(wellOperationCategories, current);
query.Add("end", dateStart.AddSeconds(1800 * 0.9).ToString("yyyy-MM-ddTHH:mm:ss.fff"));
query.Add("range", "1800");
var link = $"{host}/well/{well.Id}/telemetry/monitoring{query}"; row.Cell(columnDateStart).SetCellValue(dateStart);
row.Cell(columnDateStart).SetHyperlink(link); row.Cell(columnOperationName).SetCellValue(categoryName);
row.Cell(columnDateEnd).SetCellValue(dateEnd);
var deltaDepth = i > 0 && i + 1 < operations.Count row.Cell(columnDuration).SetCellValue((dateEnd - dateStart).TotalMinutes);
? current.DepthStart - operations[i - 1].DepthEnd row.Cell(columnDepthStart).SetCellValue(current.DepthStart);
: 0; row.Cell(columnDepthEnd).SetCellValue(current.DepthEnd);
row.Cell(columnDepth).SetCellValue(current.DepthEnd - current.DepthStart);
row.Cell(columnDeltaDepth).SetCellValue(deltaDepth); if (current.ExtraData.TryGetValue("IdReasonOfEnd", out object? idReasonOfEndObject)
&& idReasonOfEndObject is int idReasonOfEnd)
{
var reasonOfEnd = GetIdReasonOfEnd(idReasonOfEnd);
row.Cell(columnIdReasonOfEnd).SetCellValue(reasonOfEnd);
}
var comment = CreateComment(operations[i]); var query = new QueryBuilder();
row.Cell(columnComment).SetCellValue(comment); query.Add("end", dateStart.AddSeconds(1800 * 0.9).ToString("yyyy-MM-ddTHH:mm:ss.fff"));
} query.Add("range", "1800");
}
private static string GetCategoryName(IEnumerable<WellOperationCategoryDto> wellOperationCategories, DetectedOperationDto current) var link = $"{host}/well/{well.Id}/telemetry/monitoring{query}";
{ row.Cell(columnDateStart).SetHyperlink(link);
var idCategory = current.IdCategory;
if (idCategory == WellOperationCategory.IdSlide && current.EnabledSubsystems.IsAutoOscillation)
return "Бурение в слайде с осцилляцией";
var category = wellOperationCategories.FirstOrDefault(o => o.Id == current.IdCategory); var deltaDepth = i > 0 && i + 1 < operations.Count
? current.DepthStart - operations[i - 1].DepthEnd
if(category is not null) : 0;
return category.Name;
return $"Операция №{idCategory}"; row.Cell(columnDeltaDepth).SetCellValue(deltaDepth);
}
private static string GetIdReasonOfEnd(int idReasonOfEnd) var comment = CreateComment(operations[i]);
=> idReasonOfEnd switch { row.Cell(columnComment).SetCellValue(comment);
0 => "Не определена", }
1 => "Не определено начало операции", }
101 => "Разница глубин забоя и положением долота",
300 => "Низкое давление",
301 => "Высокое давление",
700 => "Изменение глубины долота и осевая нагрузка < веса на крюке",
_ => idReasonOfEnd.ToString($"Причина № {idReasonOfEnd}"),
}; private static string GetCategoryName(IEnumerable<WellOperationCategoryDto> wellOperationCategories, DetectedOperationDto current)
{
var idCategory = current.IdCategory;
if (idCategory == WellOperationCategory.IdSlide && current.EnabledSubsystems.IsAutoOscillation)
return "Бурение в слайде с осцилляцией";
private async Task<Stream> GetExcelTemplateStreamAsync(CancellationToken cancellationToken) var category = wellOperationCategories.FirstOrDefault(o => o.Id == current.IdCategory);
{
string resourceName = Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith("DetectOperations.xlsx"))!;
using var stream = Assembly.GetExecutingAssembly() if (category is not null)
.GetManifestResourceStream(resourceName)!; return category.Name;
var memoryStream = new MemoryStream(); return $"Операция №{idCategory}";
await stream.CopyToAsync(memoryStream, cancellationToken); }
memoryStream.Position = 0;
return memoryStream; private static string GetIdReasonOfEnd(int idReasonOfEnd)
} => idReasonOfEnd switch
{
private static string CreateComment(DetectedOperationDto operation) 0 => "Не определена",
{ 1 => "Не определено начало операции",
switch (operation.IdCategory) 101 => "Разница глубин забоя и положением долота",
{ 300 => "Низкое давление",
case WellOperationCategory.IdRotor: 301 => "Высокое давление",
case WellOperationCategory.IdSlide: 700 => "Изменение глубины долота и осевая нагрузка < веса на крюке",
var comment = ""; _ => idReasonOfEnd.ToString($"Причина № {idReasonOfEnd}"),
if (operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyAvgRotorSpeed, out object? oAvgRotorSpeed)) };
comment += $"Средняя скорость оборотов ротора: {oAvgRotorSpeed}\r\n";
if (operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyDispersionOfNormalizedRotorSpeed, out object? oDispersionOfNormalizedRotorSpeed)) private async Task<Stream> GetExcelTemplateStreamAsync(CancellationToken cancellationToken)
comment += $"Дисперсия нормированных оборотов ротора: {oDispersionOfNormalizedRotorSpeed}"; {
string resourceName = Assembly.GetExecutingAssembly()
return comment; .GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith("DetectOperations.xlsx"))!;
default:
return string.Empty; using var stream = Assembly.GetExecutingAssembly()
} .GetManifestResourceStream(resourceName)!;
}
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
return memoryStream;
}
private static string CreateComment(DetectedOperationDto operation)
{
switch (operation.IdCategory)
{
case WellOperationCategory.IdRotor:
case WellOperationCategory.IdSlide:
var comment = "";
if (operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyAvgRotorSpeed, out object? oAvgRotorSpeed))
comment += $"Средняя скорость оборотов ротора: {oAvgRotorSpeed}\r\n";
if (operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyDispersionOfNormalizedRotorSpeed,
out object? oDispersionOfNormalizedRotorSpeed))
comment += $"Дисперсия нормированных оборотов ротора: {oDispersionOfNormalizedRotorSpeed}";
return comment;
default:
return string.Empty;
}
}
} }

View File

@ -32,7 +32,7 @@ public class DetectedOperationService : IDetectedOperationService
new DetectorFlashing(), new DetectorFlashing(),
new DetectorConditioning(), new DetectorConditioning(),
}; };
public DetectedOperationService( public DetectedOperationService(
IDetectedOperationRepository operationRepository, IDetectedOperationRepository operationRepository,
IWellOperationCategoryRepository wellOperationCategoryRepository, IWellOperationCategoryRepository wellOperationCategoryRepository,
@ -110,7 +110,7 @@ public class DetectedOperationService : IDetectedOperationService
private async Task<int> GetIdTelemetryByWell(int idWell, CancellationToken token) private async Task<int> GetIdTelemetryByWell(int idWell, CancellationToken token)
{ {
var well = await wellService.GetOrDefaultAsync(idWell, token) ?? var well = await wellService.GetOrDefaultAsync(idWell, token) ??
throw new ArgumentInvalidException(nameof(idWell), "Well doesn`t exist"); throw new ArgumentInvalidException(nameof(idWell), "Well doesn't exist");
var idTelemetry = well.IdTelemetry ?? var idTelemetry = well.IdTelemetry ??
throw new ArgumentInvalidException(nameof(idWell), "У скважины отсутствует телеметрия"); throw new ArgumentInvalidException(nameof(idWell), "У скважины отсутствует телеметрия");
@ -177,72 +177,73 @@ public class DetectedOperationService : IDetectedOperationService
return dtos; return dtos;
} }
public async Task<IEnumerable<DetectedOperationDto>> DetectOperationsAsync(int idTelemetry, DateTimeOffset? beginDate, CancellationToken token) public async Task<(DateTimeOffset LastDate, IEnumerable<DetectedOperationDto> Items)> DetectOperationsAsync(int idTelemetry,
TelemetryDataRequest request,
DetectedOperationDto? lastDetectedOperation,
CancellationToken token)
{ {
const int take = 4 * 86_400;
var detectedOperations = new List<DetectedOperationDto>();
DetectedOperationDto? lastDetectedOperation = null;
const int minOperationLength = 5; const int minOperationLength = 5;
const int maxDetectorsInterpolationFrameLength = 30; const int maxDetectorsInterpolationFrameLength = 30;
const int gap = maxDetectorsInterpolationFrameLength + minOperationLength; const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;
var timezone = telemetryService.GetTimezone(idTelemetry);
while (true) var telemetries = await telemetryDataSaubService.GetByTelemetryAsync(idTelemetry, request, token);
var count = telemetries.Count();
if (count == 0)
throw new InvalidOperationException("InvalidOperation_EmptyTelemetries");
var timeZone = telemetryService.GetTimezone(idTelemetry);
var detectedOperations = new List<DetectedOperationDto>();
var detectableTelemetries = telemetries
.Where(t => t.BlockPosition >= 0)
.Select(t => new DetectableTelemetry
{
DateTime = new DateTimeOffset(t.DateTime, timeZone.Offset),
IdUser = t.IdUser,
Mode = t.Mode,
WellDepth = t.WellDepth,
Pressure = t.Pressure,
HookWeight = t.HookWeight,
BlockPosition = t.BlockPosition,
BitDepth = t.BitDepth,
RotorSpeed = t.RotorSpeed,
AxialLoad = t.AxialLoad,
}).ToArray();
if (detectableTelemetries.Length <= gap)
{ {
var request = new TelemetryDataRequest var lastTelemetry = telemetries.Last();
{ var lastDateTelemetry = new DateTimeOffset(lastTelemetry.DateTime, timeZone.Offset);
GeDate = beginDate, return (lastDateTelemetry, Enumerable.Empty<DetectedOperationDto>());
Take = take,
Order = 0
};
var dtos = await telemetryDataSaubService.GetByTelemetryAsync(idTelemetry, request, token);
var detectableTelemetries = dtos
.Where(t => t.BlockPosition >= 0)
.Select(t => new DetectableTelemetry
{
DateTime = new DateTimeOffset(t.DateTime, timezone.Offset),
IdUser = t.IdUser,
Mode = t.Mode,
WellDepth = t.WellDepth,
Pressure = t.Pressure,
HookWeight = t.HookWeight,
BlockPosition = t.BlockPosition,
BitDepth = t.BitDepth,
RotorSpeed = t.RotorSpeed,
AxialLoad = t.AxialLoad,
}).ToArray();
if (detectableTelemetries.Length <= gap)
break;
var isDetected = false;
var positionBegin = 0;
var positionEnd = detectableTelemetries.Length - gap;
while (positionEnd > positionBegin)
{
foreach (var detector in detectors)
{
if (!detector.TryDetect(idTelemetry, detectableTelemetries, positionBegin, positionEnd, lastDetectedOperation, out var result))
continue;
detectedOperations.Add(result!.Operation);
lastDetectedOperation = result.Operation;
isDetected = true;
positionBegin = result.TelemetryEnd;
break;
}
positionBegin += 1;
}
beginDate = isDetected
? lastDetectedOperation!.DateEnd
: detectableTelemetries[positionEnd].DateTime;
} }
return detectedOperations; var positionBegin = 0;
var positionEnd = detectableTelemetries.Length - gap;
while (positionEnd > positionBegin)
{
foreach (var detector in detectors)
{
if (!detector.TryDetect(idTelemetry, detectableTelemetries, positionBegin, positionEnd, lastDetectedOperation,
out var result))
continue;
detectedOperations.Add(result!.Operation);
lastDetectedOperation = result.Operation;
positionBegin = result.TelemetryEnd;
break;
}
var point0 = detectableTelemetries[positionBegin];
while (positionBegin < positionEnd && IsChangingTelemetryInterval(point0, detectableTelemetries[positionBegin]))
positionBegin++;
}
return (detectableTelemetries[positionBegin].DateTime, detectedOperations);
} }
public async Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token) public async Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token)
@ -256,6 +257,23 @@ public class DetectedOperationService : IDetectedOperationService
return result; return result;
} }
private static bool IsChangingTelemetryInterval(DetectableTelemetry telemetryBegin, DetectableTelemetry telemetryEnd)
{
return telemetryBegin.Mode == telemetryEnd.Mode &&
EqualParameter(telemetryBegin.WellDepth, telemetryEnd.WellDepth, 0.01f) &&
EqualParameter(telemetryBegin.Pressure, telemetryEnd.Pressure, 0.1f) &&
EqualParameter(telemetryBegin.HookWeight, telemetryEnd.HookWeight, 0.1f) &&
EqualParameter(telemetryBegin.BlockPosition, telemetryEnd.BlockPosition, 0.01f) &&
EqualParameter(telemetryBegin.BitDepth, telemetryEnd.BitDepth, 0.01f) &&
EqualParameter(telemetryBegin.RotorSpeed, telemetryEnd.RotorSpeed, 0.01f) &&
EqualParameter(telemetryBegin.AxialLoad, telemetryEnd.AxialLoad, 0.1f);
static bool EqualParameter(float value, float origin, float tolerance)
{
return value <= origin + tolerance && value >= origin - tolerance;
}
}
private static IEnumerable<DetectedOperationDrillersStatDto> GetOperationsDrillersStat(IEnumerable<DetectedOperationWithDrillerDto> operations) private static IEnumerable<DetectedOperationDrillersStatDto> GetOperationsDrillersStat(IEnumerable<DetectedOperationWithDrillerDto> operations)
{ {
var groups = operations.GroupBy(o => o.Driller); var groups = operations.GroupBy(o => o.Driller);
@ -305,7 +323,6 @@ public class DetectedOperationService : IDetectedOperationService
}; };
} }
private static DetectedOperationWithDrillerDto Convert(DetectedOperationDto operation, IEnumerable<OperationValueDto> operationValues, IEnumerable<ScheduleDto> schedules) private static DetectedOperationWithDrillerDto Convert(DetectedOperationDto operation, IEnumerable<OperationValueDto> operationValues, IEnumerable<ScheduleDto> schedules)
{ {
var dto = operation.Adapt<DetectedOperationWithDrillerDto>(); var dto = operation.Adapt<DetectedOperationWithDrillerDto>();

View File

@ -2,410 +2,410 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using AsbCloudApp.Data.DetectedOperation; using AsbCloudApp.Data.DetectedOperation;
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors;
public abstract class DetectorAbstract
{ {
public abstract class DetectorAbstract protected const int IdReasonOfEnd_NotDetected = 0;
protected const int IdReasonOfEnd_NotDetectBegin = 1;
protected const int IdReasonOfEnd_DeltaDepthIsLo = 100;
protected const int IdReasonOfEnd_DeltaDepthIsHi = 101;
protected const int IdReasonOfEnd_DeltaDepthOutOfRange = 102;
protected const int IdReasonOfEnd_WellDepthDeviates = 200;
protected const int IdReasonOfEnd_PressureIsLo = 300;
protected const int IdReasonOfEnd_PressureIsHi = 301;
protected const int IdReasonOfEnd_PressureOutOfRange = 302;
protected const int IdReasonOfEnd_PressureIsRising = 303;
protected const int IdReasonOfEnd_RotorSpeedIsLo = 400;
protected const int IdReasonOfEnd_RotorSpeedIsHi = 401;
protected const int IdReasonOfEnd_AvgRotorSpeedIsHi = 402;
protected const int IdReasonOfEnd_AvgRotorSpeedIsLo = 403;
protected const int IdReasonOfEnd_BlockPositionIsLo = 500;
protected const int IdReasonOfEnd_BlockPositionIsHi = 501;
protected const int IdReasonOfEnd_BlockPositionDeviates = 502;
protected const int IdReasonOfEnd_Drilling = 600;
protected const int IdReasonOfEnd_ChangeBitDepthAndAxiLoadLessHookWeight = 700;
protected const int IdReasonOfEnd_DeltaWellDepthAndBitDepthIsLo = 800;
protected const int IdReasonOfEnd_BitDepthIsLo = 900;
public bool TryDetect(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end, DetectedOperationDto? previousOperation,
out OperationDetectorResult? result)
{ {
protected const int IdReasonOfEnd_NotDetected = 0; // Проверка соответствия критерию начала операции
protected const int IdReasonOfEnd_NotDetectBegin = 1; if (DetectBegin(telemetry, begin, previousOperation))
protected const int IdReasonOfEnd_DeltaDepthIsLo = 100;
protected const int IdReasonOfEnd_DeltaDepthIsHi = 101;
protected const int IdReasonOfEnd_DeltaDepthOutOfRange = 102;
protected const int IdReasonOfEnd_WellDepthDeviates = 200;
protected const int IdReasonOfEnd_PressureIsLo = 300;
protected const int IdReasonOfEnd_PressureIsHi = 301;
protected const int IdReasonOfEnd_PressureOutOfRange = 302;
protected const int IdReasonOfEnd_PressureIsRising = 303;
protected const int IdReasonOfEnd_RotorSpeedIsLo = 400;
protected const int IdReasonOfEnd_RotorSpeedIsHi = 401;
protected const int IdReasonOfEnd_AvgRotorSpeedIsHi = 402;
protected const int IdReasonOfEnd_AvgRotorSpeedIsLo = 403;
protected const int IdReasonOfEnd_BlockPositionIsLo = 500;
protected const int IdReasonOfEnd_BlockPositionIsHi = 501;
protected const int IdReasonOfEnd_BlockPositionDeviates = 502;
protected const int IdReasonOfEnd_Drilling = 600;
protected const int IdReasonOfEnd_ChangeBithDepthAndAxiloadLessHookWeight = 700;
protected const int IdReasonOfEnd_DeltaWellDepthAndBithDepthIsLo = 800;
protected const int IdReasonOfEnd_BithDepthIsLo = 900;
public bool TryDetect(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end, DetectedOperationDto? previousOperation,
out OperationDetectorResult? result)
{ {
// Проверка соответствия критерию начала операции // Поиск окончания соответствия критерию
if (DetectBegin(telemetry, begin, previousOperation)) int idReasonOfEnd = 0;
{ var positionEnd = begin;
// Поиск окончания соответствия критерию
int idReasonOfEnd = 0;
var positionEnd = begin;
while (positionEnd < end)
{
positionEnd += 1;
if (positionEnd > end)
break;
idReasonOfEnd = DetectEnd(telemetry, positionEnd, previousOperation);
if (idReasonOfEnd != IdReasonOfEnd_NotDetected)
break;
}
var (Begin, End) = RefineEdges(telemetry, begin, positionEnd);
result = MakeOperationDetectorResult(idTelemetry, telemetry, Begin, End, idReasonOfEnd);
return IsValidOperationDetectorResult(result);
}
result = null;
return false;
}
protected virtual (int Begin, int End) RefineEdges(DetectableTelemetry[] telemetry, int begin, int end)
=> (begin, end);
protected virtual bool IsValidOperationDetectorResult(OperationDetectorResult operationDetectorResult)
=> operationDetectorResult.Operation.DateEnd - operationDetectorResult.Operation.DateStart > TimeSpan.FromSeconds(3);
protected abstract bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation);
protected virtual int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation)
=> DetectBegin(telemetry, position, previousOperation)
? IdReasonOfEnd_NotDetected
: IdReasonOfEnd_NotDetectBegin;
private OperationDetectorResult MakeOperationDetectorResult(
int idTelemetry,
DetectableTelemetry[] telemetry,
int begin,
int end,
int idReasonOfEnd)
{
var operation = MakeDetectedOperation(idTelemetry, telemetry, begin, end);
operation.ExtraData["IdReasonOfEnd"] = idReasonOfEnd;
var result = new OperationDetectorResult
{
TelemetryBegin = begin,
TelemetryEnd = end,
Operation = operation,
};
return result;
}
private DetectedOperationDto MakeDetectedOperation(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end)
{
var pBegin = telemetry[begin];
var pEnd = telemetry[end];
var (IdCategory, ExtraData) = GetSpecificInformation(telemetry, begin, end);
var operation = new DetectedOperationDto
{
IdCategory = IdCategory,
IdTelemetry = idTelemetry,
IdUserAtStart = pBegin.IdUser ?? -1,
DateStart = pBegin.DateTime,
DateEnd = pEnd.DateTime,
DepthStart = (double)pBegin.WellDepth,
DepthEnd = (double)pEnd.WellDepth,
ExtraData = ExtraData,
Value = CalcValue(telemetry, begin, end),
EnabledSubsystems = DetectEnabledSubsystems(telemetry, begin, end, ExtraData)
};
return operation;
}
/// <summary>
/// Получение информации специфичной для конкретного детектора
/// IdCategory - одна из констант WellOperationCategory
/// ExtraData - дополнительная информация для отладки алгоритмов авто определения
/// </summary>
/// <returns></returns>
protected abstract (int IdCategory, IDictionary<string, object> ExtraData) GetSpecificInformation(DetectableTelemetry[] telemetry, int begin, int end);
/// <summary>
/// Расчет ключевого параметра операции
/// </summary>
/// <param name="telemetry"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <returns></returns>
protected abstract double CalcValue(DetectableTelemetry[] telemetry, int begin, int end);
/// <summary>
/// Определение включенных подсистем во время выполнения операции
/// </summary>
/// <param name="telemetry"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <param name="extraData"></param>
/// <returns></returns>
private static int DetectEnabledSubsystems(DetectableTelemetry[] telemetry, int begin, int end, IDictionary<string, object> extraData)
{
var enabledSubsystems = 0;
if (extraData.TryGetValue(DetectorDrilling.ExtraDataKeyHasOscillation, out var hasOscillation)
&& hasOscillation is true)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoOscillation;
for (var i = begin; i < end; i += 2) while (positionEnd < end)
{ {
var mode = telemetry[i].Mode; positionEnd += 1;
if (positionEnd > end)
break;
//TODO: поиск провалов телеметрий. Следует обсудить, так как алгоритмы теряют в точности.
if(mode == 1) idReasonOfEnd = DetectEnd(telemetry, positionEnd, previousOperation);
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoRotor;
if (mode == 3) if (idReasonOfEnd != IdReasonOfEnd_NotDetected)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoSlide; break;
if (mode == 2)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoConditionig;
if (mode == 4)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoSinking;
if (mode == 5)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoLifting;
if (mode == 6)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoLiftingWithConditionig;
if (mode == 10)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoBlocknig;
} }
var (Begin, End) = RefineEdges(telemetry, begin, positionEnd);
result = MakeOperationDetectorResult(idTelemetry, telemetry, Begin, End, idReasonOfEnd);
return enabledSubsystems; return IsValidOperationDetectorResult(result);
} }
/// <summary> result = null;
/// расчет продолжительности операции return false;
/// </summary> }
/// <param name="telemetry"></param>
/// <param name="begin"></param> protected virtual (int Begin, int End) RefineEdges(DetectableTelemetry[] telemetry, int begin, int end)
/// <param name="end"></param> => (begin, end);
/// <returns></returns>
protected static double CalcDeltaMinutes(DetectableTelemetry[] telemetry, int begin, int end) protected virtual bool IsValidOperationDetectorResult(OperationDetectorResult operationDetectorResult)
=> operationDetectorResult.Operation.DateEnd - operationDetectorResult.Operation.DateStart > TimeSpan.FromSeconds(3);
protected abstract bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation);
protected virtual int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation)
=> DetectBegin(telemetry, position, previousOperation)
? IdReasonOfEnd_NotDetected
: IdReasonOfEnd_NotDetectBegin;
private OperationDetectorResult MakeOperationDetectorResult(
int idTelemetry,
DetectableTelemetry[] telemetry,
int begin,
int end,
int idReasonOfEnd)
{
var operation = MakeDetectedOperation(idTelemetry, telemetry, begin, end);
operation.ExtraData["IdReasonOfEnd"] = idReasonOfEnd;
var result = new OperationDetectorResult
{ {
var pBegin = telemetry[begin]; TelemetryBegin = begin,
var pEnd = telemetry[end]; TelemetryEnd = end,
var result = (pEnd.DateTime - pBegin.DateTime).TotalMinutes; Operation = operation,
return result; };
return result;
}
private DetectedOperationDto MakeDetectedOperation(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end)
{
var pBegin = telemetry[begin];
var pEnd = telemetry[end];
var (IdCategory, ExtraData) = GetSpecificInformation(telemetry, begin, end);
var operation = new DetectedOperationDto
{
IdCategory = IdCategory,
IdTelemetry = idTelemetry,
IdUserAtStart = pBegin.IdUser ?? -1,
DateStart = pBegin.DateTime,
DateEnd = pEnd.DateTime,
DepthStart = (double)pBegin.WellDepth,
DepthEnd = (double)pEnd.WellDepth,
ExtraData = ExtraData,
Value = CalcValue(telemetry, begin, end),
EnabledSubsystems = DetectEnabledSubsystems(telemetry, begin, end, ExtraData)
};
return operation;
}
/// <summary>
/// Получение информации специфичной для конкретного детектора
/// IdCategory - одна из констант WellOperationCategory
/// ExtraData - дополнительная информация для отладки алгоритмов авто определения
/// </summary>
/// <returns></returns>
protected abstract (int IdCategory, IDictionary<string, object> ExtraData) GetSpecificInformation(DetectableTelemetry[] telemetry, int begin, int end);
/// <summary>
/// Расчет ключевого параметра операции
/// </summary>
/// <param name="telemetry"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <returns></returns>
protected abstract double CalcValue(DetectableTelemetry[] telemetry, int begin, int end);
/// <summary>
/// Определение включенных подсистем во время выполнения операции
/// </summary>
/// <param name="telemetry"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <param name="extraData"></param>
/// <returns></returns>
private static int DetectEnabledSubsystems(DetectableTelemetry[] telemetry, int begin, int end, IDictionary<string, object> extraData)
{
var enabledSubsystems = 0;
if (extraData.TryGetValue(DetectorDrilling.ExtraDataKeyHasOscillation, out var hasOscillation)
&& hasOscillation is true)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoOscillation;
for (var i = begin; i < end; i += 2)
{
var mode = telemetry[i].Mode;
if(mode == 1)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoRotor;
if (mode == 3)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoSlide;
if (mode == 2)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoConditionig;
if (mode == 4)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoSinking;
if (mode == 5)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoLifting;
if (mode == 6)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoLiftingWithConditionig;
if (mode == 10)
enabledSubsystems |= (int)EnabledSubsystemsFlags.AutoBlocknig;
} }
/// <summary> return enabledSubsystems;
/// часто используемый предикат для определения отсутствия изменения глубины ствола скважины }
/// </summary>
/// <param name="telemetry"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <returns></returns>
protected static bool IsValidByWellDepthDoesNotChange(DetectableTelemetry[] telemetry, int begin, int end)
{
var pBegin = telemetry[begin];
var pEnd = telemetry[end];
if (Math.Abs((double)(pBegin.WellDepth - pEnd.WellDepth)) > 0.01)
return false;
return true;
}
protected static bool IsValidByWellDepthIncreasing(DetectableTelemetry[] telemetry, int begin, int end) /// <summary>
{ /// расчет продолжительности операции
var pBegin = telemetry[begin]; /// </summary>
var pEnd = telemetry[end]; /// <param name="telemetry"></param>
if (pBegin.WellDepth >= pEnd.WellDepth) /// <param name="begin"></param>
return false; /// <param name="end"></param>
return true; /// <returns></returns>
} protected static double CalcDeltaMinutes(DetectableTelemetry[] telemetry, int begin, int end)
{
protected static double CalcRop(DetectableTelemetry[] telemetry, int begin, int end) var pBegin = telemetry[begin];
{ var pEnd = telemetry[end];
var pBegin = telemetry[begin]; var result = (pEnd.DateTime - pBegin.DateTime).TotalMinutes;
var pEnd = telemetry[end]; return result;
var result = (double)(pEnd.WellDepth - pBegin.WellDepth) / (pEnd.DateTime - pBegin.DateTime).TotalHours; }
return result;
}
/// <summary>
/// Расчет статистики по массиву данных за интервал
/// </summary>
/// <param name="telemetry"></param>
/// <param name="getter"></param>
/// <param name="begin"></param>
/// <param name="count"></param>
/// <returns></returns>
protected static (double min, double max, double sum, int count) CalcStat(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count)
{
var sum = 0d;
var min = double.MaxValue;
var max = double.MinValue;
var end = begin + count;
end = end < telemetry.Length ? end : telemetry.Length;
for (var i = begin; i < end; i++)
{
var item = telemetry[i];
var itemValue = getter(item);
if (min > itemValue)
min = itemValue;
if (max < itemValue)
max = itemValue;
sum += itemValue;
}
return (min, max, sum, end - begin);
}
/// <summary>
/// Максимальное отклонение от среднего за интервал
/// </summary>
/// <param name="telemetry"></param>
/// <param name="getter"></param>
/// <param name="begin"></param>
/// <param name="count"></param>
/// <returns></returns>
protected static double CalcMaxDeviation(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count)
{
var stat = CalcStat(telemetry, getter, begin, count);
var avg = stat.sum / stat.count;
var dev1 = avg - stat.min;
var dev2 = stat.max - avg;
var dev = dev1 > dev2 ? dev1 : dev2;
return dev;
}
/// <summary>
/// Определяет наличие разброса значений в интервале большего указанного значения.
/// </summary>
/// <param name="telemetry"></param>
/// <param name="getter"></param>
/// <param name="begin"></param>
/// <param name="count"></param>
/// <param name="deviation"></param>
/// <returns></returns>
protected static bool ContainsDeviation(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count,
double deviation)
{
var min = double.MaxValue;
var max = double.MinValue;
var end = begin + count;
end = end < telemetry.Length ? end : telemetry.Length;
for (var i = begin; i < end; i++)
{
var item = telemetry[i];
var itemValue = getter(item);
if (min > itemValue)
min = itemValue;
if (max < itemValue)
max = itemValue;
if (max - min > deviation)
return true;
}
/// <summary>
/// часто используемый предикат для определения отсутствия изменения глубины ствола скважины
/// </summary>
/// <param name="telemetry"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <returns></returns>
protected static bool IsValidByWellDepthDoesNotChange(DetectableTelemetry[] telemetry, int begin, int end)
{
var pBegin = telemetry[begin];
var pEnd = telemetry[end];
if (Math.Abs((double)(pBegin.WellDepth - pEnd.WellDepth)) > 0.01)
return false; return false;
} return true;
}
/// <summary>
/// Определяет наличие разброса значений в интервале большего указанного значения. По нескольким значениям из интервала.
/// </summary>
/// <param name="telemetry"></param>
/// <param name="getter"></param>
/// <param name="begin"></param>
/// <param name="count"></param>
/// <param name="deviation"></param>
/// <returns></returns>
protected static bool ContainsDeviationApprox(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count,
double deviation)
{
var min = double.MaxValue;
var max = double.MinValue;
var end = begin + count;
end = end < telemetry.Length ? end : telemetry.Length;
var step = count > 15 ? count / 5 : count > 3 ? 3 : 1;
for (var i = begin; i < end; i += step)
{
var item = telemetry[i];
var itemValue = getter(item);
if (min > itemValue)
min = itemValue;
if (max < itemValue)
max = itemValue;
if (max - min > deviation)
return true;
}
protected static bool IsValidByWellDepthIncreasing(DetectableTelemetry[] telemetry, int begin, int end)
{
var pBegin = telemetry[begin];
var pEnd = telemetry[end];
if (pBegin.WellDepth >= pEnd.WellDepth)
return false; return false;
} return true;
}
protected static bool DeviatesFromBegin( protected static double CalcRop(DetectableTelemetry[] telemetry, int begin, int end)
DetectableTelemetry[] telemetry, {
Func<DetectableTelemetry, double> getter, var pBegin = telemetry[begin];
int begin, var pEnd = telemetry[end];
int count, var result = (double)(pEnd.WellDepth - pBegin.WellDepth) / (pEnd.DateTime - pBegin.DateTime).TotalHours;
double deviation) return result;
}
/// <summary>
/// Расчет статистики по массиву данных за интервал
/// </summary>
/// <param name="telemetry"></param>
/// <param name="getter"></param>
/// <param name="begin"></param>
/// <param name="count"></param>
/// <returns></returns>
protected static (double min, double max, double sum, int count) CalcStat(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count)
{
var sum = 0d;
var min = double.MaxValue;
var max = double.MinValue;
var end = begin + count;
end = end < telemetry.Length ? end : telemetry.Length;
for (var i = begin; i < end; i++)
{ {
var beginPointValue = getter(telemetry[begin]); var item = telemetry[i];
var end = begin + count; var itemValue = getter(item);
end = end < telemetry.Length ? end : telemetry.Length; if (min > itemValue)
var step = count > 15 ? count / 5 : count > 3 ? 3 : 1; min = itemValue;
for (var i = begin; i < end; i += step) if (max < itemValue)
{ max = itemValue;
var item = telemetry[i]; sum += itemValue;
var itemValue = getter(item);
if (Math.Abs(beginPointValue - itemValue) > deviation)
return true;
}
return false;
} }
protected static bool RisesFromBegin( return (min, max, sum, end - begin);
DetectableTelemetry[] telemetry, }
Func<DetectableTelemetry, double> getter,
int begin, /// <summary>
int count, /// Максимальное отклонение от среднего за интервал
double deviation) /// </summary>
/// <param name="telemetry"></param>
/// <param name="getter"></param>
/// <param name="begin"></param>
/// <param name="count"></param>
/// <returns></returns>
protected static double CalcMaxDeviation(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count)
{
var stat = CalcStat(telemetry, getter, begin, count);
var avg = stat.sum / stat.count;
var dev1 = avg - stat.min;
var dev2 = stat.max - avg;
var dev = dev1 > dev2 ? dev1 : dev2;
return dev;
}
/// <summary>
/// Определяет наличие разброса значений в интервале большего указанного значения.
/// </summary>
/// <param name="telemetry"></param>
/// <param name="getter"></param>
/// <param name="begin"></param>
/// <param name="count"></param>
/// <param name="deviation"></param>
/// <returns></returns>
protected static bool ContainsDeviation(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count,
double deviation)
{
var min = double.MaxValue;
var max = double.MinValue;
var end = begin + count;
end = end < telemetry.Length ? end : telemetry.Length;
for (var i = begin; i < end; i++)
{ {
var beginPointValue = getter(telemetry[begin]); var item = telemetry[i];
var end = begin + count; var itemValue = getter(item);
end = end < telemetry.Length ? end : telemetry.Length; if (min > itemValue)
var step = count > 15 ? count / 5 : count > 3 ? 3 : 1; min = itemValue;
for (var i = begin; i < end; i += step) if (max < itemValue)
{ max = itemValue;
var item = telemetry[i]; if (max - min > deviation)
var itemValue = getter(item); return true;
if (itemValue - beginPointValue > deviation)
return true;
}
return false;
} }
return false;
}
/// <summary>
/// Определяет наличие разброса значений в интервале большего указанного значения. По нескольким значениям из интервала.
/// </summary>
/// <param name="telemetry"></param>
/// <param name="getter"></param>
/// <param name="begin"></param>
/// <param name="count"></param>
/// <param name="deviation"></param>
/// <returns></returns>
protected static bool ContainsDeviationApprox(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count,
double deviation)
{
var min = double.MaxValue;
var max = double.MinValue;
var end = begin + count;
end = end < telemetry.Length ? end : telemetry.Length;
var step = count > 15 ? count / 5 : count > 3 ? 3 : 1;
for (var i = begin; i < end; i += step)
{
var item = telemetry[i];
var itemValue = getter(item);
if (min > itemValue)
min = itemValue;
if (max < itemValue)
max = itemValue;
if (max - min > deviation)
return true;
}
return false;
}
protected static bool DeviatesFromBegin(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count,
double deviation)
{
var beginPointValue = getter(telemetry[begin]);
var end = begin + count;
end = end < telemetry.Length ? end : telemetry.Length;
var step = count > 15 ? count / 5 : count > 3 ? 3 : 1;
for (var i = begin; i < end; i += step)
{
var item = telemetry[i];
var itemValue = getter(item);
if (Math.Abs(beginPointValue - itemValue) > deviation)
return true;
}
return false;
}
protected static bool RisesFromBegin(
DetectableTelemetry[] telemetry,
Func<DetectableTelemetry, double> getter,
int begin,
int count,
double deviation)
{
var beginPointValue = getter(telemetry[begin]);
var end = begin + count;
end = end < telemetry.Length ? end : telemetry.Length;
var step = count > 15 ? count / 5 : count > 3 ? 3 : 1;
for (var i = begin; i < end; i += step)
{
var item = telemetry[i];
var itemValue = getter(item);
if (itemValue - beginPointValue > deviation)
return true;
}
return false;
} }
} }

View File

@ -41,9 +41,9 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
if (currentPoint.RotorSpeed <=8) if (currentPoint.RotorSpeed <=8)
return IdReasonOfEnd_RotorSpeedIsHi; return IdReasonOfEnd_RotorSpeedIsHi;
if ((currentPoint.WellDepth - currentPoint.BitDepth) < 0.03d) if ((currentPoint.WellDepth - currentPoint.BitDepth) < 0.03d)
return IdReasonOfEnd_DeltaWellDepthAndBithDepthIsLo; return IdReasonOfEnd_DeltaWellDepthAndBitDepthIsLo;
if (currentPoint.BitDepth < 150) if (currentPoint.BitDepth < 150)
return IdReasonOfEnd_BithDepthIsLo; return IdReasonOfEnd_BitDepthIsLo;
return IdReasonOfEnd_NotDetected; return IdReasonOfEnd_NotDetected;
} }

View File

@ -48,11 +48,11 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
if (currentPoint.Pressure < 10) if (currentPoint.Pressure < 10)
return IdReasonOfEnd_PressureIsLo; return IdReasonOfEnd_PressureIsLo;
if ((currentPoint.WellDepth - currentPoint.BitDepth) < 0.01d) if ((currentPoint.WellDepth - currentPoint.BitDepth) < 0.01d)
return IdReasonOfEnd_DeltaWellDepthAndBithDepthIsLo; return IdReasonOfEnd_DeltaWellDepthAndBitDepthIsLo;
if (currentPoint.RotorSpeed > 8) if (currentPoint.RotorSpeed > 8)
return IdReasonOfEnd_RotorSpeedIsHi; return IdReasonOfEnd_RotorSpeedIsHi;
if (currentPoint.BitDepth < 150) if (currentPoint.BitDepth < 150)
return IdReasonOfEnd_BithDepthIsLo; return IdReasonOfEnd_BitDepthIsLo;
return IdReasonOfEnd_NotDetected; return IdReasonOfEnd_NotDetected;
} }

View File

@ -50,7 +50,7 @@ public class DetectorSlipsTime : DetectorAbstract
var deltaBitDepth = Math.Abs(currentPoint.BitDepth - prevPoint.BitDepth); var deltaBitDepth = Math.Abs(currentPoint.BitDepth - prevPoint.BitDepth);
if (deltaBitDepth > 0.001d && currentPoint.AxialLoad < currentPoint.HookWeight) if (deltaBitDepth > 0.001d && currentPoint.AxialLoad < currentPoint.HookWeight)
return IdReasonOfEnd_ChangeBithDepthAndAxiloadLessHookWeight; return IdReasonOfEnd_ChangeBitDepthAndAxiLoadLessHookWeight;
return IdReasonOfEnd_NotDetected; return IdReasonOfEnd_NotDetected;
} }

View File

@ -1,54 +1,91 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudInfrastructure.Background; using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace AsbCloudInfrastructure.Services.DetectOperations; namespace AsbCloudInfrastructure.Services.DetectOperations;
public class WorkOperationDetection: Work public class WorkOperationDetection : Work
{ {
private static readonly IDictionary<int, DateTimeOffset> CacheOfStartDatesByTelemetryId = new Dictionary<int, DateTimeOffset>();
public WorkOperationDetection() public WorkOperationDetection()
:base("Operation detection") : base("Operation detection")
{ {
Timeout = TimeSpan.FromMinutes(20); Timeout = TimeSpan.FromMinutes(20);
OnErrorAsync = (id, exception, token) => OnErrorAsync = (id, exception, _) =>
{ {
var text = $"work {id}, when {CurrentState?.State}, throw error:{exception.Message}"; var text = $"work {id}, when {CurrentState?.State}, throw error:{exception.Message}";
Trace.TraceWarning(text); Trace.TraceWarning(text);
return Task.CompletedTask; return Task.CompletedTask;
}; };
} }
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token) protected override async Task Action(string id,
{ IServiceProvider services,
var telemetryRepository = services.GetRequiredService<ICrudRepository<TelemetryDto>>(); Action<string, double?> onProgressCallback,
var detectedOperationRepository = services.GetRequiredService<IDetectedOperationRepository>(); CancellationToken token)
var detectedOperationService = services.GetRequiredService<IDetectedOperationService>(); {
var telemetryRepository = services.GetRequiredService<ICrudRepository<TelemetryDto>>();
var detectedOperationRepository = services.GetRequiredService<IDetectedOperationRepository>();
var detectedOperationService = services.GetRequiredService<IDetectedOperationService>();
var telemetryDataCache = services.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
var telemetryIds = (await telemetryRepository.GetAllAsync(token)) var idsTelemetry = (await telemetryRepository.GetAllAsync(token))
.Select(t => t.Id) .Select(t => t.Id)
.ToArray(); .ToArray();
var lastDetectedDates = await detectedOperationRepository.GetLastDetectedDatesAsync(token); var lastDetectedOperations = await detectedOperationRepository.GetLastDetectedOperationsAsync(token);
for (var i = 0; i < telemetryIds.Length; i++) for (int i = 0; i < idsTelemetry.Length; i++)
{ {
var telemetryId = telemetryIds[i]; var idTelemetry = idsTelemetry[i];
var beginDate = lastDetectedDates.TryGetValue(telemetryId, out var date) ? date : (DateTimeOffset?)null;
onProgressCallback($"Start detecting telemetry: {telemetryId} from {beginDate}", i / telemetryIds.Length); var telemetryDateRange = telemetryDataCache.GetOrDefaultWellDataDateRange(idTelemetry);
var detectedOperations = await detectedOperationService.DetectOperationsAsync(telemetryId, beginDate, token);
if(telemetryDateRange == null)
continue;
if (detectedOperations.Any()) var dateBegin = telemetryDateRange.From;
await detectedOperationRepository.InsertRangeAsync(detectedOperations, token); var dateEnd = telemetryDateRange.To;
}
} if (lastDetectedOperations.TryGetValue(idTelemetry, out var lastDetectedOperation))
} dateBegin = lastDetectedOperation.DateEnd;
if (CacheOfStartDatesByTelemetryId.TryGetValue(idTelemetry, out var dateBeginFromCache))
dateBegin = dateBeginFromCache;
onProgressCallback.Invoke($"Start detecting telemetry: {idTelemetry} from {dateBegin}", i / idsTelemetry.Length);
const int pointsCount = 4 * 86_400;
while (dateBegin < dateEnd)
{
var request = new TelemetryDataRequest
{
GeDate = dateBegin,
Take = pointsCount,
Order = 0
};
var detectedOperations =
await detectedOperationService.DetectOperationsAsync(idTelemetry, request, lastDetectedOperation, token);
dateBegin = detectedOperations.LastDate;
CacheOfStartDatesByTelemetryId[idTelemetry] = dateBegin;
await detectedOperationRepository.InsertRangeAsync(detectedOperations.Items, token);
}
}
}
}

View File

@ -96,7 +96,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
var allUsers = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryUser>(), token); var allUsers = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryUser>(), token);
var users = allUsers.Where(u => u.IdTelemetry == telemetry.Id); var users = allUsers.Where(u => u.IdTelemetry == telemetry.Id);
var eventsDict = events.ToDictionary(x=>x.IdEvent, x => x); var eventsDict = events.ToDictionary(x => x.IdEvent, x => x);
var usersDict = users.ToDictionary(x => x.IdUser, x => x); var usersDict = users.ToDictionary(x => x.IdUser, x => x);
var messagesDtoList = new List<MessageDto>(); var messagesDtoList = new List<MessageDto>();
@ -142,7 +142,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
return Task.CompletedTask; return Task.CompletedTask;
var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid); var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid);
foreach (var dto in dtos) foreach (var dto in dtos)
{ {
var entity = dto.Adapt<TelemetryMessage>(); var entity = dto.Adapt<TelemetryMessage>();
@ -154,6 +154,33 @@ namespace AsbCloudInfrastructure.Services.SAUB
return db.SaveChangesAsync(token); return db.SaveChangesAsync(token);
} }
private IQueryable<TelemetryMessage> BuildQuery(TelemetryPartDeleteRequest request)
{
var query = db.Set<TelemetryMessage>()
.Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.GeDate is not null)
{
var geDate = request.GeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateTime <= geDate);
}
if (request.LeDate is not null)
{
var leDate = request.LeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateTime >= leDate);
}
return query;
}
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var query = BuildQuery(request);
db.Set<TelemetryMessage>().RemoveRange(query);
return await db.SaveChangesAsync(token);
}
} }
} }

View File

@ -1,6 +1,7 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Exceptions; using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
@ -11,276 +12,299 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Requests;
using Mapster;
namespace AsbCloudInfrastructure.Services.SAUB namespace AsbCloudInfrastructure.Services.SAUB;
public abstract class TelemetryDataBaseService<TDto, TEntity> : ITelemetryDataService<TDto>
where TDto : AsbCloudApp.Data.ITelemetryData
where TEntity : class, AsbCloudDb.Model.ITelemetryData
{ {
public abstract class TelemetryDataBaseService<TDto, TEntity> : ITelemetryDataService<TDto> protected readonly IAsbCloudDbContext db;
where TDto : AsbCloudApp.Data.ITelemetryData protected readonly ITelemetryService telemetryService;
where TEntity : class, AsbCloudDb.Model.ITelemetryData protected readonly ITelemetryDataCache<TDto> telemetryDataCache;
{
protected readonly IAsbCloudDbContext db;
protected readonly ITelemetryService telemetryService;
protected readonly ITelemetryDataCache<TDto> telemetryDataCache;
protected TelemetryDataBaseService( protected TelemetryDataBaseService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
ITelemetryService telemetryService, ITelemetryService telemetryService,
ITelemetryDataCache<TDto> telemetryDataCache) ITelemetryDataCache<TDto> telemetryDataCache)
{
this.db = db;
this.telemetryService = telemetryService;
this.telemetryDataCache = telemetryDataCache;
}
/// <inheritdoc/>
public virtual async Task<int> UpdateDataAsync(string uid, IEnumerable<TDto> dtos, CancellationToken token = default)
{
if (dtos == default || !dtos.Any())
return 0;
var dtosList = dtos.OrderBy(d => d.DateTime).ToList();
var dtoMinDate = dtosList.First().DateTime;
var dtoMaxDate = dtosList.Last().DateTime;
if (dtosList.Count > 1)
{ {
this.db = db; var duplicates = new List<TDto>(8);
this.telemetryService = telemetryService; for (int i = 1; i < dtosList.Count; i++)
this.telemetryDataCache = telemetryDataCache; if (dtosList[i].DateTime - dtosList[i - 1].DateTime < TimeSpan.FromMilliseconds(100))
duplicates.Add(dtosList[i - 1]);
foreach (var duplicate in duplicates)
dtosList.Remove(duplicate);
} }
/// <inheritdoc/> var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid);
public virtual async Task<int> UpdateDataAsync(string uid, IEnumerable<TDto> dtos, CancellationToken token = default) var timeZone = telemetryService.GetTimezone(telemetry.Id);
telemetryDataCache.AddRange(telemetry.Id, dtos);
var entities = dtosList.Select(dto =>
{ {
if (dtos == default || !dtos.Any()) var entity = Convert(dto, timeZone.Hours);
return 0; entity.IdTelemetry = telemetry.Id;
return entity;
});
var dtosList = dtos.OrderBy(d => d.DateTime).ToList(); var stopwatch = Stopwatch.StartNew();
var dbSet = db.Set<TEntity>();
try
{
return await db.Database.ExecInsertOrUpdateAsync(dbSet, entities, token).ConfigureAwait(false);
}
catch (Exception ex)
{
stopwatch.Stop();
Trace.WriteLine($"Fail to save data telemetry " +
$"uid: {uid}, " +
$"idTelemetry {telemetry.Id}, " +
$"count: {entities.Count()}, " +
$"dataDate: {entities.FirstOrDefault()?.DateTime}, " +
$"dbSaveDurationTime:{stopwatch.ElapsedMilliseconds}ms. " +
$"Message: {ex.Message}");
return 0;
}
}
var dtoMinDate = dtosList.First().DateTime; /// <inheritdoc/>
var dtoMaxDate = dtosList.Last().DateTime; public virtual async Task<IEnumerable<TDto>> GetByWellAsync(int idWell,
DateTime dateBegin = default, double intervalSec = 600d,
int approxPointsCount = 1024, CancellationToken token = default)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null)
return Enumerable.Empty<TDto>();
if (dtosList.Count > 1) var timezone = telemetryService.GetTimezone(telemetry.Id);
var filterByDateEnd = dateBegin != default;
DateTimeOffset dateBeginUtc;
if (dateBegin == default)
{
var dateRange = telemetryDataCache.GetOrDefaultWellDataDateRange(telemetry.Id);
dateBeginUtc = (dateRange?.To.ToUniversalTime() ?? DateTimeOffset.UtcNow)
.AddSeconds(-intervalSec);
}
else
{
dateBeginUtc = dateBegin.ToUtcDateTimeOffset(timezone.Hours);
}
var cacheData = telemetryDataCache.GetOrDefault(telemetry.Id, dateBeginUtc.ToRemoteDateTime(timezone.Hours), intervalSec, approxPointsCount);
if (cacheData is not null)
return cacheData;
var dateEnd = dateBeginUtc.AddSeconds(intervalSec);
var dbSet = db.Set<TEntity>();
var query = dbSet
.Where(d => d.IdTelemetry == telemetry.Id
&& d.DateTime >= dateBeginUtc);
if (filterByDateEnd)
query = query.Where(d => d.DateTime <= dateEnd);
var fullDataCount = await query.CountAsync(token)
.ConfigureAwait(false);
if (fullDataCount == 0)
return Enumerable.Empty<TDto>();
if (fullDataCount > 1.75 * approxPointsCount)
{
var m = (int)Math.Round(1d * fullDataCount / approxPointsCount);
if (m > 1)
query = query.Where((d) => (((d.DateTime.DayOfYear * 24 + d.DateTime.Hour) * 60 + d.DateTime.Minute) * 60 + d.DateTime.Second) % m == 0);
}
var entities = await query
.AsNoTracking()
.ToArrayAsync(token);
var dtos = entities.Select(e => Convert(e, timezone.Hours));
return dtos;
}
/// <inheritdoc/>
public virtual async Task<IEnumerable<TDto>> GetByWellAsync(int idWell, TelemetryDataRequest request, CancellationToken token)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null)
return Enumerable.Empty<TDto>();
return await GetByTelemetryAsync(telemetry.Id, request, token);
}
public async Task<IEnumerable<TDto>> GetByTelemetryAsync(int idTelemetry, TelemetryDataRequest request, CancellationToken token)
{
var timeZone = telemetryService.GetTimezone(idTelemetry);
var cache = telemetryDataCache.GetOrDefault(idTelemetry, request);
if (cache is not null)
return cache;
var query = BuildQuery(idTelemetry, request);
var entities = await query
.AsNoTracking()
.ToArrayAsync(token);
var dtos = entities.Select(e => Convert(e, timeZone.Hours));
return dtos;
}
private IQueryable<TEntity> BuildQuery(int idTelemetry, TelemetryDataRequest request)
{
var dbSet = db.Set<TEntity>();
var query = dbSet
.Where(d => d.IdTelemetry == idTelemetry);
if (request.GeDate.HasValue)
{
var geDate = request.GeDate.Value.UtcDateTime;
query = query.Where(d => d.DateTime >= geDate);
}
if (request.LeDate.HasValue)
{
var leDate = request.LeDate.Value.UtcDateTime;
query = query.Where(d => d.DateTime <= leDate);
}
if (request.Divider > 1)
query = query.Where((d) => (((d.DateTime.DayOfYear * 24 + d.DateTime.Hour) * 60 + d.DateTime.Minute) * 60 + d.DateTime.Second) % request.Divider == 0);
switch (request.Order)
{
case 1:// Поздние вперед
query = query
.OrderByDescending(d => d.DateTime)
.Skip(request.Skip)
.Take(request.Take)
.OrderBy(d => d.DateTime);
break;
default:// Ранние вперед
query = query
.OrderBy(d => d.DateTime)
.Skip(request.Skip)
.Take(request.Take);
break;
}
return query;
}
private IQueryable<TEntity> BuildQuery(TelemetryPartDeleteRequest request)
{
var query = db.Set<TEntity>()
.Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.GeDate is not null)
{
var geDate = request.GeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateTime <= geDate);
}
if (request.LeDate is not null)
{
var leDate = request.LeDate.Value.ToUniversalTime();
query = query.Where(o => o.DateTime >= leDate);
}
return query;
}
/// <inheritdoc/>
public async Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
?? throw new ArgumentInvalidException(nameof(idWell), $"По скважине id:{idWell} нет телеметрии");
if ((DateTimeOffset.UtcNow - geDate) < TimeSpan.FromHours(12))
{
// пробуем обойтись кэшем
var cachedRange = telemetryDataCache.GetOrDefaultCachedDataDateRange(telemetry.Id);
if (cachedRange is not null)
{ {
var duplicates = new List<TDto>(8); var datesRange = new DatesRangeDto { From = cachedRange.From, To = cachedRange.To };
for (int i = 1; i < dtosList.Count; i++) if (geDate >= cachedRange.From)
if (dtosList[i].DateTime - dtosList[i - 1].DateTime < TimeSpan.FromMilliseconds(100)) datesRange.From = geDate.ToOffset(cachedRange.From.Offset);
duplicates.Add(dtosList[i - 1]);
foreach (var duplicate in duplicates) if (leDate.HasValue && leDate <= cachedRange.To)
dtosList.Remove(duplicate); datesRange.To = leDate.Value.ToOffset(cachedRange.To.Offset);
return datesRange;
} }
}
var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid); var query = db.Set<TEntity>()
var timezone = telemetryService.GetTimezone(telemetry.Id); .Where(entity => entity.IdTelemetry == telemetry.Id)
.Where(entity => entity.DateTime >= geDate.ToUniversalTime());
telemetryDataCache.AddRange(telemetry.Id, dtos); if (leDate.HasValue)
query = query.Where(entity => entity.DateTime <= leDate.Value.ToUniversalTime());
var entities = dtosList.Select(dto => var groupQuery = query
.GroupBy(entity => entity.IdTelemetry)
.Select(group => new
{ {
var entity = Convert(dto, timezone.Hours); MinDate = group.Min(entity => entity.DateTime),
entity.IdTelemetry = telemetry.Id; MaxDate = group.Max(entity => entity.DateTime),
return entity;
}); });
var stopwatch = Stopwatch.StartNew(); var result = await groupQuery.FirstOrDefaultAsync(token);
var dbSet = db.Set<TEntity>(); if (result is null)
try return null;
{
return await db.Database.ExecInsertOrUpdateAsync(dbSet, entities, token).ConfigureAwait(false);
}
catch (Exception ex)
{
stopwatch.Stop();
Trace.WriteLine($"Fail to save data telemetry " +
$"uid: {uid}, " +
$"idTelemetry {telemetry.Id}, " +
$"count: {entities.Count()}, " +
$"dataDate: {entities.FirstOrDefault()?.DateTime}, " +
$"dbSaveDurationTime:{stopwatch.ElapsedMilliseconds}ms. " +
$"Message: {ex.Message}");
return 0;
}
}
/// <inheritdoc/> var range = new DatesRangeDto
public virtual async Task<IEnumerable<TDto>> GetByWellAsync(int idWell,
DateTime dateBegin = default, double intervalSec = 600d,
int approxPointsCount = 1024, CancellationToken token = default)
{ {
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell); From = result.MinDate.ToOffset(telemetry.TimeZone!.Offset),
if (telemetry is null) To = result.MaxDate.ToOffset(telemetry.TimeZone!.Offset),
return Enumerable.Empty<TDto>(); };
return range;
}
var timezone = telemetryService.GetTimezone(telemetry.Id); public DatesRangeDto? GetRange(int idWell)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null)
return default;
var filterByDateEnd = dateBegin != default; return telemetryDataCache.GetOrDefaultWellDataDateRange(telemetry.Id);
DateTimeOffset dateBeginUtc; }
if (dateBegin == default)
{
var dateRange = telemetryDataCache.GetOrDefaultWellDataDateRange(telemetry.Id);
dateBeginUtc = (dateRange?.To.ToUniversalTime() ?? DateTimeOffset.UtcNow)
.AddSeconds(-intervalSec);
}
else
{
dateBeginUtc = dateBegin.ToUtcDateTimeOffset(timezone.Hours);
}
var cacheData = telemetryDataCache.GetOrDefault(telemetry.Id, dateBeginUtc.ToRemoteDateTime(timezone.Hours), intervalSec, approxPointsCount); protected abstract TDto Convert(TEntity src, double timeZoneOffset);
if (cacheData is not null)
return cacheData;
var dateEnd = dateBeginUtc.AddSeconds(intervalSec); protected abstract TEntity Convert(TDto src, double timeZoneOffset);
var dbSet = db.Set<TEntity>();
var query = dbSet
.Where(d => d.IdTelemetry == telemetry.Id
&& d.DateTime >= dateBeginUtc);
if (filterByDateEnd)
query = query.Where(d => d.DateTime <= dateEnd);
var fullDataCount = await query.CountAsync(token)
.ConfigureAwait(false);
if (fullDataCount == 0)
return Enumerable.Empty<TDto>();
if (fullDataCount > 1.75 * approxPointsCount)
{
var m = (int)Math.Round(1d * fullDataCount / approxPointsCount);
if (m > 1)
query = query.Where((d) => (((d.DateTime.DayOfYear * 24 + d.DateTime.Hour) * 60 + d.DateTime.Minute) * 60 + d.DateTime.Second) % m == 0);
}
var entities = await query
.AsNoTracking()
.ToArrayAsync(token);
var dtos = entities.Select(e => Convert(e, timezone.Hours));
return dtos;
}
/// <inheritdoc/>
public virtual async Task<IEnumerable<TDto>> GetByWellAsync(int idWell, TelemetryDataRequest request, CancellationToken token)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null)
return Enumerable.Empty<TDto>();
return await GetByTelemetryAsync(telemetry.Id, request, token);
}
public async Task<IEnumerable<TDto>> GetByTelemetryAsync(int idTelemetry, TelemetryDataRequest request, CancellationToken token)
{
var timezone = telemetryService.GetTimezone(idTelemetry);
var cache = telemetryDataCache.GetOrDefault(idTelemetry, request);
if(cache is not null)
return cache;
var query = BuildQuery(idTelemetry, request);
var entities = await query
.AsNoTracking()
.ToArrayAsync(token);
var dtos = entities.Select(e => Convert(e, timezone.Hours));
return dtos;
}
private IQueryable<TEntity> BuildQuery(int idTelemetry, TelemetryDataRequest request)
{
var dbSet = db.Set<TEntity>();
var query = dbSet
.Where(d => d.IdTelemetry == idTelemetry);
if (request.GeDate.HasValue)
{
var geDate = request.GeDate.Value.UtcDateTime;
query = query.Where(d => d.DateTime >= geDate);
}
if (request.LeDate.HasValue)
{
var leDate = request.LeDate.Value.UtcDateTime;
query = query.Where(d => d.DateTime <= leDate);
}
if (request.Divider > 1)
query = query.Where((d) => (((d.DateTime.DayOfYear * 24 + d.DateTime.Hour) * 60 + d.DateTime.Minute) * 60 + d.DateTime.Second) % request.Divider == 0);
switch (request.Order)
{
case 1:// Поздние вперед
query = query
.OrderByDescending(d => d.DateTime)
.Skip(request.Skip)
.Take(request.Take)
.OrderBy(d => d.DateTime);
break;
default:// Ранние вперед
query = query
.OrderBy(d => d.DateTime)
.Skip(request.Skip)
.Take(request.Take);
break;
}
return query;
}
/// <inheritdoc/>
public async Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
?? throw new ArgumentInvalidException(nameof(idWell), $"По скважине id:{idWell} нет телеметрии");
if ((DateTimeOffset.UtcNow - geDate) < TimeSpan.FromHours(12))
{
// пробуем обойтись кэшем
var cachedRange = telemetryDataCache.GetOrDefaultCachedDataDateRange(telemetry.Id);
if (cachedRange is not null)
{
var datesRange = new DatesRangeDto {From = cachedRange.From, To = cachedRange.To };
if (geDate >= cachedRange.From)
datesRange.From = geDate.ToOffset(cachedRange.From.Offset);
if (leDate.HasValue && leDate <= cachedRange.To)
datesRange.To = leDate.Value.ToOffset(cachedRange.To.Offset);
return datesRange;
}
}
var query = db.Set<TEntity>()
.Where(entity => entity.IdTelemetry == telemetry.Id)
.Where(entity => entity.DateTime >= geDate.ToUniversalTime());
if(leDate.HasValue)
query = query.Where(entity => entity.DateTime <= leDate.Value.ToUniversalTime());
var groupQuery = query
.GroupBy(entity => entity.IdTelemetry)
.Select(group => new
{
MinDate = group.Min(entity => entity.DateTime),
MaxDate = group.Max(entity => entity.DateTime),
});
var result = await groupQuery.FirstOrDefaultAsync(token);
if (result is null)
return null;
var range = new DatesRangeDto
{
From = result.MinDate.ToOffset(telemetry.TimeZone!.Offset),
To = result.MaxDate.ToOffset(telemetry.TimeZone!.Offset),
};
return range;
}
public DatesRangeDto? GetRange(int idWell)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null)
return default;
return telemetryDataCache.GetOrDefaultWellDataDateRange(telemetry.Id);
}
protected abstract TDto Convert(TEntity src, double timezoneOffset);
protected abstract TEntity Convert(TDto src, double timezoneOffset);
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var query = BuildQuery(request);
db.Set<TEntity>().RemoveRange(query);
return await db.SaveChangesAsync(token);
} }
} }

View File

@ -152,9 +152,9 @@ public class TelemetryDataCache<TDto> : ITelemetryDataCache<TDto> where TDto : A
if (cacheItem.LastData.Count == 0) if (cacheItem.LastData.Count == 0)
return null; return null;
var to = FromDate(cacheItem.FirstByDate.DateTime, cacheItem.TimezoneOffset); var to = FromDate(cacheItem.LastData[^1].DateTime, cacheItem.TimezoneOffset);
var from = FromDate(cacheItem.LastData[^1].DateTime, cacheItem.TimezoneOffset); var from = FromDate(cacheItem.FirstByDate.DateTime, cacheItem.TimezoneOffset);
return new DatesRangeDto { From = from, To = to }; return new DatesRangeDto { From = from, To = to };
} }

View File

@ -0,0 +1,82 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Data.WITS;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB
{
/// <summary>
/// Сервис по работе с данными телеметрии
/// </summary>
public class TelemetryDataEditorService : ITelemetryDataEditorService
{
private readonly ITelemetryDataEditorService[] repositories;
/// <summary>
///
/// </summary>
/// <param name="dataSaubService"></param>
/// <param name="dataSpinService"></param>
/// <param name="dataSaubStatRepository"></param>
/// <param name="messageService"></param>
/// <param name="drillTestRepository"></param>
/// <param name="limitingParameterRepository"></param>
/// <param name="detectedOperationRepository"></param>
/// <param name="witsRecord1Repository"></param>
/// <param name="witsRecord7Repository"></param>
/// <param name="witsRecord8Repository"></param>
/// <param name="witsRecord50Repository"></param>
/// <param name="witsRecord60Repository"></param>
/// <param name="witsRecord61Repository"></param>
/// <param name="gtrRepository"></param>
public TelemetryDataEditorService(
ITelemetryDataSaubService dataSaubService,
ITelemetryDataService<TelemetryDataSpinDto> dataSpinService,
IDataSaubStatRepository dataSaubStatRepository,
IMessageService messageService,
IDrillTestRepository drillTestRepository,
ILimitingParameterRepository limitingParameterRepository,
IDetectedOperationRepository detectedOperationRepository,
IWitsRecordRepository<Record1Dto> witsRecord1Repository,
IWitsRecordRepository<Record7Dto> witsRecord7Repository,
IWitsRecordRepository<Record8Dto> witsRecord8Repository,
IWitsRecordRepository<Record50Dto> witsRecord50Repository,
IWitsRecordRepository<Record60Dto> witsRecord60Repository,
IWitsRecordRepository<Record61Dto> witsRecord61Repository,
IGtrRepository gtrRepository
)
{
repositories =
[
dataSaubService,
dataSpinService,
dataSaubStatRepository,
messageService,
drillTestRepository,
limitingParameterRepository,
detectedOperationRepository,
witsRecord1Repository,
witsRecord7Repository,
witsRecord8Repository,
witsRecord50Repository,
witsRecord60Repository,
witsRecord61Repository,
gtrRepository,
];
}
public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
{
var result = 0;
foreach (var repository in repositories)
{
result += await repository.DeleteAsync(request, token);
}
return result;
}
}
}

View File

@ -13,6 +13,8 @@ using System.Linq;
using System.Text.Csv; using System.Text.Csv;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Requests;
namespace AsbCloudInfrastructure.Services.SAUB; namespace AsbCloudInfrastructure.Services.SAUB;
@ -174,4 +176,4 @@ public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSa
outStream.Seek(0, SeekOrigin.Begin); outStream.Seek(0, SeekOrigin.Begin);
return outStream; return outStream;
} }
} }

View File

@ -90,6 +90,26 @@ namespace AsbCloudInfrastructure.Services
return await db.SaveChangesAsync(token); return await db.SaveChangesAsync(token);
} }
public async Task<int> CopyAsync(int idWell, int idWellTarget, IEnumerable<int> contactIds, CancellationToken token)
{
var contacts = await GetContacts(idWell, contactIds, token);
if (!contacts.Any())
return 0;
var newContacts = contacts.Select(contact =>
{
var newContact = contact.Adapt<Contact>();
newContact.IdWell = idWellTarget;
newContact.Id = default;
return newContact;
});
db.Contacts.AddRange(newContacts);
return await db.SaveChangesAsync(token);
}
private async Task<Contact?> GetContact(int idWell, int idContact, CancellationToken token) private async Task<Contact?> GetContact(int idWell, int idContact, CancellationToken token)
{ {
var contact = await db.Contacts var contact = await db.Contacts
@ -100,5 +120,16 @@ namespace AsbCloudInfrastructure.Services
return contact; return contact;
} }
private async Task<Contact[]> GetContacts(int idWell, IEnumerable<int> contactIds, CancellationToken token)
{
var contacts = await db.Contacts
.Where(c => c.IdWell == idWell)
.Where(c => contactIds.Contains(c.Id))
.AsNoTracking()
.ToArrayAsync(token);
return contacts;
}
} }
} }

View File

@ -1,7 +1,10 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,12 +19,15 @@ namespace AsbCloudWebApi.Controllers
public class AdminTelemetryController : CrudController<TelemetryDto, ICrudRepository<TelemetryDto>> public class AdminTelemetryController : CrudController<TelemetryDto, ICrudRepository<TelemetryDto>>
{ {
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
private readonly ITelemetryDataEditorService telemetryEditorService;
public AdminTelemetryController(ICrudRepository<TelemetryDto> service, public AdminTelemetryController(ICrudRepository<TelemetryDto> service,
ITelemetryService telemetryService) ITelemetryService telemetryService,
ITelemetryDataEditorService telemetryEditorService)
: base(service) : base(service)
{ {
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
this.telemetryEditorService = telemetryEditorService;
} }
/// <summary> /// <summary>
@ -36,5 +42,22 @@ namespace AsbCloudWebApi.Controllers
.ConfigureAwait(false); .ConfigureAwait(false);
return Ok(count); return Ok(count);
} }
/// <summary>
/// Удаление куска телеметрии по запросу
/// </summary>
/// <param name="request">запрос</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpDelete("part")]
[Permission]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> DeleteAsync([FromQuery] TelemetryPartDeleteRequest request,CancellationToken token)
{
var result = await telemetryEditorService.DeleteAsync(request, token);
return Ok(result);
}
} }
} }

View File

@ -111,7 +111,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
} }
/// <summary> /// <summary>
/// Получить список автоопределенных операций для редактирования /// Получить список авто определенных операций для редактирования
/// </summary> /// </summary>
/// <param name="idWell"></param> /// <param name="idWell"></param>
/// <param name="request"></param> /// <param name="request"></param>
@ -136,7 +136,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
} }
/// <summary> /// <summary>
/// Получить статистику по автоопределенным операциям /// Получить статистику по авто определенным операциям
/// </summary> /// </summary>
/// <param name="idWell"></param> /// <param name="idWell"></param>
/// <param name="request"></param> /// <param name="request"></param>
@ -167,13 +167,13 @@ namespace AsbCloudWebApi.Controllers.SAUB
public async Task<IActionResult> ExportAsync(int idWell, CancellationToken token) public async Task<IActionResult> ExportAsync(int idWell, CancellationToken token)
{ {
var idCompany = User.GetCompanyId(); var idCompany = User.GetCompanyId();
if (idCompany is null) if (idCompany is null)
return Forbid(); return Forbid();
var host = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}"; var host = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}";
var stream = await detectedOperationExportService.ExportAsync(idWell, host, token); var stream = await detectedOperationExportService.ExportAsync(idWell, host, token);
return File(stream, "application/octet-stream", "operations.xlsx"); return File(stream, "application/octet-stream", "operations.xlsx");
} }

View File

@ -139,6 +139,34 @@ namespace AsbCloudWebApi.Controllers
return Ok(result); return Ok(result);
} }
/// <summary>
/// Скопировать контакты в другую скважину
/// </summary>
/// <param name="idWell">ключ скважины, откуда копировать контакты</param>
/// <param name="idWellTarget">ключ скважины, куда копировать контакты</param>
/// <param name="contactIds"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("copy/{idWellTarget}")]
[Permission]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> CopyAsync(
int idWell,
int idWellTarget,
[FromBody] IEnumerable<int> contactIds,
CancellationToken token)
{
if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false))
return Forbid();
if (!await CanUserAccessToWellAsync(idWellTarget, token).ConfigureAwait(false))
return Forbid();
var result = await wellContactsRepository.CopyAsync(idWell, idWellTarget, contactIds, token);
return Ok(result);
}
private async Task<bool> CanUserAccessToWellAsync(int idWell, CancellationToken token) private async Task<bool> CanUserAccessToWellAsync(int idWell, CancellationToken token)
{ {
int? idCompany = User.GetCompanyId(); int? idCompany = User.GetCompanyId();