Merge branch 'dev' into fix/export_well_operations

This commit is contained in:
Степанов Дмитрий 2024-02-21 11:24:20 +03:00
commit 406eab8618
28 changed files with 385 additions and 284 deletions

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Data.DetectedOperation; namespace AsbCloudApp.Data.DetectedOperation;
@ -29,6 +30,11 @@ public class DetectedOperationDto: IId
/// </summary> /// </summary>
[Required] [Required]
public int IdUserAtStart { get; set; } public int IdUserAtStart { get; set; }
/// <summary>
/// Пользователь панели оператора
/// </summary>
public string? TelemetryUserName { get; set; }
/// <summary> /// <summary>
/// Дата завершения операции в часовом поясе скважины /// Дата завершения операции в часовом поясе скважины
@ -72,14 +78,14 @@ public class DetectedOperationDto: IId
[Required] [Required]
public WellOperationCategoryDto OperationCategory { get; set; } = null!; public WellOperationCategoryDto OperationCategory { get; set; } = null!;
/// <summary>
/// Пользователь панели оператора
/// </summary>
public string? TelemetryUserName { get; set; }
/// <summary> /// <summary>
/// Ключевой параметр операции /// Ключевой параметр операции
/// </summary> /// </summary>
[Required] [Required]
public double Value { get; set; } public double Value { get; set; }
/// <summary>
/// Доп. инфо по операции
/// </summary>
public IDictionary<string, object> ExtraData { get; set; } = new Dictionary<string, object>();
} }

View File

@ -13,6 +13,11 @@ namespace AsbCloudApp.Data.SAUB
/// </summary> /// </summary>
[Required] [Required]
public DateTime DateTime { get; set; } public DateTime DateTime { get; set; }
/// <summary>
/// Пользователь САУБ
/// </summary>
public int? IdUser { get; set; }
/// <summary> /// <summary>
/// Режим работы САУБ: /// Режим работы САУБ:

View File

@ -1,15 +1,17 @@
using AsbCloudApp.Data.DetectedOperation; using System;
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.Tasks;
using System.Threading; using System.Threading;
using AsbCloudApp.Services;
namespace AsbCloudApp.Repositories; namespace AsbCloudApp.Repositories;
/// <summary> /// <summary>
/// Таблица автоматически определенных операций /// Таблица автоматически определенных операций
/// </summary> /// </summary>
public interface IDetectedOperationRepository public interface IDetectedOperationRepository : ICrudRepository<DetectedOperationDto>
{ {
/// <summary> /// <summary>
/// Добавление записей /// Добавление записей
@ -63,4 +65,11 @@ public interface IDetectedOperationRepository
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token); Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token);
/// <summary>
/// Получение дат последних определённых операций
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
Task<IDictionary<int, DateTimeOffset>> GetLastDetectedDatesAsync(CancellationToken token);
} }

View File

@ -1,4 +1,5 @@
using AsbCloudApp.Data; 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;
@ -52,5 +53,14 @@ namespace AsbCloudApp.Services
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<DetectedOperationStatDto>> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token); Task<IEnumerable<DetectedOperationStatDto>> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token);
/// <summary>
/// Определение операций
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="beginDate"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<DetectedOperationDto>> DetectOperationsAsync(int idTelemetry, DateTimeOffset? beginDate, CancellationToken token);
} }
} }

View File

@ -23,18 +23,27 @@ namespace AsbCloudApp.Services
/// <param name="approxPointsCount">кол-во элементов до которых эти данные прореживаются</param> /// <param name="approxPointsCount">кол-во элементов до которых эти данные прореживаются</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TDto>> GetAsync(int idWell, Task<IEnumerable<TDto>> GetByWellAsync(int idWell,
DateTime dateBegin = default, double intervalSec = 600d, DateTime dateBegin = default, double intervalSec = 600d,
int approxPointsCount = 1024, CancellationToken token = default); int approxPointsCount = 1024, CancellationToken token = default);
/// <summary> /// <summary>
/// Получить данные тех. процесса /// Получить данные тех. процесса по скважине
/// </summary> /// </summary>
/// <param name="idWell"></param> /// <param name="idWell"></param>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TDto>> GetAsync(int idWell, TelemetryDataRequest request, CancellationToken token); Task<IEnumerable<TDto>> GetByWellAsync(int idWell, TelemetryDataRequest request, CancellationToken token);
/// <summary>
/// Получение данных тех. процесса по телеметрии
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TDto>> GetByTelemetryAsync(int idTelemetry, TelemetryDataRequest request, CancellationToken token);
/// <summary> /// <summary>
/// Период за который есть данные по скважине в рамках временного интервала /// Период за который есть данные по скважине в рамках временного интервала

View File

