diff --git a/AsbCloudApp/Data/SlipsStatDto.cs b/AsbCloudApp/Data/SlipsStatDto.cs new file mode 100644 index 00000000..05843a92 --- /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 SlipsDepth { get; set; } + } +} diff --git a/AsbCloudApp/Repositories/ISlipsStatsRepository.cs b/AsbCloudApp/Repositories/ISlipsStatsRepository.cs new file mode 100644 index 00000000..644a8a79 --- /dev/null +++ b/AsbCloudApp/Repositories/ISlipsStatsRepository.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.Repositories +{ + /// + /// Сервис для получения аналитики удержания в клиньях + /// + public interface ISlipsStatsRepository + { + /// + /// Получение записей для построения аналитики удержания в клиньях + /// + /// параметры запроса + /// + /// + Task> GetAllAsync(OperationStatRequest request, CancellationToken token); + } +} diff --git a/AsbCloudApp/Requests/OperationStatRequest.cs b/AsbCloudApp/Requests/OperationStatRequest.cs new file mode 100644 index 00000000..c249b86b --- /dev/null +++ b/AsbCloudApp/Requests/OperationStatRequest.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace AsbCloudApp.Requests +{ + /// + /// Параметры фильтра операции + /// + public class OperationStatRequest : RequestBase + { + + /// + /// Дата начала периода, за который строится отчет + /// + public DateTime? DateStart { get; set; } + + /// + /// Дата окончания периода, за который строится отчет + /// + public DateTime? DateEnd { get; set; } + + + /// + /// Минимальная продолжительность операции, мину + /// + public int? DurationMinutesMin { get; set; } + + /// + /// Максимальная продолжительность операции, мин + /// + public int? DurationMinutesMax { get; set; } + + + } +} diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 3b480e2d..a0268bd9 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -199,6 +199,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient, CrudCacheRepositoryBase>(); diff --git a/AsbCloudInfrastructure/Repository/SlipsStatRepository.cs b/AsbCloudInfrastructure/Repository/SlipsStatRepository.cs new file mode 100644 index 00000000..7e43f000 --- /dev/null +++ b/AsbCloudInfrastructure/Repository/SlipsStatRepository.cs @@ -0,0 +1,173 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Repository +{ + public class SlipsStatRepository : ISlipsStatsRepository + { + private readonly IAsbCloudDbContext db; + public SlipsStatRepository(IAsbCloudDbContext db) + { + this.db = db; + } + + public async Task> GetAllAsync(OperationStatRequest request, CancellationToken token) + { + if (request.DateStart.HasValue) + request.DateStart = DateTime.SpecifyKind(request.DateStart.Value, DateTimeKind.Utc); + + if (request.DateEnd.HasValue) + request.DateEnd = DateTime.SpecifyKind(request.DateEnd.Value, DateTimeKind.Utc); + + var schedulesQuery = db.Schedule + .Include(s => s.Well) + .Include(s => s.Driller) + .AsNoTracking(); + + if (request.DateStart.HasValue && request.DateEnd.HasValue) + schedulesQuery = schedulesQuery. + Where(s => s.DrillStart >= request.DateStart && s.DrillEnd <= request.DateEnd); + + 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.DateStart.HasValue && request.DateEnd.HasValue) + factWellOperationsQuery = factWellOperationsQuery + .Where(o => o.DateStart.AddHours(o.DurationHours) > request.DateStart && o.DateStart < request.DateEnd); + + 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.FirstOrDefault()!.WellSectionType.Caption + }); + + var detectedOperationsQuery = db.DetectedOperations + .Where(o => idsTelemetries.Contains(o.IdTelemetry)) + .Where(o => o.IdCategory == WellOperationCategory.IdSlipsTime) + .AsNoTracking(); + + if (request.DateStart.HasValue && request.DateEnd.HasValue) + detectedOperationsQuery = detectedOperationsQuery + .Where(o => o.DateStart < request.DateEnd) + .Where(o => o.DateEnd > request.DateStart); + + TimeSpan? durationMinutesMin = request.DurationMinutesMin.HasValue + ? new TimeSpan(0, request.DurationMinutesMin.Value, 0) + : null; + TimeSpan? durationMinutesMax = request.DurationMinutesMax.HasValue + ? new TimeSpan(0, request.DurationMinutesMax.Value, 0) + : null; + + if (durationMinutesMin.HasValue && durationMinutesMax.HasValue) + { + detectedOperationsQuery = detectedOperationsQuery + .Where(o => o.DateEnd - o.DateStart >= durationMinutesMin.Value + && o.DateEnd - o.DateStart <= durationMinutesMax.Value); + } + else if (durationMinutesMin.HasValue && !durationMinutesMax.HasValue) + { + detectedOperationsQuery = detectedOperationsQuery + .Where(o => o.DateEnd - o.DateStart >= durationMinutesMin.Value); + } + else if (!durationMinutesMin.HasValue && durationMinutesMax.HasValue) + { + detectedOperationsQuery = detectedOperationsQuery + .Where(o => o.DateEnd - o.DateStart <= durationMinutesMax.Value); + } + + 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), + SlipsDepth = 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..f5e1dec8 --- /dev/null +++ b/AsbCloudWebApi/Controllers/SlipsStatController.cs @@ -0,0 +1,48 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +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 ISlipsStatsRepository slipsAnalyticsService; + + public SlipsStatController(ISlipsStatsRepository slipsAnalyticsService) + { + this.slipsAnalyticsService = slipsAnalyticsService; + } + + /// + /// Получить аналитику по удержанию в клиньях (по бурильщикам) + /// + /// Параметры запроса + /// Токен отмены задачи + /// Список бурильщиков + [HttpGet] + [Permission] + [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + public async Task GetAllAsync( + [FromQuery] OperationStatRequest request, + CancellationToken token) + { + var modal = await slipsAnalyticsService.GetAllAsync(request, token).ConfigureAwait(false); + + return Ok(modal); + } + } +} +