diff --git a/AsbCloudApp/Data/SlipsStatDto.cs b/AsbCloudApp/Data/SlipsStatDto.cs new file mode 100644 index 00000000..dba3228b --- /dev/null +++ b/AsbCloudApp/Data/SlipsStatDto.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsbCloudApp.Data +{ + /// + /// DTO, описывающая аналитику удержания в клиньях + /// + public class SlipsStatDto + { + /// + /// ФИО бурильщика + /// + public string DrillerName { get; set; } = null!; + + /// + /// Количество скважин + /// + public int WellCount { get; set; } + + /// + /// Название секции + /// + public string SectionCaption { get; set; } = null!; + + /// + /// Количество удержаний в клиньях, шт. + /// + public int SlipsCount { get; set; } + + /// + /// Время удержания в клиньях, мин. + /// + public double SlipsTimeInMinutes { get; set; } + + /// + /// Проходка, м. + /// + public double SectionDepth { get; set; } + } +} diff --git a/AsbCloudApp/Requests/OperationStatRequest.cs b/AsbCloudApp/Requests/OperationStatRequest.cs new file mode 100644 index 00000000..d92d803d --- /dev/null +++ b/AsbCloudApp/Requests/OperationStatRequest.cs @@ -0,0 +1,30 @@ +using System; + +namespace AsbCloudApp.Requests +{ + /// + /// Параметры фильтра операции + /// + public class OperationStatRequest + { + /// + /// Дата начала операции в UTC + /// + public DateTime? DateStartUTC { get; set; } + + /// + /// Дата окончания операции в UTC + /// + public DateTime? DateEndUTC { get; set; } + + /// + /// Минимальная продолжительность операции, мин + /// + public double? DurationMinutesMin { get; set; } + + /// + /// Максимальная продолжительность операции, мин + /// + public double? DurationMinutesMax { get; set; } + } +} diff --git a/AsbCloudApp/Services/ISlipsStatService.cs b/AsbCloudApp/Services/ISlipsStatService.cs new file mode 100644 index 00000000..55dd0c0a --- /dev/null +++ b/AsbCloudApp/Services/ISlipsStatService.cs @@ -0,0 +1,23 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Requests; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudApp.Services +{ + /// + /// Сервис для получения аналитики удержания в клиньях + /// + public interface ISlipsStatService + { + /// + /// Получение записей для построения аналитики удержания в клиньях + /// + /// параметры запроса + /// + /// + Task> GetAllAsync(OperationStatRequest request, CancellationToken token); + } +} diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 9f845fff..e2c60158 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -26,12 +26,10 @@ using System; using AsbCloudApp.Data.Manuals; using AsbCloudApp.Services.AutoGeneratedDailyReports; using AsbCloudApp.Services.Notifications; -using AsbCloudApp.Services.WellOperationImport; using AsbCloudDb.Model.Manuals; using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports; +using AsbCloudApp.Services.WellOperationImport; using AsbCloudInfrastructure.Services.WellOperationImport; -using AsbCloudInfrastructure.Services.WellOperationImport.FileParser; -using AsbCloudInfrastructure.Services.ProcessMap.ProcessMapWellboreDevelopment; namespace AsbCloudInfrastructure { @@ -123,7 +121,6 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -136,6 +133,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -151,7 +149,6 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -204,6 +201,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient, CrudCacheRepositoryBase>(); @@ -235,13 +233,6 @@ namespace AsbCloudInfrastructure services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - services.AddTransient(); - services.AddTransient(); - return services; } diff --git a/AsbCloudInfrastructure/Services/SlipsStatService.cs b/AsbCloudInfrastructure/Services/SlipsStatService.cs new file mode 100644 index 00000000..2458da93 --- /dev/null +++ b/AsbCloudInfrastructure/Services/SlipsStatService.cs @@ -0,0 +1,163 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services; + +public class SlipsStatService : ISlipsStatService +{ + private readonly IAsbCloudDbContext db; + + public SlipsStatService(IAsbCloudDbContext db) + { + this.db = db; + } + + public async Task> GetAllAsync(OperationStatRequest request, CancellationToken token) + { + if (request.DateStartUTC.HasValue) + request.DateStartUTC = DateTime.SpecifyKind(request.DateStartUTC.Value, DateTimeKind.Utc); + + if (request.DateEndUTC.HasValue) + request.DateEndUTC = DateTime.SpecifyKind(request.DateEndUTC.Value, DateTimeKind.Utc); + + var schedulesQuery = db.Schedule + .Include(s => s.Well) + .Include(s => s.Driller) + .AsNoTracking(); + + if (request.DateStartUTC.HasValue && request.DateEndUTC.HasValue) + schedulesQuery = schedulesQuery. + Where(s => s.DrillStart >= request.DateStartUTC && s.DrillEnd <= request.DateEndUTC); + + var schedules = await schedulesQuery.ToArrayAsync(token); + + var wells = schedules + .Select(d => d.Well) + .Where(well => well.IdTelemetry != null) + .GroupBy(w => w.Id) + .ToDictionary(g => g.Key, g => g.First().IdTelemetry!.Value); + + var idsWells = wells.Keys; + var idsTelemetries = wells.Values; + var telemetries = wells.ToDictionary(wt => wt.Value, wt => wt.Key); + + var factWellOperationsQuery = db.WellOperations + .Where(o => idsWells.Contains(o.IdWell)) + .Where(o => o.IdType == 1) + .Where(o => WellOperationCategory.MechanicalDrillingSubIds.Contains(o.IdCategory)) + .Include(o => o.WellSectionType) + .AsNoTracking(); + + if (request.DateStartUTC.HasValue && request.DateEndUTC.HasValue) + factWellOperationsQuery = factWellOperationsQuery + .Where(o => o.DateStart.AddHours(o.DurationHours) > request.DateStartUTC && o.DateStart < request.DateEndUTC); + + var factWellOperations = await factWellOperationsQuery.ToArrayAsync(token); + + var sections = factWellOperations + .GroupBy(o => new { o.IdWell, o.IdWellSectionType }) + .Select(g => new + { + g.Key.IdWell, + g.Key.IdWellSectionType, + DepthStart = g.Min(o => o.DepthStart), + DepthEnd = g.Max(o => o.DepthEnd), + g.First().WellSectionType.Caption + }); + + var detectedOperationsQuery = db.DetectedOperations + .Where(o => idsTelemetries.Contains(o.IdTelemetry)) + .Where(o => o.IdCategory == WellOperationCategory.IdSlipsTime) + .AsNoTracking(); + + if (request.DateStartUTC.HasValue && request.DateEndUTC.HasValue) + detectedOperationsQuery = detectedOperationsQuery + .Where(o => o.DateStart < request.DateEndUTC) + .Where(o => o.DateEnd > request.DateStartUTC); + + if (request.DurationMinutesMin.HasValue) + { + var durationMinutesMin = TimeSpan.FromMinutes(request.DurationMinutesMin.Value); + detectedOperationsQuery = detectedOperationsQuery + .Where(o => o.DateEnd - o.DateStart >= durationMinutesMin); + } + + if (request.DurationMinutesMax.HasValue) + { + var durationMinutesMax = TimeSpan.FromMinutes(request.DurationMinutesMax.Value); + detectedOperationsQuery = detectedOperationsQuery + .Where(o => o.DateEnd - o.DateStart <= durationMinutesMax); + } + + var detectedOperations = await detectedOperationsQuery + .ToArrayAsync(token); + + var detectedOperationsGroupedByDrillerAndSection = detectedOperations.Select(o => new + { + Operation = o, + IdWell = telemetries[o.IdTelemetry], + schedules.FirstOrDefault(s => + s.IdWell == telemetries[o.IdTelemetry] + && s.DrillStart <= o.DateStart + && s.DrillEnd >= o.DateStart + && new TimeDto(s.ShiftStart) <= new TimeDto(o.DateStart.DateTime) + && new TimeDto(s.ShiftEnd) >= new TimeDto(o.DateStart.DateTime)) + ?.Driller, + Section = sections.FirstOrDefault(s => + s.IdWell == telemetries[o.IdTelemetry] + && s.DepthStart <= o.DepthStart + && s.DepthEnd >= o.DepthStart) + }) + .Where(o => o.Driller != null) + .Where(o => o.Section != null) + .Select(o => new + { + o.Operation, + o.IdWell, + Driller = o.Driller!, + Section = o.Section! + }) + .GroupBy(o => new { o.Driller.Id, o.Section.IdWellSectionType }); + + + var factWellOperationsGroupedByDrillerAndSection = factWellOperations + .Select(o => new + { + Operation = o, + schedules.FirstOrDefault(s => + s.IdWell == o.IdWell + && s.DrillStart <= o.DateStart + && s.DrillEnd >= o.DateStart + && new TimeDto(s.ShiftStart) <= new TimeDto(o.DateStart.DateTime) + && new TimeDto(s.ShiftEnd) >= new TimeDto(o.DateStart.DateTime)) + ?.Driller, + }) + .Where(o => o.Driller != null) + .GroupBy(o => new { o.Driller!.Id, o.Operation.IdWellSectionType }); + + + var stats = detectedOperationsGroupedByDrillerAndSection.Select(group => new SlipsStatDto + { + DrillerName = $"{group.First().Driller!.Name} {group.First().Driller!.Patronymic} {group.First().Driller!.Surname}", + SlipsCount = group.Count(), + SlipsTimeInMinutes = group + .Sum(y => (y.Operation.DateEnd - y.Operation.DateStart).TotalMinutes), + SectionDepth = factWellOperationsGroupedByDrillerAndSection + .Where(o => o.Key.Id == group.Key.Id) + .Where(o => o.Key.IdWellSectionType == group.Key.IdWellSectionType) + .Sum(o => o.Max(op => op.Operation.DepthEnd) - o.Min(op => op.Operation.DepthStart)), + SectionCaption = group.First().Section.Caption, + WellCount = group.GroupBy(g => g.IdWell).Count(), + }); + + return stats; + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/SlipsStatController.cs b/AsbCloudWebApi/Controllers/SlipsStatController.cs new file mode 100644 index 00000000..124622a3 --- /dev/null +++ b/AsbCloudWebApi/Controllers/SlipsStatController.cs @@ -0,0 +1,53 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Exceptions; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudDb.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudWebApi.Controllers +{ + /// + /// Аналитика по удержанию в клиньях + /// + [Route("api/slipsStat")] + [ApiController] + [Authorize] + public class SlipsStatController : ControllerBase + { + private readonly ISlipsStatService slipsAnalyticsService; + + public SlipsStatController(ISlipsStatService slipsAnalyticsService) + { + this.slipsAnalyticsService = slipsAnalyticsService; + } + + /// + /// Получить аналитику по удержанию в клиньях (по бурильщикам) + /// + /// Параметры запроса + /// Токен отмены задачи + /// Список бурильщиков + [HttpGet] + [Permission] + [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + public async Task GetAllAsync( + [FromQuery] OperationStatRequest request, + CancellationToken token) + { + var idUser = User.GetUserId(); + + if (!idUser.HasValue) + throw new ForbidException("Не удается вас опознать"); + + var data = await slipsAnalyticsService.GetAllAsync(request, token).ConfigureAwait(false); + return Ok(data); + } + } +} +