@ -8,7 +8,7 @@ using System.Text.Json.Serialization;
namespace AsbCloudDb.Model namespace AsbCloudDb.Model
{ {
[Table("t_detected_operation"), Comment("автоматически определенные операции по телеметрии")] [Table("t_detected_operation"), Comment("автоматически определенные операции по телеметрии")]
public class DetectedOperation public class DetectedOperation : IId
{ {
[Key] [Key]
[Column("id")] [Column("id")]
@ -20,14 +20,14 @@ namespace AsbCloudDb.Model
[Column("id_category"), Comment("Id категории операции")] [Column("id_category"), Comment("Id категории операции")]
public int IdCategory { get; set; } public int IdCategory { get; set; }
[Column("id_user"), Comment("Id пользователя по телеметрии на момент начала операции")]
public int IdUsersAtStart { get; set; }
[Column("date_start", TypeName = "timestamp with time zone"), Comment("Дата начала операции")] [Column("date_start", TypeName = "timestamp with time zone"), Comment("Дата начала операции")]
public DateTimeOffset DateStart { get; set; } public DateTimeOffset DateStart { get; set; }
[Column("date_end", TypeName = "timestamp with time zone"), Comment("Дата начала операции")] [Column("date_end", TypeName = "timestamp with time zone"), Comment("Дата начала операции")]
public DateTimeOffset DateEnd { get; set; } public DateTimeOffset DateEnd { get; set; }
[Column("id_user"), Comment("Id пользователя по телеметрии на момент начала операции")]
public int IdUsersAtStart { get; set; }
[NotMapped] [NotMapped]
public double DurationMinutes => (DateEnd - DateStart).TotalMinutes; public double DurationMinutes => (DateEnd - DateStart).TotalMinutes;

View File

@ -15,37 +15,46 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository; namespace AsbCloudInfrastructure.Repository;
public class DetectedOperationRepository : IDetectedOperationRepository public class DetectedOperationRepository : CrudRepositoryBase<DetectedOperationDto, DetectedOperation>,
IDetectedOperationRepository
{ {
private readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
public DetectedOperationRepository( public DetectedOperationRepository(IAsbCloudDbContext context,
IAsbCloudDbContext db,
ITelemetryService telemetryService) ITelemetryService telemetryService)
: base(context)
{ {
this.db = db;
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
} }
public async Task<int> Delete(int idUser, DetectedOperationByTelemetryRequest request, CancellationToken token) public async Task<int> Delete(int idUser, DetectedOperationByTelemetryRequest request, CancellationToken token)
{ {
var query = BuildQuery(request); var query = BuildQuery(request);
db.Set<DetectedOperation>().RemoveRange(query); dbContext.Set<DetectedOperation>().RemoveRange(query);
return await db.SaveChangesAsync(token); return await dbContext.SaveChangesAsync(token);
} }
public async Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token) public async Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token)
{ {
var query = db.Set<DetectedOperation>() var query = dbContext.Set<DetectedOperation>()
.Where(e => ids.Contains( e.Id)); .Where(e => ids.Contains( e.Id));
db.Set<DetectedOperation>() dbContext.Set<DetectedOperation>()
.RemoveRange(query); .RemoveRange(query);
return await db.SaveChangesAsync(token); return await dbContext.SaveChangesAsync(token);
} }
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)
@ -63,14 +72,14 @@ public class DetectedOperationRepository : IDetectedOperationRepository
return 0; return 0;
var entities = dtos.Select(Convert); var entities = dtos.Select(Convert);
var dbset = db.Set<DetectedOperation>(); var dbset = dbContext.Set<DetectedOperation>();
foreach(var entity in entities) foreach(var entity in entities)
{ {
entity.Id = default; entity.Id = default;
dbset.Add(entity); dbset.Add(entity);
} }
return await db.SaveChangesWithExceptionHandling(token); return await dbContext.SaveChangesWithExceptionHandling(token);
} }
public async Task<int> Update(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token) public async Task<int> Update(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token)
@ -89,7 +98,7 @@ public class DetectedOperationRepository : IDetectedOperationRepository
if (ids.Length != dtos.Count()) if (ids.Length != dtos.Count())
throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь уникальные Id"); throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь уникальные Id");
var dbSet = db.Set<DetectedOperation>(); var dbSet = dbContext.Set<DetectedOperation>();
var existingEntitiesCount = await dbSet var existingEntitiesCount = await dbSet
.Where(o => ids.Contains(o.Id)) .Where(o => ids.Contains(o.Id))
@ -106,7 +115,7 @@ public class DetectedOperationRepository : IDetectedOperationRepository
for(var i = 0; i < entities.Length; i++) for(var i = 0; i < entities.Length; i++)
entries[i] = dbSet.Update(entities[i]); entries[i] = dbSet.Update(entities[i]);
var result = await db.SaveChangesWithExceptionHandling(token); var result = await dbContext.SaveChangesWithExceptionHandling(token);
for (var i = 0; i < entries.Length; i++) for (var i = 0; i < entries.Length; i++)
entries[i].State = EntityState.Detached; entries[i].State = EntityState.Detached;
@ -131,7 +140,7 @@ public class DetectedOperationRepository : IDetectedOperationRepository
private IQueryable<DetectedOperation> BuildQuery(DetectedOperationByTelemetryRequest request) private IQueryable<DetectedOperation> BuildQuery(DetectedOperationByTelemetryRequest request)
{ {
var query = db.Set<DetectedOperation>() var query = dbContext.Set<DetectedOperation>()
.Where(o => o.IdTelemetry == request.IdTelemetry); .Where(o => o.IdTelemetry == request.IdTelemetry);
if (request.IdsCategories.Any()) if (request.IdsCategories.Any())
@ -173,19 +182,19 @@ public class DetectedOperationRepository : IDetectedOperationRepository
return query; return query;
} }
private static DetectedOperationDto Convert(DetectedOperation entity, TimeSpan offset) protected virtual DetectedOperationDto Convert(DetectedOperation src, TimeSpan offset)
{ {
var dto = entity.Adapt<DetectedOperationDto>(); var dto = src.Adapt<DetectedOperationDto>();
dto.DateStart = entity.DateStart.ToOffset(offset); dto.DateStart = src.DateStart.ToOffset(offset);
dto.DateEnd = entity.DateEnd.ToOffset(offset); dto.DateEnd = src.DateEnd.ToOffset(offset);
return dto; return dto;
} }
private static DetectedOperation Convert(DetectedOperationDto dto) protected override DetectedOperation Convert(DetectedOperationDto src)
{ {
var entity = dto.Adapt<DetectedOperation>(); var entity = src.Adapt<DetectedOperation>();
entity.DateStart = dto.DateStart.ToUniversalTime(); entity.DateStart = src.DateStart.ToUniversalTime();
entity.DateEnd = dto.DateEnd.ToUniversalTime(); entity.DateEnd = src.DateEnd.ToUniversalTime();
return entity; return entity;
} }
} }

View File

