diff --git a/AsbCloudApp/Data/DetectedOperationDto.cs b/AsbCloudApp/Data/DetectedOperationDto.cs index d977d0f3..59b28043 100644 --- a/AsbCloudApp/Data/DetectedOperationDto.cs +++ b/AsbCloudApp/Data/DetectedOperationDto.cs @@ -2,6 +2,7 @@ namespace AsbCloudApp.Data { +#nullable enable /// /// Автоматически определяемая операция /// @@ -61,13 +62,17 @@ namespace AsbCloudApp.Data /// /// Бурильщик /// - public DrillerDto Driller { get; set; } + public DrillerDto? Driller { get; set; } /// /// Целевые/нормативные показатели /// - public OperationValueDto OperationValue { get; set; } + public OperationValueDto? OperationValue { get; set; } + /// + /// Ключевой параметр операции + /// public double Value { get; set; } } +#nullable disable } diff --git a/AsbCloudApp/Data/DetectedOperationListDto.cs b/AsbCloudApp/Data/DetectedOperationListDto.cs index 33669416..82718e2d 100644 --- a/AsbCloudApp/Data/DetectedOperationListDto.cs +++ b/AsbCloudApp/Data/DetectedOperationListDto.cs @@ -4,12 +4,16 @@ using System.Linq; namespace AsbCloudApp.Data { +#nullable enable + /// + /// Статистика по операциям бурильщика + /// public class DetectedOperationStatDto { /// - /// Бурильцик + /// Бурильщик /// - public DrillerDto Driller { get; set; } + public DrillerDto? Driller { get; set; } /// /// Количество операции @@ -17,24 +21,24 @@ namespace AsbCloudApp.Data public int Count { get; set; } /// - /// Среднее по целевым показателям + /// Среднее по ключевому показателю /// - public double Average { get; set; } + public double AverageValue { get; set; } + + /// + /// Среднее целевого показателя + /// + public double? AverageTargetValue { get; set; } /// /// Коэффициент эффективности /// - public double Efficiency { get; set; } - - /// - /// Среднее по ключевому показателю - /// - public double AverageByParam { get; set; } + public double? Efficiency { get; set; } /// /// Коэффициент потерь /// - public double Loss { get; set; } + public double? Loss { get; set; } } /// @@ -45,8 +49,9 @@ namespace AsbCloudApp.Data /// /// Список всех операций /// - public IEnumerable List { get; set; } + public IEnumerable Operations { get; set; } - public ICollection Stats { get; set; } + public IEnumerable Stats { get; set; } } +#nullable disable } diff --git a/AsbCloudApp/Data/OperationValueDto.cs b/AsbCloudApp/Data/OperationValueDto.cs index ef0dd1da..4e9ea565 100644 --- a/AsbCloudApp/Data/OperationValueDto.cs +++ b/AsbCloudApp/Data/OperationValueDto.cs @@ -5,7 +5,7 @@ namespace AsbCloudApp.Data /// /// Описание целевых/нормативных показателей операций /// - public class OperationValueDto : IId + public class OperationValueDto : IId, IWellRelated { /// /// Идентификатор в БД diff --git a/AsbCloudApp/Data/ScheduleDto.cs b/AsbCloudApp/Data/ScheduleDto.cs index ade938f4..00fb51bb 100644 --- a/AsbCloudApp/Data/ScheduleDto.cs +++ b/AsbCloudApp/Data/ScheduleDto.cs @@ -37,5 +37,10 @@ namespace AsbCloudApp.Data /// Конец бурения /// public DateTime DrillEnd { get; set; } + + /// + /// Бурильщик + /// + public DrillerDto Driller { get; set; } } } diff --git a/AsbCloudApp/Data/TimeDto.cs b/AsbCloudApp/Data/TimeDto.cs index 28cb8916..7d628c91 100644 --- a/AsbCloudApp/Data/TimeDto.cs +++ b/AsbCloudApp/Data/TimeDto.cs @@ -5,7 +5,7 @@ namespace AsbCloudApp.Data /// /// DTO времени /// - public class TimeDto + public class TimeDto: IComparable { private int hour = 0; private int minute = 0; @@ -51,6 +51,11 @@ namespace AsbCloudApp.Data } } + /// + /// Кол-во секунд с начала суток + /// + public int TotalSeconds => (Hour * 60 + minute) * 60 + second; + /// public TimeDto() { } @@ -71,6 +76,14 @@ namespace AsbCloudApp.Data second = time.Second; } + /// + public TimeDto(DateTime fullDate) + { + hour = fullDate.Hour; + minute = fullDate.Minute; + second = fullDate.Second; + } + /// /// Makes System.TimeOnly /// @@ -83,5 +96,27 @@ namespace AsbCloudApp.Data var str = $"{Hour:00}:{Minute:00}:{Second:00}"; return str; } + + /// + public static bool operator ==(TimeDto a, TimeDto b) => a?.TotalSeconds == b?.TotalSeconds; + + /// + public static bool operator !=(TimeDto a, TimeDto b) => !(a == b); + + /// + public static bool operator <=(TimeDto a, TimeDto b) => a.TotalSeconds <= b.TotalSeconds; + + /// + public static bool operator >=(TimeDto a, TimeDto b) => a.TotalSeconds >= b.TotalSeconds; + + /// + public static bool operator <(TimeDto a, TimeDto b) => a.TotalSeconds < b.TotalSeconds; + + /// + public static bool operator >(TimeDto a, TimeDto b) => a.TotalSeconds > b.TotalSeconds; + + /// + public int CompareTo(TimeDto other) + => TotalSeconds - other.TotalSeconds; } } diff --git a/AsbCloudApp/Services/ICrudWellRelatedService.cs b/AsbCloudApp/Services/ICrudWellRelatedService.cs index 8c350bf3..25e3d8f8 100644 --- a/AsbCloudApp/Services/ICrudWellRelatedService.cs +++ b/AsbCloudApp/Services/ICrudWellRelatedService.cs @@ -19,7 +19,7 @@ namespace AsbCloudApp.Services /// id скважины /// /// emptyList if nothing found - Task> GetAllAsync(int idWell, CancellationToken token); + Task> GetByIdWellAsync(int idWell, CancellationToken token); /// /// Получение всех записей по нескольким скважинам @@ -27,7 +27,7 @@ namespace AsbCloudApp.Services /// id скважин /// /// emptyList if nothing found - Task> GetAllAsync(IEnumerable idsWells, CancellationToken token); + Task> GetByIdWellAsync(IEnumerable idsWells, CancellationToken token); } #nullable disable } \ No newline at end of file diff --git a/AsbCloudApp/Services/IOperationValueService.cs b/AsbCloudApp/Services/IOperationValueService.cs index 67206dbb..a35af223 100644 --- a/AsbCloudApp/Services/IOperationValueService.cs +++ b/AsbCloudApp/Services/IOperationValueService.cs @@ -2,7 +2,7 @@ namespace AsbCloudApp.Services { - public interface IOperationValueService : ICrudService + public interface IOperationValueService : ICrudWellRelatedService { } } diff --git a/AsbCloudApp/Services/IScheduleService.cs b/AsbCloudApp/Services/IScheduleService.cs index 5037ef43..326490e6 100644 --- a/AsbCloudApp/Services/IScheduleService.cs +++ b/AsbCloudApp/Services/IScheduleService.cs @@ -6,9 +6,8 @@ using System.Threading.Tasks; namespace AsbCloudApp.Services { - public interface IScheduleService : ICrudService + public interface IScheduleService : ICrudWellRelatedService { - Task> GetByIdWellAsync(int idWell, CancellationToken token = default); - Task GetDrillerAsync(int idWell, DateTime workTime, CancellationToken token = default); + Task GetDrillerAsync(int idWell, DateTime workTime, CancellationToken token); } } diff --git a/AsbCloudDb/Model/OperationValue.cs b/AsbCloudDb/Model/OperationValue.cs index 6fab8a3c..515c2db2 100644 --- a/AsbCloudDb/Model/OperationValue.cs +++ b/AsbCloudDb/Model/OperationValue.cs @@ -1,16 +1,11 @@ using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace AsbCloudDb.Model { [Table("t_operationvalue"), Comment("Целевые/нормативные показатели операции")] - public class OperationValue:IId + public class OperationValue: IId, IWellRelated { [Key] [Column("id"), Comment("Идентификатор")] diff --git a/AsbCloudDb/Model/Schedule.cs b/AsbCloudDb/Model/Schedule.cs index 8b96995c..3704419a 100644 --- a/AsbCloudDb/Model/Schedule.cs +++ b/AsbCloudDb/Model/Schedule.cs @@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace AsbCloudDb.Model { [Table("t_schedule"), Comment("График работы бурильщика")] - public class Schedule: IId + public class Schedule: IId, IWellRelated { [Key] [Column("id"),Comment("Идентификатор")] diff --git a/AsbCloudInfrastructure/Services/CrudWellRelatedServiceBase.cs b/AsbCloudInfrastructure/Services/CrudWellRelatedServiceBase.cs index 3f34a245..044f6def 100644 --- a/AsbCloudInfrastructure/Services/CrudWellRelatedServiceBase.cs +++ b/AsbCloudInfrastructure/Services/CrudWellRelatedServiceBase.cs @@ -23,7 +23,7 @@ namespace AsbCloudInfrastructure.Services public CrudWellRelatedServiceBase(IAsbCloudDbContext context, Func, IQueryable> makeQuery) : base(context, makeQuery) { } - public async Task> GetAllAsync(int idWell, CancellationToken token) + public async Task> GetByIdWellAsync(int idWell, CancellationToken token) { var entities = await GetQuery() .Where(e => e.IdWell == idWell) @@ -32,7 +32,7 @@ namespace AsbCloudInfrastructure.Services return dtos; } - public async Task> GetAllAsync(IEnumerable idsWells, CancellationToken token) + public async Task> GetByIdWellAsync(IEnumerable idsWells, CancellationToken token) { if (!idsWells.Any()) return Enumerable.Empty(); diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs index 8e368dd6..4caa5687 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs @@ -15,6 +15,10 @@ namespace AsbCloudInfrastructure.Services.DetectOperations { public class DetectedOperationService: IDetectedOperationService { + public const int IdOperationRotor = 1; + public const int IdOperationSlide = 3; + public const int IdOperationSlipsTime = 14; + private readonly IAsbCloudDbContext db; private readonly IWellService wellService; private readonly IOperationValueService operationValueService; @@ -35,38 +39,43 @@ namespace AsbCloudInfrastructure.Services.DetectOperations if (well?.IdTelemetry is null || well.Timezone is null) return null; - var res = new DetectedOperationListDto(); var query = BuildQuery(well, request) .AsNoTracking(); var data = await query.ToListAsync(token); - var operationValues = await operationValueService.GetAllAsync(token); - operationValues = operationValues.Where(o => o.IdWell == idWell); - var dtos = data.Select(o => Convert(o, well, operationValues)); - foreach (var item in dtos) + var operationValues = await operationValueService.GetByIdWellAsync(idWell, token); + var schedules = await scheduleService.GetByIdWellAsync(idWell, token); + var dtos = data.Select(o => Convert(o, well, operationValues, schedules)); + var groups = dtos.GroupBy(o => o.Driller); + + var stats = new List(groups.Count()); + foreach (var group in groups) { - item.Driller = await scheduleService.GetDrillerAsync(idWell, item.DateStart); + var itemsWithTarget = group.Where(i => i.OperationValue is not null); + var stat = new DetectedOperationStatDto + { + Driller = group.Key, + AverageValue = group.Sum(e => e.Value) / group.Count(), + Count = group.Count(), + }; + if (itemsWithTarget.Any()) + { + var itemsOutOfTarget = itemsWithTarget.Where(o => !IsTargetOk(o)); + stat.AverageTargetValue = itemsWithTarget.Average(e => e.OperationValue.TargetValue); + stat.Efficiency = 100d * itemsOutOfTarget.Count() / itemsWithTarget.Count(); + stat.Loss = itemsOutOfTarget.Sum(DeltaToTarget); + } + + stats.Add(stat); } - var group = dtos.GroupBy(o => o.Driller == null ? 0 : o.Driller.Id, - p => p, - (key, gr) => (key, gr.ToList())).ToDictionary(e => e.key, e => e.Item2); - res.List = dtos; - res.Stats = new List(); - foreach (var item in group) + + var result = new DetectedOperationListDto { - var obj = new DetectedOperationStatDto(); - obj.Driller = item.Value.FirstOrDefault()?.Driller; - obj.Count = item.Value.Count(); - obj.Average = item.Value.Sum(e=>e.OperationValue?.TargetValue ?? 0)/obj.Count; - obj.Efficiency = 100d * item.Value.Count(e => PredicateTarget(e)(e.Value)) / obj.Count; - obj.AverageByParam = item.Value.Sum(e => e.Value) / obj.Count; - obj.Loss = item.Value - .Where(e => !PredicateTarget(e)(e.Value)) - .Sum(p => Math.Abs(p.Value - p.OperationValue?.TargetValue ?? 0)); - res.Stats.Add(obj); - } - return res; + Operations = dtos, + Stats = stats + }; + return result; } public async Task DeleteAsync(int idWell, DetectedOperationRequest request, CancellationToken token) @@ -80,17 +89,25 @@ namespace AsbCloudInfrastructure.Services.DetectOperations return await db.SaveChangesAsync(token); } - /// - /// Определение применяемого предикат по типц операции - /// - /// Предикат для использования - private static Predicate PredicateTarget(DetectedOperationDto op) + private static bool IsTargetOk(DetectedOperationDto op) { - return op.OperationValue.IdOperationCategory switch + return (op.IdCategory) switch { - 1 => (x) => false, - 11 => (x) => x > op.OperationValue.TargetValue, - _ => (x) => true + IdOperationRotor => op.Value > op.OperationValue.TargetValue, + IdOperationSlide => op.Value > op.OperationValue.TargetValue, + IdOperationSlipsTime => op.Value > op.OperationValue.TargetValue, + _ => op.Value > op.OperationValue.TargetValue, + }; + } + + private static double DeltaToTarget(DetectedOperationDto op) + { + return (op.IdCategory) switch + { + IdOperationRotor => 0, + IdOperationSlide => 0, + IdOperationSlipsTime => op.Value - op.OperationValue.TargetValue, + _ => 0, }; } @@ -142,14 +159,26 @@ namespace AsbCloudInfrastructure.Services.DetectOperations return query; } - private static DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable operationValues) + private DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable operationValues, IEnumerable schedules) { var dto = operation.Adapt(); dto.IdWell = well.Id; - dto.DateStart = operation.DateStart.ToRemoteDateTime(well.Timezone.Hours); + var dateStart = operation.DateStart.ToRemoteDateTime(well.Timezone.Hours); + dto.DateStart = dateStart; dto.DateEnd = operation.DateEnd.ToRemoteDateTime(well.Timezone.Hours); dto.OperationValue = operationValues.FirstOrDefault(e => e.IdOperationCategory == dto.IdCategory && e.DepthStart <= dto.DepthStart); + + var timeStart = new TimeDto(dateStart); + var driller = schedules.FirstOrDefault(s => + s.DrillStart <= dateStart && + s.DrillEnd > dateStart && ( + s.ShiftStart > s.ShiftEnd + ) ^ (s.ShiftStart <= timeStart && + s.ShiftEnd > timeStart + )) + ?.Driller; + dto.Driller = driller; return dto; } diff --git a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionBackgroundService.cs b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionBackgroundService.cs index 98ff3fff..afb43a57 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionBackgroundService.cs @@ -97,15 +97,17 @@ namespace AsbCloudInfrastructure.Services.DetectOperations IdTelemetry = outer, LastDate = inner.SingleOrDefault()?.LastDate , }); - + var affected = 0; foreach (var item in JounedlastDetectedDates) { var newOperations = await DetectOperationsAsync(item.IdTelemetry, item.LastDate??DateTimeOffset.MinValue, db, token); if (newOperations.Any()) + { db.DetectedOperations.AddRange(newOperations); + affected += await db.SaveChangesAsync(token); + } } - - return await db.SaveChangesAsync(token); + return affected; } private async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) @@ -131,7 +133,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations var dbRequests_ = 0; var dbTime_ = 0d; - var sw_ = new System.Diagnostics.Stopwatch(); + var sw_ = new Stopwatch(); var otherTime_ = 0d; while (true) diff --git a/AsbCloudInfrastructure/Services/OperationValueService.cs b/AsbCloudInfrastructure/Services/OperationValueService.cs index 75640b8a..1ca687fc 100644 --- a/AsbCloudInfrastructure/Services/OperationValueService.cs +++ b/AsbCloudInfrastructure/Services/OperationValueService.cs @@ -4,7 +4,7 @@ using AsbCloudDb.Model; namespace AsbCloudInfrastructure.Services { - public class OperationValueService : CrudServiceBase, IOperationValueService + public class OperationValueService : CrudWellRelatedServiceBase, IOperationValueService { public OperationValueService(IAsbCloudDbContext context) : base(context) { diff --git a/AsbCloudInfrastructure/Services/ScheduleService.cs b/AsbCloudInfrastructure/Services/ScheduleService.cs index 7df6026e..0a09331f 100644 --- a/AsbCloudInfrastructure/Services/ScheduleService.cs +++ b/AsbCloudInfrastructure/Services/ScheduleService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { #nullable enable - public class ScheduleService : CrudServiceBase, IScheduleService + public class ScheduleService : CrudWellRelatedServiceBase, IScheduleService { private readonly IWellService wellService; @@ -22,16 +22,7 @@ namespace AsbCloudInfrastructure.Services this.wellService = wellService; } - public async Task> GetByIdWellAsync(int idWell, CancellationToken token = default) - { - var entities = await GetQuery() - .Where(s => s.IdWell == idWell) - .ToListAsync(token); - var dtos = entities.Select(Convert); - return dtos; - } - - public async Task GetDrillerAsync(int idWell, DateTime workTime, CancellationToken token = default) + public async Task GetDrillerAsync(int idWell, DateTime workTime, CancellationToken token) { var hoursOffset = wellService.GetTimezone(idWell).Hours; var date = workTime.ToUtcDateTimeOffset(hoursOffset); diff --git a/AsbCloudWebApi/Controllers/CrudWellRelatedController.cs b/AsbCloudWebApi/Controllers/CrudWellRelatedController.cs index 9677c411..da6cb100 100644 --- a/AsbCloudWebApi/Controllers/CrudWellRelatedController.cs +++ b/AsbCloudWebApi/Controllers/CrudWellRelatedController.cs @@ -48,7 +48,7 @@ namespace AsbCloudWebApi.Controllers return NoContent(); var idsWells = wells.Select(w => w.Id); - var result = await service.GetAllAsync(idsWells, token); + var result = await service.GetByIdWellAsync(idsWells, token); return Ok(result); } @@ -59,12 +59,12 @@ namespace AsbCloudWebApi.Controllers /// /// [HttpGet("well/{idWell}")] - public async Task>> GetAllAsync(int idWell, CancellationToken token) + public async Task>> GetByIdWellAsync(int idWell, CancellationToken token) { if (!await UserHasAccesToWellAsync(idWell, token)) return Forbid(); - var result = await service.GetAllAsync(idWell, token); + var result = await service.GetByIdWellAsync(idWell, token); return Ok(result); } @@ -108,6 +108,7 @@ namespace AsbCloudWebApi.Controllers return await base.UpdateAsync(value, token); } + /// [HttpDelete("{id}")] public override async Task> DeleteAsync(int id, CancellationToken token) { diff --git a/AsbCloudWebApi/Controllers/OperationValueController.cs b/AsbCloudWebApi/Controllers/OperationValueController.cs index f72a61c7..7f4d344a 100644 --- a/AsbCloudWebApi/Controllers/OperationValueController.cs +++ b/AsbCloudWebApi/Controllers/OperationValueController.cs @@ -5,12 +5,12 @@ using Microsoft.AspNetCore.Mvc; namespace AsbCloudWebApi.Controllers { - [Route("api/operationvalue")] + [Route("api/operationValue")] [ApiController] [Authorize] - public class OperationValueController : CrudController + public class OperationValueController : CrudWellRelatedController { - public OperationValueController(IOperationValueService service) : base(service) + public OperationValueController(IOperationValueService service, IWellService wellService) : base(wellService, service) { } } diff --git a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs index 70547e8f..ac551003 100644 --- a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs +++ b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs @@ -45,7 +45,7 @@ namespace AsbCloudWebApi.Controllers.SAUB /// /// [HttpGet] - [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + [ProducesResponseType(typeof(DetectedOperationListDto), (int)System.Net.HttpStatusCode.OK)] public async Task GetAsync( int idWell, [FromQuery] DetectedOperationRequest request,