@ -60,16 +60,15 @@ public class WellOperationRepository : IWellOperationRepository
OperationType = WellOperation.IdOperationTypePlan, OperationType = WellOperation.IdOperationTypePlan,
}; };
var entities = await BuildQuery(request) var dtos = await BuildQuery(request)
.AsNoTracking() .AsNoTracking()
.ToArrayAsync(token) .ToArrayAsync(token);
.ConfigureAwait(false);
var dateLastAssosiatedPlanOperation = await GetDateLastAssosiatedPlanOperationAsync(idWell, currentDate, timezone.Hours, token); var dateLastAssosiatedPlanOperation = await GetDateLastAssosiatedPlanOperationAsync(idWell, currentDate, timezone.Hours, token);
var result = new WellOperationPlanDto() var result = new WellOperationPlanDto()
{ {
WellOperationsPlan = entities, WellOperationsPlan = dtos.Select(Convert),
DateLastAssosiatedPlanOperation = dateLastAssosiatedPlanOperation DateLastAssosiatedPlanOperation = dateLastAssosiatedPlanOperation
}; };
@ -211,7 +210,7 @@ public class WellOperationRepository : IWellOperationRepository
var dtos = await query.ToArrayAsync(token); var dtos = await query.ToArrayAsync(token);
return dtos; return dtos.Select(Convert);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -219,21 +218,19 @@ public class WellOperationRepository : IWellOperationRepository
WellOperationRequest request, WellOperationRequest request,
CancellationToken token) CancellationToken token)
{ {
var query = BuildQuery(request) var query = BuildQuery(request);
.AsNoTracking();
var result = new PaginationContainer<WellOperationDto> var result = new PaginationContainer<WellOperationDto>
{ {
Skip = request.Skip ?? 0, Skip = request.Skip ?? 0,
Take = request.Take ?? 32, Take = request.Take ?? 32,
Count = await query.CountAsync(token).ConfigureAwait(false), Count = await query.CountAsync(token),
}; };
query = query var dtos = await query.ToArrayAsync(token);
.Skip(result.Skip)
.Take(result.Take); result.Items = dtos.Select(Convert);
result.Items = await query.ToArrayAsync(token);
return result; return result;
} }
@ -384,12 +381,13 @@ public class WellOperationRepository : IWellOperationRepository
/// В результате попрежнему требуется конвертировать дату /// В результате попрежнему требуется конвертировать дату
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
private IQueryable<WellOperationDto> BuildQuery(WellOperationRequest request) private IQueryable<WellOperationDto> BuildQuery(WellOperationRequest request)
{ {
var timezone = wellService.GetTimezone(request.IdWell); var timezone = wellService.GetTimezone(request.IdWell);
var timeZoneOffset = TimeSpan.FromHours(timezone.Hours); var timeZoneOffset = timezone.Hours;
var query = db.WellOperations var query = db.WellOperations
.Include(s => s.WellSectionType) .Include(s => s.WellSectionType)
.Include(s => s.OperationCategory) .Include(s => s.OperationCategory)
@ -413,20 +411,20 @@ public class WellOperationRepository : IWellOperationRepository
if (request.GeDate.HasValue) if (request.GeDate.HasValue)
{ {
var geDateOffset = request.GeDate.Value.ToUtcDateTimeOffset(timezone.Hours); var geDateOffset = request.GeDate.Value.ToUtcDateTimeOffset(timeZoneOffset);
query = query.Where(e => e.DateStart >= geDateOffset); query = query.Where(e => e.DateStart >= geDateOffset);
} }
if (request.LtDate.HasValue) if (request.LtDate.HasValue)
{ {
var ltDateOffset = request.LtDate.Value.ToUtcDateTimeOffset(timezone.Hours); var ltDateOffset = request.LtDate.Value.ToUtcDateTimeOffset(timeZoneOffset);
query = query.Where(e => e.DateStart < ltDateOffset); query = query.Where(e => e.DateStart < ltDateOffset);
} }
var currentWellOperations = db.WellOperations var currentWellOperations = db.WellOperations
.Where(subOp => subOp.IdWell == request.IdWell); .Where(subOp => subOp.IdWell == request.IdWell);
var wellOperationsWithCategoryNPT = currentWellOperations var wellOperationsWithCategoryNpt = currentWellOperations
.Where(subOp => subOp.IdType == 1) .Where(subOp => subOp.IdType == 1)
.Where(subOp => WellOperationCategory.NonProductiveTimeSubIds.Contains(subOp.IdCategory)); .Where(subOp => WellOperationCategory.NonProductiveTimeSubIds.Contains(subOp.IdCategory));
@ -442,14 +440,14 @@ public class WellOperationRepository : IWellOperationRepository
CategoryName = o.OperationCategory.Name, CategoryName = o.OperationCategory.Name,
WellSectionTypeName = o.WellSectionType.Caption, WellSectionTypeName = o.WellSectionType.Caption,
DateStart = DateTime.SpecifyKind(o.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified), DateStart = o.DateStart,
DepthStart = o.DepthStart, DepthStart = o.DepthStart,
DepthEnd = o.DepthEnd, DepthEnd = o.DepthEnd,
DurationHours = o.DurationHours, DurationHours = o.DurationHours,
CategoryInfo = o.CategoryInfo, CategoryInfo = o.CategoryInfo,
Comment = o.Comment, Comment = o.Comment,
NptHours = wellOperationsWithCategoryNPT NptHours = wellOperationsWithCategoryNpt
.Where(subOp => subOp.DateStart <= o.DateStart) .Where(subOp => subOp.DateStart <= o.DateStart)
.Select(subOp => subOp.DurationHours) .Select(subOp => subOp.DurationHours)
.Sum(), .Sum(),
@ -460,22 +458,39 @@ public class WellOperationRepository : IWellOperationRepository
.Min(subOp => subOp.DateStart)) .Min(subOp => subOp.DateStart))
.TotalDays, .TotalDays,
IdUser = o.IdUser, IdUser = o.IdUser,
LastUpdateDate = DateTime.SpecifyKind(o.LastUpdateDate.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified) LastUpdateDate = o.LastUpdateDate,
}); });
if (request.SortFields?.Any() == true) if (request.SortFields?.Any() == true)
{ {
dtos = dtos.SortBy(request.SortFields); dtos = dtos.SortBy(request.SortFields);
} }
else
{
dtos = dtos
.OrderBy(e => e.DateStart)
.ThenBy(e => e.DepthEnd)
.ThenBy(e => e.Id);
}
return dtos; dtos = dtos
.OrderBy(e => e.DateStart)
.ThenBy(e => e.DepthEnd)
.ThenBy(e => e.Id);
if (request.Skip.HasValue)
dtos = dtos.Skip(request.Skip.Value);
if (request.Take.HasValue)
dtos = dtos.Take(request.Take.Value);
return dtos.AsNoTracking();
}
private WellOperationDto Convert(WellOperationDto dto)
{
var timezone = wellService.GetTimezone(dto.IdWell);
var timezoneOffset = TimeSpan.FromHours(timezone.Hours);
var dtoWithRemoteDateTime = dto.Adapt<WellOperationDto>();
dtoWithRemoteDateTime.DateStart = dto.DateStart.ToOffset(TimeSpan.FromHours(timezoneOffset.Hours));
dtoWithRemoteDateTime.LastUpdateDate = dto.LastUpdateDate?.ToOffset(TimeSpan.FromHours(timezoneOffset.Hours));
return dtoWithRemoteDateTime;
} }
public async Task<int> RemoveDuplicates(Action<string, double?> onProgressCallback, CancellationToken token) public async Task<int> RemoveDuplicates(Action<string, double?> onProgressCallback, CancellationToken token)

View File

@ -5,8 +5,8 @@ namespace AsbCloudInfrastructure.Services.DetectOperations;
public class DetectableTelemetry public class DetectableTelemetry
{ {
public DateTimeOffset DateTime { get; set; } public DateTimeOffset DateTime { get; set; }
public int? IdUser { get; set; }
public int Mode { get; set; } public int Mode { get; set; }
public int? IdUser { get; set; }
public float WellDepth { get; set; } public float WellDepth { get; set; }
public float Pressure { get; set; } public float Pressure { get; set; }
public float HookWeight { get; set; } public float HookWeight { get; set; }

View File

@ -19,9 +19,9 @@ namespace AsbCloudInfrastructure.Services.DetectOperations;
public class DetectedOperationExportService public class DetectedOperationExportService
{ {
private readonly IAsbCloudDbContext db; private readonly IWellService wellService;
private readonly IWellService wellService;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
private readonly IDetectedOperationService detectedOperationService;
private const int headerRowsCount = 1; private const int headerRowsCount = 1;
private const string cellDepositName = "B1"; private const string cellDepositName = "B1";
@ -40,15 +40,14 @@ public class DetectedOperationExportService
private const int columnIdReasonOfEnd = 9; private const int columnIdReasonOfEnd = 9;
private const int columnComment = 10; private const int columnComment = 10;
public DetectedOperationExportService( public DetectedOperationExportService(IWellService wellService,
IAsbCloudDbContext db, IWellOperationCategoryRepository wellOperationCategoryRepository,
IWellService wellService, IDetectedOperationService detectedOperationService)
IWellOperationCategoryRepository wellOperationCategoryRepository)
{ {
this.db = db; this.wellService = wellService;
this.wellService = wellService;
this.wellOperationCategoryRepository = wellOperationCategoryRepository; this.wellOperationCategoryRepository = wellOperationCategoryRepository;
} this.detectedOperationService = detectedOperationService;
}
/// <summary> /// <summary>
/// Экспорт excel файла с операциями по скважине /// Экспорт excel файла с операциями по скважине
@ -68,12 +67,12 @@ public class DetectedOperationExportService
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 WorkOperationDetection.DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, db, token); var operations = await detectedOperationService.DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, token);
return await GenerateExcelFileStreamAsync(well, host, operations, token); return await GenerateExcelFileStreamAsync(well, host, operations, token);
} }
private async Task<Stream> GenerateExcelFileStreamAsync(WellDto well, string host, IEnumerable<DetectedOperation> operationDetectorResults, private async Task<Stream> GenerateExcelFileStreamAsync(WellDto well, string host, IEnumerable<DetectedOperationDto> operationDetectorResults,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken); using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
@ -88,7 +87,7 @@ public class DetectedOperationExportService
return memoryStream; return memoryStream;
} }
private void AddToWorkbook(XLWorkbook workbook, WellDto well, string host, IEnumerable<DetectedOperation> operations) private void AddToWorkbook(XLWorkbook workbook, WellDto well, string host, IEnumerable<DetectedOperationDto> operations)
{ {
const string sheetName = "Операции"; const string sheetName = "Операции";
@ -104,14 +103,17 @@ public class DetectedOperationExportService
AddToSheet(sheet, well, host, orderedOperations); AddToSheet(sheet, well, host, orderedOperations);
} }
private void AddToSheet(IXLWorksheet sheet, WellDto well, string host, IList<DetectedOperation> operations) private void AddToSheet(IXLWorksheet sheet, WellDto well, string host, IList<DetectedOperationDto> operations)
{ {
var wellOperationCategories = wellOperationCategoryRepository.Get(true); var wellOperationCategories = wellOperationCategoryRepository.Get(true);
sheet.Cell(cellDepositName).SetCellValue(well.Deposit); sheet.Cell(cellDepositName).SetCellValue(well.Deposit);
sheet.Cell(cellClusterName).SetCellValue(well.Cluster); sheet.Cell(cellClusterName).SetCellValue(well.Cluster);
sheet.Cell(cellWellName).SetCellValue(well.Caption); sheet.Cell(cellWellName).SetCellValue(well.Caption);
sheet.Cell(cellDeltaDate).SetCellValue((TimeSpan)(Enumerable.Max<DetectedOperation, DateTimeOffset>(operations, (Func<DetectedOperation, DateTimeOffset>)(o => o.DateEnd)) - Enumerable.Min<DetectedOperation, DateTimeOffset>(operations, (Func<DetectedOperation, DateTimeOffset>)(o => o.DateStart))));
var deltaDate = operations.Max(o => o.DateEnd - o.DateStart);
sheet.Cell(cellDeltaDate).SetCellValue(deltaDate);
for (int i = 0; i < operations.Count; i++) for (int i = 0; i < operations.Count; i++)
{ {
@ -157,7 +159,7 @@ public class DetectedOperationExportService
} }
} }
private static string GetCategoryName(IEnumerable<WellOperationCategoryDto> wellOperationCategories, DetectedOperation current) private static string GetCategoryName(IEnumerable<WellOperationCategoryDto> wellOperationCategories, DetectedOperationDto current)
{ {
var idCategory = current.IdCategory; var idCategory = current.IdCategory;
if (idCategory == WellOperationCategory.IdSlide && if (idCategory == WellOperationCategory.IdSlide &&
@ -198,7 +200,7 @@ public class DetectedOperationExportService
return memoryStream; return memoryStream;
} }
private static string CreateComment(DetectedOperation operation) private static string CreateComment(DetectedOperationDto operation)
{ {
switch (operation.IdCategory) switch (operation.IdCategory)
{ {

View File

@ -1,16 +1,16 @@
using AsbCloudApp.Data; using System;
using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation; using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
namespace AsbCloudInfrastructure.Services.DetectOperations; namespace AsbCloudInfrastructure.Services.DetectOperations;
@ -19,21 +19,29 @@ public class DetectedOperationService : IDetectedOperationService
private readonly IDetectedOperationRepository operationRepository; private readonly IDetectedOperationRepository operationRepository;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly IRepositoryWellRelated<OperationValueDto> operationValueService; private readonly IRepositoryWellRelated<OperationValueDto> operationValueRepository;
private readonly IScheduleRepository scheduleService; private readonly IScheduleRepository scheduleRepository;
private readonly ITelemetryDataSaubService telemetryDataSaubService;
private static readonly DetectorAbstract[] detectors = {
new DetectorDrilling(),
new DetectorSlipsTime()
};
public DetectedOperationService( public DetectedOperationService(
IDetectedOperationRepository operationRepository, IDetectedOperationRepository operationRepository,
IWellOperationCategoryRepository wellOperationCategoryRepository, IWellOperationCategoryRepository wellOperationCategoryRepository,
IWellService wellService, IWellService wellService,
IRepositoryWellRelated<OperationValueDto> operationValueRepository, IRepositoryWellRelated<OperationValueDto> operationValueRepository,
IScheduleRepository scheduleRepository) IScheduleRepository scheduleRepository,
ITelemetryDataSaubService telemetryDataSaubService)
{ {
this.operationRepository = operationRepository; this.operationRepository = operationRepository;
this.wellOperationCategoryRepository = wellOperationCategoryRepository; this.wellOperationCategoryRepository = wellOperationCategoryRepository;
this.wellService = wellService; this.wellService = wellService;
this.operationValueService = operationValueRepository; this.operationValueRepository = operationValueRepository;
this.scheduleService = scheduleRepository; this.scheduleRepository = scheduleRepository;
this.telemetryDataSaubService = telemetryDataSaubService;
} }
public async Task<DetectedOperationListDto> GetAsync(DetectedOperationByWellRequest request, CancellationToken token) public async Task<DetectedOperationListDto> GetAsync(DetectedOperationByWellRequest request, CancellationToken token)
@ -60,8 +68,8 @@ public class DetectedOperationService : IDetectedOperationService
var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request); var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request);
var data = await operationRepository.Get(requestByTelemetry, token); var data = await operationRepository.Get(requestByTelemetry, token);
var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token); var operationValues = await operationValueRepository.GetByIdWellAsync(request.IdWell, token);
var schedules = await scheduleService.GetByIdWellAsync(request.IdWell, token); var schedules = await scheduleRepository.GetByIdWellAsync(request.IdWell, token);
var dtos = data.Select(o => Convert(o, operationValues, schedules)); var dtos = data.Select(o => Convert(o, operationValues, schedules));
return dtos; return dtos;
} }
@ -103,9 +111,7 @@ public class DetectedOperationService : IDetectedOperationService
if (!operations.Any()) if (!operations.Any())
return Enumerable.Empty<DetectedOperationStatDto>(); return Enumerable.Empty<DetectedOperationStatDto>();
var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
var dtos = operations var dtos = operations
.GroupBy(o => (o.IdCategory, o.OperationCategory.Name)) .GroupBy(o => (o.IdCategory, o.OperationCategory.Name))
.OrderBy(g => g.Key) .OrderBy(g => g.Key)
@ -126,6 +132,69 @@ public class DetectedOperationService : IDetectedOperationService
return dtos; return dtos;
} }
public async Task<IEnumerable<DetectedOperationDto>> DetectOperationsAsync(int idTelemetry, DateTimeOffset? beginDate, CancellationToken token)
{
const int take = 4 * 86_400;
var detectedOperations = new List<DetectedOperationDto>();
DetectedOperationDto? lastDetectedOperation = null;
const int minOperationLength = 5;
const int maxDetectorsInterpolationFrameLength = 30;
const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;
while (true)
{
var request = new TelemetryDataRequest
{
GeDate = beginDate,
Take = take,
Order = 0
};
var detectableTelemetries = (await telemetryDataSaubService.GetByTelemetryAsync(idTelemetry, request, token))
.Where(t => t.BlockPosition >= 0)
.Select(t => new DetectableTelemetry
{
DateTime = t.DateTime,
IdUser = t.IdUser,
Mode = t.Mode,
WellDepth = t.WellDepth,
Pressure = t.Pressure,
HookWeight = t.HookWeight,
BlockPosition = t.BlockPosition,
BitDepth = t.BitDepth,
RotorSpeed = t.RotorSpeed,
}).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;
}
public async Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token) public async Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token)
{ {
var well = await wellService.GetOrDefaultAsync(request.IdWell, token); var well = await wellService.GetOrDefaultAsync(request.IdWell, token);

View File

@ -35,7 +35,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
protected const int IdReasonOfEnd_Custom1 = 10_000; protected const int IdReasonOfEnd_Custom1 = 10_000;
public bool TryDetect(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end, DetectedOperation? previousOperation, public bool TryDetect(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end, DetectedOperationDto? previousOperation,
out OperationDetectorResult? result) out OperationDetectorResult? result)
{ {
// Проверка соответствия критерию начала операции // Проверка соответствия критерию начала операции
@ -82,9 +82,9 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
protected virtual bool IsValidOperationDetectorResult(OperationDetectorResult operationDetectorResult) protected virtual bool IsValidOperationDetectorResult(OperationDetectorResult operationDetectorResult)
=> operationDetectorResult.Operation.DateEnd - operationDetectorResult.Operation.DateStart > TimeSpan.FromSeconds(3); => operationDetectorResult.Operation.DateEnd - operationDetectorResult.Operation.DateStart > TimeSpan.FromSeconds(3);
protected abstract bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation); protected abstract bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation);
protected virtual int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation) protected virtual int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation)
=> DetectBegin(telemetry, position, previousOperation) => DetectBegin(telemetry, position, previousOperation)
? IdReasonOfEnd_NotDetected ? IdReasonOfEnd_NotDetected
: IdReasonOfEnd_NotDetectBegin; : IdReasonOfEnd_NotDetectBegin;
@ -110,16 +110,16 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
return result; return result;
} }
private DetectedOperation MakeDetectedOperation(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end) private DetectedOperationDto MakeDetectedOperation(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end)
{ {
var pBegin = telemetry[begin]; var pBegin = telemetry[begin];
var pEnd = telemetry[end]; var pEnd = telemetry[end];
var (IdCategory, ExtraData) = GetSpecificInformation(telemetry, begin, end); var (IdCategory, ExtraData) = GetSpecificInformation(telemetry, begin, end);
var operation = new DetectedOperation var operation = new DetectedOperationDto
{ {
IdCategory = IdCategory, IdCategory = IdCategory,
IdTelemetry = idTelemetry, IdTelemetry = idTelemetry,
IdUsersAtStart = pBegin.IdUser ?? -1, IdUserAtStart = pBegin.IdUser ?? -1,
DateStart = pBegin.DateTime, DateStart = pBegin.DateTime,
DateEnd = pEnd.DateTime, DateEnd = pEnd.DateTime,
DepthStart = (double)pBegin.WellDepth, DepthStart = (double)pBegin.WellDepth,

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudDb.Model; using AsbCloudDb.Model;
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors; namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors;
@ -12,7 +13,7 @@ public class DetectorDrilling : DetectorAbstract
public const string ExtraDataKeyDispersionOfNormalizedRotorSpeed = "dispersionOfNormalizedRotorSpeed"; public const string ExtraDataKeyDispersionOfNormalizedRotorSpeed = "dispersionOfNormalizedRotorSpeed";
public const string ExtraDataKeyAvgRotorSpeed = "avgRotorSpeed"; public const string ExtraDataKeyAvgRotorSpeed = "avgRotorSpeed";
protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation) protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation)
{ {
var point0 = telemetry[position]; var point0 = telemetry[position];
var delta = point0.WellDepth - point0.BitDepth; var delta = point0.WellDepth - point0.BitDepth;
@ -25,7 +26,7 @@ public class DetectorDrilling : DetectorAbstract
return true; return true;
} }
protected override int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation) protected override int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation)
{ {
var point0 = telemetry[position]; var point0 = telemetry[position];
var delta = point0.WellDepth - point0.BitDepth; var delta = point0.WellDepth - point0.BitDepth;

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudDb.Model; using AsbCloudDb.Model;
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors; namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors;
@ -9,7 +10,7 @@ public class DetectorSlipsTime : DetectorAbstract
protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end) protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end)
=> CalcDeltaMinutes(telemetry, begin, end); => CalcDeltaMinutes(telemetry, begin, end);
protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation) protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperationDto? previousOperation)
{ {
var point0 = telemetry[position]; var point0 = telemetry[position];
var delta = point0.WellDepth - point0.BitDepth; var delta = point0.WellDepth - point0.BitDepth;

View File

@ -1,4 +1,4 @@
using AsbCloudDb.Model; using AsbCloudApp.Data.DetectedOperation;
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
{ {
@ -7,6 +7,6 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
{ {
public int TelemetryBegin { get; set; } public int TelemetryBegin { get; set; }
public int TelemetryEnd { get; set; } public int TelemetryEnd { get; set; }
public DetectedOperation Operation { get; set; } = null!; public DetectedOperationDto Operation { get; set; } = null!;
} }
} }

View File

@ -1,12 +1,11 @@
using AsbCloudDb.Model; using System;
using Microsoft.EntityFrameworkCore;
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 AsbCloudInfrastructure.Services.DetectOperations.Detectors; using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudInfrastructure.Background; using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -14,19 +13,6 @@ namespace AsbCloudInfrastructure.Services.DetectOperations;
public class WorkOperationDetection: Work public class WorkOperationDetection: Work
{ {
private static readonly DetectorAbstract[] detectors = new DetectorAbstract[]
{
new DetectorDrilling(),
new DetectorSlipsTime()
// new DetectorRotor(),
// new DetectorSlide(),
//new DetectorDevelopment(),
//new DetectorTemplating(),
//new DetectorStaticSurveying(),
//new DetectorFlashingBeforeConnection(),
//new DetectorFlashing(),
//new DetectorTemplatingWhileDrilling(),
};
public WorkOperationDetection() public WorkOperationDetection()
:base("Operation detection") :base("Operation detection")
@ -42,115 +28,27 @@ public class WorkOperationDetection: Work
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token) protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
{ {
using var db = services.GetRequiredService<IAsbCloudDbContext>(); var telemetryRepository = services.GetRequiredService<ICrudRepository<TelemetryDto>>();
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); var detectedOperationRepository = services.GetRequiredService<IDetectedOperationRepository>();
var lastDetectedDates = await db.DetectedOperations var detectedOperationService = services.GetRequiredService<IDetectedOperationService>();
.GroupBy(o => o.IdTelemetry)
.Select(g => new
{
IdTelemetry = g.Key,
LastDate = g.Max(o => o.DateEnd)
})
.ToListAsync(token);
var telemetryIds = await db.Telemetries var telemetryIds = (await telemetryRepository.GetAllAsync(token))
.Where(t => t.Info != null && t.TimeZone != null)
.Select(t => t.Id) .Select(t => t.Id)
.ToListAsync(token); .ToArray();
var joinedlastDetectedDates = telemetryIds var lastDetectedDates = await detectedOperationRepository.GetLastDetectedDatesAsync(token);
.GroupJoin(lastDetectedDates,
t => t,
o => o.IdTelemetry,
(outer, inner) => new
{
IdTelemetry = outer,
inner.SingleOrDefault()?.LastDate,
});
var affected = 0; for (var i = 0; i < telemetryIds.Length; i++)
var count = joinedlastDetectedDates.Count();
var i = 0d;
foreach (var item in joinedlastDetectedDates)
{ {
var stopwatch = Stopwatch.StartNew(); var telemetryId = telemetryIds[i];
var startDate = item.LastDate ?? DateTimeOffset.MinValue;
onProgressCallback($"start detecting telemetry: {item.IdTelemetry} from {startDate}", i++ / count); var beginDate = lastDetectedDates.TryGetValue(telemetryId, out var date) ? date : (DateTimeOffset?)null;
var newOperations = await DetectOperationsAsync(item.IdTelemetry, startDate, db, token);
stopwatch.Stop(); onProgressCallback($"Start detecting telemetry: {telemetryId} from {beginDate}", i++ / telemetryIds.Length);
if (newOperations.Any()) var detectedOperations = await detectedOperationService.DetectOperationsAsync(telemetryId, beginDate, token);
{
db.DetectedOperations.AddRange(newOperations); if (detectedOperations.Any())
affected += await db.SaveChangesAsync(token); await detectedOperationRepository.InsertRangeAsync(detectedOperations, token);
}
} }
} }
//todo: move this logic to DetectedOperationsService
internal static async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
{
var query = db.TelemetryDataSaub
.AsNoTracking()
.Where(d => d.IdTelemetry == idTelemetry)
.Where(d => d.BlockPosition >= 0)
.Select(d => new DetectableTelemetry
{
DateTime = d.DateTime,
IdUser = d.IdUser,
Mode = d.Mode,
WellDepth = d.WellDepth,
Pressure = d.Pressure,
HookWeight = d.HookWeight,
BlockPosition = d.BlockPosition,
BitDepth = d.BitDepth,
RotorSpeed = d.RotorSpeed,
})
.OrderBy(d => d.DateTime);
var take = 4 * 86_400; // 4 дня
var startDate = begin;
var detectedOperations = new List<DetectedOperation>(8);
DetectedOperation? lastDetectedOperation = null;
const int minOperationLength = 5;
const int maxDetectorsInterpolationFrameLength = 30;
const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;
while (true)
{
var data = await query
.Where(d => d.DateTime > startDate)
.Take(take)
.ToArrayAsync(token);
if (data.Length < gap)
break;
var isDetected = false;
var positionBegin = 0;
var positionEnd = data.Length - gap;
while (positionEnd > positionBegin)
{
foreach (var detector in detectors)
{
if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result))
continue;
detectedOperations.Add(result!.Operation);
lastDetectedOperation = result.Operation;
isDetected = true;
positionBegin = result.TelemetryEnd;
break;
}
positionBegin += 1;
}
if (isDetected)
startDate = lastDetectedOperation!.DateEnd;
else
startDate = data[positionEnd].DateTime;
}
return detectedOperations;
}
} }

View File

@ -11,6 +11,7 @@ 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;
namespace AsbCloudInfrastructure.Services.SAUB namespace AsbCloudInfrastructure.Services.SAUB
{ {
@ -22,7 +23,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
protected readonly ITelemetryService telemetryService; protected readonly ITelemetryService telemetryService;
protected readonly ITelemetryDataCache<TDto> telemetryDataCache; protected readonly ITelemetryDataCache<TDto> telemetryDataCache;
public TelemetryDataBaseService( protected TelemetryDataBaseService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
ITelemetryService telemetryService, ITelemetryService telemetryService,
ITelemetryDataCache<TDto> telemetryDataCache) ITelemetryDataCache<TDto> telemetryDataCache)
@ -86,7 +87,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual async Task<IEnumerable<TDto>> GetAsync(int idWell, public virtual async Task<IEnumerable<TDto>> GetByWellAsync(int idWell,
DateTime dateBegin = default, double intervalSec = 600d, DateTime dateBegin = default, double intervalSec = 600d,
int approxPointsCount = 1024, CancellationToken token = default) int approxPointsCount = 1024, CancellationToken token = default)
{ {
@ -146,23 +147,41 @@ namespace AsbCloudInfrastructure.Services.SAUB
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual async Task<IEnumerable<TDto>> GetAsync(int idWell, AsbCloudApp.Requests.TelemetryDataRequest request, CancellationToken token) public virtual async Task<IEnumerable<TDto>> GetByWellAsync(int idWell, TelemetryDataRequest request, CancellationToken token)
{ {
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell); var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null) if (telemetry is null)
return Enumerable.Empty<TDto>(); return Enumerable.Empty<TDto>();
var timezone = telemetryService.GetTimezone(telemetry.Id); return await GetByTelemetryAsync(telemetry.Id, request, token);
}
var cache = telemetryDataCache.GetOrDefault(telemetry.Id, request); 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) if(cache is not null)
return cache; 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 dbSet = db.Set<TEntity>();
var query = dbSet var query = dbSet
.Where(d => d.IdTelemetry == telemetry.Id) .Where(d => d.IdTelemetry == idTelemetry);
.AsNoTracking();
if (request.GeDate.HasValue) if (request.GeDate.HasValue)
{ {
@ -196,12 +215,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
break; break;
} }
var entities = await query return query;
.ToArrayAsync(token);
var dtos = entities.Select(e => Convert(e, timezone.Hours));
return dtos;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -263,9 +277,9 @@ namespace AsbCloudInfrastructure.Services.SAUB
return telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id); return telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id);
} }
public abstract TDto Convert(TEntity src, double timezoneOffset); protected abstract TDto Convert(TEntity src, double timezoneOffset);
public abstract TEntity Convert(TDto src, double timezoneOffset); protected abstract TEntity Convert(TDto src, double timezoneOffset);
} }
} }

View File

@ -111,7 +111,7 @@ public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSa
return await query.ToArrayAsync(token); return await query.ToArrayAsync(token);
} }
public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset) protected override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
{ {
var entity = src.Adapt<TelemetryDataSaub>(); var entity = src.Adapt<TelemetryDataSaub>();
var telemetryUser = telemetryUserService var telemetryUser = telemetryUserService
@ -122,7 +122,7 @@ public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSa
return entity; return entity;
} }
public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset) protected override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset)
{ {
var dto = src.Adapt<TelemetryDataSaubDto>(); var dto = src.Adapt<TelemetryDataSaubDto>();
var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser ?? int.MinValue); var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser ?? int.MinValue);
@ -151,7 +151,7 @@ public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSa
_ => 32_768 _ => 32_768
}; };
var data = await GetAsync(idWell, beginDate, intervalSec, approxPointsCount, token); var data = await GetByWellAsync(idWell, beginDate, intervalSec, approxPointsCount, token);
var fileName = $"DataSaub idWell{idWell}"; var fileName = $"DataSaub idWell{idWell}";
if (telemetry.Info is not null) if (telemetry.Info is not null)

View File

@ -16,14 +16,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
: base(db, telemetryService, telemetryDataCache) : base(db, telemetryService, telemetryDataCache)
{ } { }
public override TelemetryDataSpin Convert(TelemetryDataSpinDto src, double timezoneOffset) protected override TelemetryDataSpin Convert(TelemetryDataSpinDto src, double timezoneOffset)
{ {
var entity = src.Adapt<TelemetryDataSpin>(); var entity = src.Adapt<TelemetryDataSpin>();
entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset); entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset);
return entity; return entity;
} }
public override TelemetryDataSpinDto Convert(TelemetryDataSpin src, double timezoneOffset) protected override TelemetryDataSpinDto Convert(TelemetryDataSpin src, double timezoneOffset)
{ {
var dto = src.Adapt<TelemetryDataSpinDto>(); var dto = src.Adapt<TelemetryDataSpinDto>();
dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset); dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset);

View File

@ -5,20 +5,24 @@ using System.Linq;
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Data.WellOperationImport; using AsbCloudApp.Data.WellOperationImport;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudApp.Services.WellOperationImport; using AsbCloudApp.Services.WellOperationImport;
namespace AsbCloudInfrastructure.Services.WellOperationImport; namespace AsbCloudInfrastructure.Services.WellOperationImport;
public class WellOperationImportService : IWellOperationImportService public class WellOperationImportService : IWellOperationImportService
{ {
private readonly IWellService wellService;
private readonly IWellOperationRepository wellOperationRepository; private readonly IWellOperationRepository wellOperationRepository;
private static readonly DateTime dateLimitMin = new(2001, 1, 1, 0, 0, 0); private static readonly DateTime dateLimitMin = new(2001, 1, 1, 0, 0, 0);
private static readonly DateTime dateLimitMax = new(2099, 1, 1, 0, 0, 0); private static readonly DateTime dateLimitMax = new(2099, 1, 1, 0, 0, 0);
private static readonly TimeSpan drillingDurationLimitMax = TimeSpan.FromDays(366); private static readonly TimeSpan drillingDurationLimitMax = TimeSpan.FromDays(366);
public WellOperationImportService(IWellOperationRepository wellOperationRepository) public WellOperationImportService(IWellService wellService,
IWellOperationRepository wellOperationRepository)
{ {
this.wellService = wellService;
this.wellOperationRepository = wellOperationRepository; this.wellOperationRepository = wellOperationRepository;
} }
@ -30,8 +34,12 @@ public class WellOperationImportService : IWellOperationImportService
var categories = wellOperationRepository.GetCategories(false); var categories = wellOperationRepository.GetCategories(false);
var wellOperations = new List<WellOperationDto>(); var wellOperations = new List<WellOperationDto>();
foreach (var row in sheet.Rows) var rows = sheet.Rows.OrderBy(r => r.Date);
var prevRow = new RowDto();
foreach (var row in rows)
{ {
try try
{ {
@ -58,15 +66,18 @@ public class WellOperationImportService : IWellOperationImportService
if (row.Date < dateLimitMin && row.Date > dateLimitMax) if (row.Date < dateLimitMin && row.Date > dateLimitMax)
throw new FileFormatException( throw new FileFormatException(
$"Лист '{sheet.Name}'. Строка '{row.Number}' неправильно получена дата начала операции"); $"Лист '{sheet.Name}'. Строка '{row.Number}' неправильно получена дата начала операции");
if (wellOperations.LastOrDefault()?.DateStart > row.Date) if (prevRow.Date > row.Date)
throw new FileFormatException( throw new FileFormatException(
$"Лист '{sheet.Name}' строка '{row.Number}' дата позднее даты предыдущей операции"); $"Лист '{sheet.Name}' строка '{row.Number}' дата позднее даты предыдущей операции");
if (row.Duration is not (>= 0d and <= 240d)) if (row.Duration is not (>= 0d and <= 240d))
throw new FileFormatException($"Лист '{sheet.Name}'. Строка '{row.Number}' некорректная длительность операции"); throw new FileFormatException($"Лист '{sheet.Name}'. Строка '{row.Number}' некорректная длительность операции");
wellOperations.Add(new WellOperationDto var timezone = wellService.GetTimezone(idWell);
var timezoneOffset = TimeSpan.FromHours(timezone.Hours);
var wellOperation = new WellOperationDto
{ {
IdWell = idWell, IdWell = idWell,
IdUser = idUser, IdUser = idUser,
@ -76,10 +87,14 @@ public class WellOperationImportService : IWellOperationImportService
CategoryInfo = row.CategoryInfo, CategoryInfo = row.CategoryInfo,
DepthStart = row.DepthStart, DepthStart = row.DepthStart,
DepthEnd = row.DepthEnd, DepthEnd = row.DepthEnd,
DateStart = row.Date, DateStart = new DateTimeOffset(row.Date, timezoneOffset),
DurationHours = row.Duration, DurationHours = row.Duration,
Comment = row.Comment Comment = row.Comment
}); };
wellOperations.Add(wellOperation);
prevRow = row;
} }
catch (FileFormatException ex) catch (FileFormatException ex)
{ {

View File

@ -18,6 +18,14 @@
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="WellOperationsPlan.xlsx" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="WellOperationsPlan.xlsx" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AsbCloudWebApi\AsbCloudWebApi.csproj" /> <ProjectReference Include="..\AsbCloudWebApi\AsbCloudWebApi.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -6,16 +6,22 @@ namespace AsbCloudWebApi.IntegrationTests.Clients;
public interface IWellOperationClient public interface IWellOperationClient
{ {
private const string BaseRoute = "/api/well/{idWell}/wellOperations"; private const string BaseRoute = "/api/well/{idWell}/wellOperations";
[Post(BaseRoute + "/{idType}/{deleteBeforeInsert}")] [Post(BaseRoute + "/{idType}/{deleteBeforeInsert}")]
Task<IApiResponse<int>> InsertRangeAsync(int idWell, int idType, bool deleteBeforeInsert, [Body] IEnumerable<WellOperationDto> dtos); Task<IApiResponse<int>> InsertRangeAsync(int idWell, int idType, bool deleteBeforeInsert, [Body] IEnumerable<WellOperationDto> dtos);
[Put(BaseRoute + "/{idOperation}")] [Put(BaseRoute + "/{idOperation}")]
Task<IApiResponse<int>> UpdateAsync(int idWell, int idOperation, [Body] WellOperationDto value, CancellationToken token); Task<IApiResponse<int>> UpdateAsync(int idWell, int idOperation, [Body] WellOperationDto value, CancellationToken token);
[Get(BaseRoute + "/plan")] [Get(BaseRoute + "/plan")]
Task<IApiResponse<PaginationContainer<WellOperationDto>>> GetPageOperationsPlanAsync(int idWell, Task<IApiResponse<PaginationContainer<WellOperationDto>>> GetPageOperationsPlanAsync(int idWell,
[Query] WellOperationRequestBase request, [Query] WellOperationRequestBase request,
CancellationToken token); CancellationToken token);
[Multipart]
[Post(BaseRoute + "/import/plan/default")]
Task<IApiResponse<IEnumerable<WellOperationDto>>> ImportPlanDefaultExcelFileAsync(int idWell,
[AliasAs("files")] IEnumerable<StreamPart> streams,
CancellationToken token);
} }

View File

@ -2,7 +2,10 @@ using AsbCloudApp.Data;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudWebApi.IntegrationTests.Clients; using AsbCloudWebApi.IntegrationTests.Clients;
using System.Net; using System.Net;
using System.Reflection;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudWebApi.IntegrationTests.Data;
using Refit;
using Xunit; using Xunit;
namespace AsbCloudWebApi.IntegrationTests.Controllers; namespace AsbCloudWebApi.IntegrationTests.Controllers;
@ -28,7 +31,7 @@ public class WellOperationControllerTest : BaseIntegrationTest
DepthEnd = 20.0, DepthEnd = 20.0,
Day = 0.0, Day = 0.0,
NptHours = 0.0, NptHours = 0.0,
DateStart = new DateTimeOffset(new DateTime(2023, 02, 03, 1, 0, 0, DateTimeKind.Unspecified)), DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(Defaults.Wells[0].Timezone.Hours)),
DurationHours = 1.0, DurationHours = 1.0,
Comment = "1", Comment = "1",
IdUser = 1, IdUser = 1,
@ -117,4 +120,37 @@ public class WellOperationControllerTest : BaseIntegrationTest
var excludeProps = new[] { nameof(WellOperationDto.Id) }; var excludeProps = new[] { nameof(WellOperationDto.Id) };
MatchHelper.Match(dto, wellOperation, excludeProps); MatchHelper.Match(dto, wellOperation, excludeProps);
} }
[Fact]
public async Task ImportPlanDefaultExcelFileAsync_returns_success()
{
//arrange
//TODO: вынести в метод расширения. Сделать когда доберёмся до рефакторинга операций по скважине
var resourceName = Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith("WellOperationsPlan.xlsx"));
if (string.IsNullOrWhiteSpace(resourceName))
throw new ArgumentNullException(nameof(resourceName));
var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName);
if (stream is null)
throw new ArgumentNullException(nameof(stream));
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
//act
var streamPart = new StreamPart(memoryStream, "WellOperations.xlsx", "application/octet-stream");
var response = await client.ImportPlanDefaultExcelFileAsync(idWell, new[] { streamPart }, CancellationToken.None);
//assert
Assert.NotNull(response.Content);
Assert.Equal(4, response.Content.Count());
Assert.True(response.Content.All(w => Math.Abs(w.DateStart.Offset.Hours - Defaults.Wells[0].Timezone.Hours) < 0.1));
}
} }

View File

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using AsbCloudDb.Model; using AsbCloudApp.Data.DetectedOperation;
using AsbCloudInfrastructure.Services.DetectOperations; using AsbCloudInfrastructure.Services.DetectOperations;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors; using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
using Xunit; using Xunit;
@ -10,8 +10,7 @@ public class DetectorDrillingTests : DetectorDrilling
{ {
private const int idSlide = 5002; private const int idSlide = 5002;
private const int idRotor = 5003; private const int idRotor = 5003;
private const int idSlideWithOscillation = 12000;
[Theory] [Theory]
[MemberData(nameof(TelemetryRangeDrillingRotor))] [MemberData(nameof(TelemetryRangeDrillingRotor))]
public void DefineDrillingOperation_ShouldReturn_DrillingRotor(DetectableTelemetry[] telemetryRange) public void DefineDrillingOperation_ShouldReturn_DrillingRotor(DetectableTelemetry[] telemetryRange)
@ -54,7 +53,7 @@ public class DetectorDrillingTests : DetectorDrilling
//arrange //arrange
var operationDetectorResult = new OperationDetectorResult var operationDetectorResult = new OperationDetectorResult
{ {
Operation = new DetectedOperation Operation = new DetectedOperationDto
{ {
DepthStart = 5000, DepthStart = 5000,
DepthEnd = 6000, DepthEnd = 6000,
@ -76,7 +75,7 @@ public class DetectorDrillingTests : DetectorDrilling
//arrange //arrange
var operationDetectorResult = new OperationDetectorResult var operationDetectorResult = new OperationDetectorResult
{ {
Operation = new DetectedOperation Operation = new DetectedOperationDto
{ {
DepthStart = 5000, DepthStart = 5000,
DepthEnd = 5000, DepthEnd = 5000,
@ -201,7 +200,6 @@ public class DetectorDrillingTests : DetectorDrilling
{ {
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.306f, WellDepth = 415.306f,
Pressure = 53.731934f, Pressure = 53.731934f,
HookWeight = 41.049942f, HookWeight = 41.049942f,
@ -211,7 +209,6 @@ public class DetectorDrillingTests : DetectorDrilling
}, },
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.311f, WellDepth = 415.311f,
Pressure = 57.660595f, Pressure = 57.660595f,
HookWeight = 40.898712f, HookWeight = 40.898712f,
@ -221,7 +218,6 @@ public class DetectorDrillingTests : DetectorDrilling
}, },
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.326f, WellDepth = 415.326f,
Pressure = 59.211086f, Pressure = 59.211086f,
HookWeight = 40.882797f, HookWeight = 40.882797f,
@ -231,7 +227,6 @@ public class DetectorDrillingTests : DetectorDrilling
}, },
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.344f, WellDepth = 415.344f,
Pressure = 59.484406f, Pressure = 59.484406f,
HookWeight = 40.91972f, HookWeight = 40.91972f,
@ -241,7 +236,6 @@ public class DetectorDrillingTests : DetectorDrilling
}, },
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.364f, WellDepth = 415.364f,
Pressure = 60.739918f, Pressure = 60.739918f,
HookWeight = 40.795666f, HookWeight = 40.795666f,
@ -251,7 +245,6 @@ public class DetectorDrillingTests : DetectorDrilling
}, },
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.378f, WellDepth = 415.378f,
Pressure = 62.528984f, Pressure = 62.528984f,
HookWeight = 40.52114f, HookWeight = 40.52114f,
@ -261,7 +254,6 @@ public class DetectorDrillingTests : DetectorDrilling
}, },
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.392f, WellDepth = 415.392f,
Pressure = 67.0039f, Pressure = 67.0039f,
HookWeight = 38.878895f, HookWeight = 38.878895f,
@ -271,7 +263,6 @@ public class DetectorDrillingTests : DetectorDrilling
}, },
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.392f, WellDepth = 415.392f,
Pressure = 65.72418f, Pressure = 65.72418f,
HookWeight = 42.53173f, HookWeight = 42.53173f,
@ -281,7 +272,6 @@ public class DetectorDrillingTests : DetectorDrilling
}, },
new DetectableTelemetry new DetectableTelemetry
{ {
IdUser = 1,
WellDepth = 415.392f, WellDepth = 415.392f,
Pressure = 56.82195f, Pressure = 56.82195f,
HookWeight = 43.15844f, HookWeight = 43.15844f,

View File

@ -117,7 +117,7 @@ namespace AsbCloudWebApi.Tests.Services.WellOperationExport
wellOperationImportTemplateService = new WellOperationImportTemplateService(); wellOperationImportTemplateService = new WellOperationImportTemplateService();
wellOperationExportService = new WellOperationExportService(wellOperationRepository, wellService, wellOperationImportTemplateService); wellOperationExportService = new WellOperationExportService(wellOperationRepository, wellService, wellOperationImportTemplateService);
wellOperationImportService = new WellOperationImportService(wellOperationRepository); wellOperationImportService = new WellOperationImportService(wellService, wellOperationRepository);
wellOperationDefaultExcelParser = new WellOperationDefaultExcelParser(); wellOperationDefaultExcelParser = new WellOperationDefaultExcelParser();
this.output = output; this.output = output;
} }

View File

@ -92,7 +92,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
if (!isCompanyOwnsWell) if (!isCompanyOwnsWell)
return Forbid(); return Forbid();
var content = await telemetryDataService.GetAsync(idWell, begin, var content = await telemetryDataService.GetByWellAsync(idWell, begin,
intervalSec, approxPointsCount, token).ConfigureAwait(false); intervalSec, approxPointsCount, token).ConfigureAwait(false);
return Ok(content); return Ok(content);
@ -123,7 +123,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
if (!isCompanyOwnsWell) if (!isCompanyOwnsWell)
return Forbid(); return Forbid();
var content = await telemetryDataService.GetAsync(idWell, request, token); var content = await telemetryDataService.GetByWellAsync(idWell, request, token);
return Ok(content); return Ok(content);
} }

View File

@ -281,7 +281,6 @@ namespace AsbCloudWebApi.Controllers
foreach (var wellOperation in wellOperations) foreach (var wellOperation in wellOperations)
{ {
wellOperation.IdWell = idWell; wellOperation.IdWell = idWell;
wellOperation.LastUpdateDate = DateTimeOffset.UtcNow;
wellOperation.IdUser = User.GetUserId(); wellOperation.IdUser = User.GetUserId();
wellOperation.IdType = idType; wellOperation.IdType = idType;
} }
@ -313,7 +312,6 @@ namespace AsbCloudWebApi.Controllers
value.IdWell = idWell; value.IdWell = idWell;
value.Id = idOperation; value.Id = idOperation;
value.LastUpdateDate = DateTimeOffset.UtcNow;
value.IdUser = User.GetUserId(); value.IdUser = User.GetUserId();
var result = await operationRepository.UpdateAsync(value, token) var result = await operationRepository.UpdateAsync(value, token)