diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs index 0f2e903a..a6d4d22b 100644 --- a/PARR.API/Contracts/V1/ApiRoutes.cs +++ b/PARR.API/Contracts/V1/ApiRoutes.cs @@ -1,4 +1,6 @@ -namespace PARR.API.Contracts.V1 +using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; + +namespace PARR.API.Contracts.V1 { // https://tproger.ru/translations/luchshie-praktiki-razrabotki-rest-api-20-sovetov/ @@ -324,6 +326,16 @@ public const string GetWorkloadTemplateReport = BaseStat + "/workload/templates/{reportType}/{filter}/{state}/{date}"; } + public static class StatRobotMetrics + { + public const string GetRobotStatusMetrics = BaseStat + "/robot-metrics/robot-status/{robotCode}/{period}"; + + public const string GetTaskStatusMetrics = BaseStat + "/robot-metrics/task-status/{robotCode}/{period}"; + + public const string GetFilteredMetrics = BaseStat + "/robot-metrics/filtered"; + + } + #endregion #region Наряды diff --git a/PARR.API/Contracts/V1/Requests/Queries/RobotFilteredMetricsQuery.cs b/PARR.API/Contracts/V1/Requests/Queries/RobotFilteredMetricsQuery.cs new file mode 100644 index 00000000..5dfc4ee0 --- /dev/null +++ b/PARR.API/Contracts/V1/Requests/Queries/RobotFilteredMetricsQuery.cs @@ -0,0 +1,22 @@ +using PARR.Domain.Enums; + +namespace PARR.API.Contracts.V1.Requests.Queries +{ + public record RobotFilteredMetricsQuery + { + public RobotsEnum? RobotCode { get; init; } + + public RobotStatusEnum? RobotStatusCode { get; init; } + + public TaskStatusEnum? TaskStatusCode { get; init; } + + public DateTimeOffset? DateFrom { get; init; } + + public DateTimeOffset? DateTo { get; init; } + + /// + /// Шаг группировки в минутах (например, 2, 30, 60, 1440) + /// + public int IntervalMinutes { get; set; } = 30; + } +} diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatFilteredChartPoint.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatFilteredChartPoint.cs new file mode 100644 index 00000000..cc2d21b2 --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/Statistics/StatFilteredChartPoint.cs @@ -0,0 +1,8 @@ +namespace PARR.API.Contracts.V1.Responses.Statistics +{ + public record StatFilteredChartPoint + { + public DateTimeOffset Timestamp { get; init; } + public int Count { get; init; } + } +} diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatRobotStatusChartPoint.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatRobotStatusChartPoint.cs new file mode 100644 index 00000000..435f0a22 --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/Statistics/StatRobotStatusChartPoint.cs @@ -0,0 +1,11 @@ +namespace PARR.API.Contracts.V1.Responses.Statistics +{ + public record StatRobotStatusChartPoint + { + public DateTimeOffset Timestamp { get; init; } + public int Wait { get; init; } + public int InProgress { get; init; } + public int Error { get; init; } + public int Complete { get; init; } + } +} diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatTaskStatusChartPoint.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatTaskStatusChartPoint.cs new file mode 100644 index 00000000..c2576a02 --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/Statistics/StatTaskStatusChartPoint.cs @@ -0,0 +1,10 @@ +namespace PARR.API.Contracts.V1.Responses.Statistics +{ + public record StatTaskStatusChartPoint + { + public DateTimeOffset Timestamp { get; init; } + public int Creating { get; init; } + public int Updating { get; init; } + public int Ok { get; init; } + } +} diff --git a/PARR.API/Controllers/V1/Statistics/StatRobotMetricsController.cs b/PARR.API/Controllers/V1/Statistics/StatRobotMetricsController.cs new file mode 100644 index 00000000..3d891a40 --- /dev/null +++ b/PARR.API/Controllers/V1/Statistics/StatRobotMetricsController.cs @@ -0,0 +1,84 @@ +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using PARR.API.Contracts.V1; +using PARR.API.Contracts.V1.Requests.Queries; +using PARR.API.Contracts.V1.Responses.Base; +using PARR.API.Contracts.V1.Responses.Statistics; +using PARR.API.Controllers.V1.Base; +using PARR.Core.Services.RobotMetrics; +using PARR.Domain.Common.Roles; +using PARR.Domain.DTOs.RobotMetrics; +using PARR.Domain.Enums; + +namespace PARR.API.Controllers.V1.Statistics +{ + /// + /// Метрики заданий роботам + /// + [Authorize(Roles = ParrRoles.Administrator.Role)] + public class StatRobotMetricsController : BaseApiController + { + private readonly IMapper _mapper; + private readonly IRobotMetricsService _robotMetricsService; + + public StatRobotMetricsController( + IMapper mapper, + IRobotMetricsService robotMetricsService + ) + { + _mapper = mapper; + _robotMetricsService = robotMetricsService; + } + + + /// + /// Статистика по Заданиям Роботу, график + /// + /// + [HttpGet(ApiRoutes.StatRobotMetrics.GetRobotStatusMetrics)] + public async Task GetRobotStatusMetrics([FromRoute] RobotsEnum robotCode, [FromRoute] ChartPeriod period, CancellationToken cancellationToken) + { + var data = await _robotMetricsService.GetRobotStatusMetricsAsync(robotCode, period, cancellationToken); + + var response = _mapper.Map>(data); + + return Ok(new Response>(response, true)); + } + + + /// + /// Статистика по Статусам Заданий, график + /// + /// + [HttpGet(ApiRoutes.StatRobotMetrics.GetTaskStatusMetrics)] + public async Task GetTaskStatusMetrics([FromRoute] RobotsEnum robotCode, [FromRoute] ChartPeriod period, CancellationToken cancellationToken) + { + var data = await _robotMetricsService.GetTaskStatusMetricsAsync(robotCode, period, cancellationToken); + + var response = _mapper.Map>(data); + + return Ok(new Response>(response, true)); + } + + + /// + /// Статистика по статусам заданий, гибкий фильтр + /// + /// + [HttpGet(ApiRoutes.StatRobotMetrics.GetFilteredMetrics)] + public async Task GetFilteredMetrics([FromQuery] RobotFilteredMetricsQuery request, CancellationToken cancellationToken) + { + // ------- Правильность расчетов этого метода доконца не проверена ------- + + var filter = _mapper.Map(request); + + var data = await _robotMetricsService.GetFilteredMetricsAsync(filter, cancellationToken); + + var response = _mapper.Map>(data); + + return Ok(new Response>(response, true)); + } + + } +} diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index 457c6391..604125c4 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -5,6 +5,7 @@ using PARR.API.Contracts.V1.Responses.Statistics; using PARR.API.MappingProfiles.Resolvers; using PARR.Core.Repositories.Interfaces.Schedule; using PARR.Domain.DTOs.Matching; +using PARR.Domain.DTOs.RobotMetrics; using PARR.Domain.DTOs.RobotSnapshotDTO; using PARR.Domain.DTOs.RobotTask; using PARR.Domain.DTOs.Shortcode; @@ -500,6 +501,14 @@ namespace PARR.API.MappingProfiles #endregion + #region StatRobotMetrics + + CreateMap(); + CreateMap(); + CreateMap(); + + #endregion + } } diff --git a/PARR.API/MappingProfiles/RequestToDomainProfile.cs b/PARR.API/MappingProfiles/RequestToDomainProfile.cs index 2e9501e6..edd1f09a 100644 --- a/PARR.API/MappingProfiles/RequestToDomainProfile.cs +++ b/PARR.API/MappingProfiles/RequestToDomainProfile.cs @@ -2,6 +2,7 @@ using PARR.API.Contracts.V1.Requests; using PARR.API.Contracts.V1.Requests.Queries; using PARR.Domain.Common.Pagination; +using PARR.Domain.DTOs.RobotMetrics; using PARR.Domain.DTOs.RobotSnapshotDTO; using PARR.Domain.Entities.JobEntities; @@ -52,6 +53,9 @@ namespace PARR.API.MappingProfiles #endregion CreateMap(); + + + CreateMap(); } } } diff --git a/PARR.Core/DependencyInjection.cs b/PARR.Core/DependencyInjection.cs index d54d6055..c1d29715 100644 --- a/PARR.Core/DependencyInjection.cs +++ b/PARR.Core/DependencyInjection.cs @@ -8,6 +8,7 @@ using PARR.Core.Common.Interfaces; using PARR.Core.Services.MatchingStatusService; using PARR.Core.Services.NextRunServices; using PARR.Core.Services.NextRunServices.Subservices; +using PARR.Core.Services.RobotMetrics; using PARR.Core.Services.RobotSnapshotServices; using PARR.Core.Services.RobotTask.Implementations; using PARR.Core.Services.RobotTask.Interfaces; @@ -28,7 +29,6 @@ using PARR.Core.Services.UnitService.Implementations; using PARR.Core.Services.UnitService.Interfaces; using PARR.Core.Services.Workload.Implementations; using PARR.Core.Services.Workload.Interfaces; -using PARR.Domain.Entities.RobotEntities; using PARR.Domain.Enums; using PARR.Domain.Settings; @@ -115,6 +115,8 @@ namespace PARR.Core services.AddScoped(); services.AddScoped(); + services.AddScoped(); + //services.AddScoped(); #endregion diff --git a/PARR.Core/Extensions/EnumerableExtensions.cs b/PARR.Core/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..5d90e5d2 --- /dev/null +++ b/PARR.Core/Extensions/EnumerableExtensions.cs @@ -0,0 +1,10 @@ +namespace PARR.Core.Extensions +{ + public static class EnumerableExtensions + { + public static int MaxOrDefault(this IEnumerable source) + { + return source.Any() ? source.Max() : 0; + } + } +} diff --git a/PARR.Core/Services/RobotMetrics/IRobotMetricsService.cs b/PARR.Core/Services/RobotMetrics/IRobotMetricsService.cs new file mode 100644 index 00000000..fbfa7c27 --- /dev/null +++ b/PARR.Core/Services/RobotMetrics/IRobotMetricsService.cs @@ -0,0 +1,37 @@ +using PARR.Domain.DTOs.RobotMetrics; +using PARR.Domain.Enums; + +namespace PARR.Core.Services.RobotMetrics +{ + /// + /// Отчетность по метрикам заданий и работы роботов. + /// + public interface IRobotMetricsService + { + /// + /// Статистика по Заданиям Роботу + /// + /// + /// + /// + /// + Task> GetRobotStatusMetricsAsync(RobotsEnum robotCode, ChartPeriod period, CancellationToken cancellationToken); + + /// + /// Статистика по Статусам Заданий + /// + /// + /// + /// + /// + Task> GetTaskStatusMetricsAsync(RobotsEnum robotCode, ChartPeriod period, CancellationToken cancellationToken); + + /// + /// Статистика с применением гибких фильтров + /// + /// + /// + /// + Task> GetFilteredMetricsAsync(MetricFilter filter, CancellationToken cancellationToken); + } +} diff --git a/PARR.Core/Services/RobotMetrics/RobotMetricsService.cs b/PARR.Core/Services/RobotMetrics/RobotMetricsService.cs new file mode 100644 index 00000000..e8cee692 --- /dev/null +++ b/PARR.Core/Services/RobotMetrics/RobotMetricsService.cs @@ -0,0 +1,260 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PARR.Core.Extensions; +using PARR.Core.Repositories.Interfaces; +using PARR.Core.Repositories.Interfaces.RobotRepositories; +using PARR.Domain.DTOs.RobotMetrics; +using PARR.Domain.Enums; +using PARR.Domain.Exceptions; + +namespace PARR.Core.Services.RobotMetrics +{ + internal class RobotMetricsService : IRobotMetricsService + { + private readonly IRobotConfigurationSnapshotRepository _snapshotRepository; + private readonly IRobotConfigurationRepository _configurationRepository; + private readonly ILogger _logger; + + public RobotMetricsService( + IRobotConfigurationSnapshotRepository snapshotRepository, + IRobotConfigurationRepository configurationRepository, + ILogger logger + ) + { + _snapshotRepository = snapshotRepository; + _configurationRepository = configurationRepository; + _logger = logger; + } + + + public async Task> GetFilteredMetricsAsync(MetricFilter filter, CancellationToken cancellationToken) + { + // ------- Правильность расчетов этого метода доконца не проверена ------- + + if (filter == null) + throw new AppValidationException("Фильтр не может быть пустым."); + + if (filter.IntervalMinutes < 1) + throw new AppValidationException("Интервал группировки не может быть меньше 1 минуты."); + + var query = _snapshotRepository.Get().AsNoTracking(); + + if (filter.RobotCode.HasValue) + query = query.Where(t => t.RobotCode == (int)filter.RobotCode.Value); + + if (filter.RobotStatusCode.HasValue) + query = query.Where(t => t.RobotStatusCode == (int)filter.RobotStatusCode.Value); + + if (filter.TaskStatusCode.HasValue) + query = query.Where(t => t.TaskStatusCode == (int)filter.TaskStatusCode); + + // Если даты не переданы, берем последние 24 часа по умолчанию + var dateFrom = filter.DateFrom ?? DateTimeOffset.UtcNow.AddDays(-1); + query = query.Where(s => s.DateCreated >= dateFrom); + + if (filter.DateTo.HasValue) + query = query.Where(s => s.DateCreated <= filter.DateTo.Value); + + var dbData = await query.ToListAsync(cancellationToken); + + var dbGrouped = dbData + .GroupBy(s => RoundToInterval(s.DateCreated, filter.IntervalMinutes)) + .ToDictionary(t => t.Key, t => t.ToList()); + + var dateTo = filter.DateTo ?? DateTimeOffset.UtcNow; + + var result = GenerateTimeGrid(dateFrom, dateTo, filter.IntervalMinutes) + .Select(time => new FilteredChartPoint( + Timestamp: time, + //Count: dbGrouped.TryGetValue(time, out var points) ? points.Max(x => x.Count) : 0 + Count: dbGrouped.TryGetValue(time, out var points) + ? points.GroupBy(x => x.DateCreated) // Группируем по точной минуте снапшота + .Select(g => g.Sum(x => x.Count)) // Складываем всё, что подошли под фильтр в эту минуту + .MaxOrDefault() // Берем максимальный пик за весь интервал (например, за час) + : 0 // Если снапшотов не было — честный ноль + )).ToList(); + + return result; + } + + + public async Task> GetRobotStatusMetricsAsync(RobotsEnum robotCode, ChartPeriod period, CancellationToken cancellationToken) + { + ValidateRobot(robotCode); + CalculatePeriodDates(period, out var fromDate, out var intervalMinutes); + + // История из снапшотов + var snapshots = await _snapshotRepository.Get() + .AsNoTracking() + .Where(s => s.RobotCode == (int)robotCode && s.DateCreated >= fromDate) + .ToListAsync(cancellationToken); + + var dbGrouped = snapshots + .GroupBy(t => RoundToInterval(t.DateCreated, intervalMinutes)) + .ToDictionary(t => t.Key, t => t.ToList()); + + // Генерим сетку значений, если значений нет, вставляем нули + var history = GenerateTimeGrid(fromDate, DateTimeOffset.UtcNow, intervalMinutes) + .Select(time => dbGrouped.TryGetValue(time, out var points) + ? new RobotStatusChartPoint( + Timestamp: time, + // группируем по точной минуте снапшота, складываем внутренности, а потом ищем пик (Max) за весь интервал + Wait: //points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Wait).MaxOrDefault(x => x.Count), + points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Wait) + .GroupBy(x => x.DateCreated) + .Select(t => t.Sum(x => x.Count)) + .MaxOrDefault(), + InProgress: //points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.InProgress).MaxOrDefault(x => x.Count), + points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.InProgress) + .GroupBy(x => x.DateCreated) + .Select(g => g.Sum(x => x.Count)) + .MaxOrDefault(), + Error: //points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Error).MaxOrDefault(x => x.Count) + points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Error) + .GroupBy(x => x.DateCreated) + .Select(g => g.Sum(x => x.Count)) // Честная сумма всех ошибок в рамках одной минуты снапшота + .MaxOrDefault(), + Complete: + points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Complete) + .GroupBy(x => x.DateCreated) + .Select(g => g.Sum(x => x.Count)) // Честная сумма всех ошибок в рамках одной минуты снапшота + .MaxOrDefault() + ) + : new RobotStatusChartPoint(time, Wait: 0, InProgress: 0, Error: 0, Complete: 0) + ).ToList(); + + // Последнее значение в конце графика из реальной таблицы + var liveRaw = await _configurationRepository + .Get() + .AsNoTracking() + .Where(t => t.RobotCode == (int)robotCode) + .GroupBy(t => t.RobotStatusCode) + .Select(g => new { RobotStatusCode = g.Key, Count = g.Count() }) + .ToListAsync(cancellationToken); + + history.Add(new RobotStatusChartPoint( + Timestamp: DateTimeOffset.UtcNow, + Wait: liveRaw.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Wait).Sum(x => x.Count), + InProgress: liveRaw.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.InProgress).Sum(x => x.Count), + Error: liveRaw.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Error).Sum(x => x.Count), + Complete: liveRaw.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Complete).Sum(x => x.Count) + )); + + return history; + } + + + public async Task> GetTaskStatusMetricsAsync(RobotsEnum robotCode, ChartPeriod period, CancellationToken cancellationToken) + { + ValidateRobot(robotCode); + CalculatePeriodDates(period, out var fromDate, out var intervalMinutes); + + // История из снапшотов + var snapshots = await _snapshotRepository.Get() + .AsNoTracking() + .Where(s => s.RobotCode == (int)robotCode && s.DateCreated >= fromDate) + .ToListAsync(cancellationToken); + + var dbGrouped = snapshots + .GroupBy(s => RoundToInterval(s.DateCreated, intervalMinutes)) + .ToDictionary(t => t.Key, t => t.ToList()); + + var history = GenerateTimeGrid(fromDate, DateTimeOffset.UtcNow, intervalMinutes) + .Select(time => dbGrouped.TryGetValue(time, out var points) + ? new TaskStatusChartPoint( + Timestamp: time, + // группируем по точной минуте снапшота, складываем внутренности, а потом ищем пик (Max) за весь интервал + Creating: //points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Creating).MaxOrDefault(x => x.Count), + points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Creating) + .GroupBy(x => x.DateCreated) + .Select(g => g.Sum(x => x.Count)) + .MaxOrDefault(), + Updating: //points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Updating).MaxOrDefault(x => x.Count), + points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Updating) + .GroupBy(x => x.DateCreated) + .Select(g => g.Sum(x => x.Count)) + .MaxOrDefault(), + Ok: //points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Ok).MaxOrDefault(x => x.Count) + points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Ok) + .GroupBy(x => x.DateCreated) + .Select(g => g.Sum(x => x.Count)) + .MaxOrDefault() + ) + : new TaskStatusChartPoint(time, Creating: 0, Updating: 0, Ok: 0) + ).ToList(); + + // Живой текущий кадр в конец графика + var liveRaw = await _configurationRepository + .Get() + .AsNoTracking() + .Where(t => t.RobotCode == (int)robotCode) + .GroupBy(t => t.TaskStatusCode) + .Select(g => new { TaskStatusCode = g.Key, Count = g.Count() }) + .ToListAsync(cancellationToken); + + history.Add(new TaskStatusChartPoint( + Timestamp: DateTimeOffset.UtcNow, + Creating: liveRaw.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Creating).Sum(x => x.Count), + Updating: liveRaw.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Updating).Sum(x => x.Count), + Ok: liveRaw.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Ok).Sum(x => x.Count) + )); + + return history; + } + + + private void ValidateRobot(RobotsEnum robotCode) + { + if (!Enum.IsDefined(typeof(RobotsEnum), robotCode)) + throw new NotFoundException($"Робот с кодом {robotCode} не найден в системе."); + } + + + private void CalculatePeriodDates(ChartPeriod period, out DateTimeOffset fromDate, out int intervalMinutes) + { + switch (period) + { + case ChartPeriod.TwoHours: + fromDate = DateTimeOffset.UtcNow.AddHours(-2); + intervalMinutes = 2; + break; + case ChartPeriod.TwentyFourHours: + fromDate = DateTimeOffset.UtcNow.AddDays(-1); + intervalMinutes = 30; + break; + case ChartPeriod.SevenDays: + fromDate = DateTimeOffset.UtcNow.AddDays(-7); + intervalMinutes = 60; + break; + default: + throw new AppValidationException("Указан неподдерживаемый период времени."); + } + } + + + private DateTimeOffset RoundToInterval(DateTimeOffset dt, int intervalMinutes) + { + var minutes = (dt.Minute / intervalMinutes) * intervalMinutes; + return new DateTimeOffset(dt.Year, dt.Month, dt.Day, dt.Hour, minutes, 0, dt.Offset); + } + + /// + /// Генератор сетки времени + /// + /// + /// + /// + /// + private IEnumerable GenerateTimeGrid(DateTimeOffset fromDate, DateTimeOffset toDate, int intervalMinutes) + { + var startTime = RoundToInterval(fromDate, intervalMinutes); + var endTime = RoundToInterval(toDate, intervalMinutes); + + for (var time = startTime; time <= endTime; time = time.AddMinutes(intervalMinutes)) + { + yield return time; + } + } + + } +} diff --git a/PARR.Domain/DTOs/RobotMetrics/FilteredChartPoint.cs b/PARR.Domain/DTOs/RobotMetrics/FilteredChartPoint.cs new file mode 100644 index 00000000..498fa195 --- /dev/null +++ b/PARR.Domain/DTOs/RobotMetrics/FilteredChartPoint.cs @@ -0,0 +1,9 @@ +namespace PARR.Domain.DTOs.RobotMetrics +{ + /// + /// Статистика по роботам, согласно гибким фильтрам. + /// + /// + /// + public record FilteredChartPoint(DateTimeOffset Timestamp, int Count); +} diff --git a/PARR.Domain/DTOs/RobotMetrics/MetricFilter.cs b/PARR.Domain/DTOs/RobotMetrics/MetricFilter.cs new file mode 100644 index 00000000..77e05c32 --- /dev/null +++ b/PARR.Domain/DTOs/RobotMetrics/MetricFilter.cs @@ -0,0 +1,16 @@ +using PARR.Domain.Enums; + +namespace PARR.Domain.DTOs.RobotMetrics +{ + public record MetricFilter + { + public RobotsEnum? RobotCode { get; init; } + public RobotStatusEnum? RobotStatusCode { get; init; } + public TaskStatusEnum? TaskStatusCode { get; init; } + public DateTimeOffset? DateFrom { get; init; } + public DateTimeOffset? DateTo { get; init; } + + // Шаг группировки в минутах (например, 2, 30, 60, 1440) + public int IntervalMinutes { get; set; } = 30; + } +} diff --git a/PARR.Domain/DTOs/RobotMetrics/RobotStatusChartPoint.cs b/PARR.Domain/DTOs/RobotMetrics/RobotStatusChartPoint.cs new file mode 100644 index 00000000..1c7a8388 --- /dev/null +++ b/PARR.Domain/DTOs/RobotMetrics/RobotStatusChartPoint.cs @@ -0,0 +1,7 @@ +namespace PARR.Domain.DTOs.RobotMetrics +{ + /// + /// Статистика по Робот Сатусам, на графике + /// + public record RobotStatusChartPoint(DateTimeOffset Timestamp, int Wait, int InProgress, int Error, int Complete); +} diff --git a/PARR.Domain/DTOs/RobotMetrics/TaskStatusChartPoint.cs b/PARR.Domain/DTOs/RobotMetrics/TaskStatusChartPoint.cs new file mode 100644 index 00000000..ad161a95 --- /dev/null +++ b/PARR.Domain/DTOs/RobotMetrics/TaskStatusChartPoint.cs @@ -0,0 +1,11 @@ +namespace PARR.Domain.DTOs.RobotMetrics +{ + /// + /// Статистика по Статусам заданий роботам, на графике + /// + /// + /// + /// + /// + public record TaskStatusChartPoint(DateTimeOffset Timestamp, int Creating, int Updating, int Ok); +} diff --git a/PARR.Domain/Enums/ChartPeriod.cs b/PARR.Domain/Enums/ChartPeriod.cs new file mode 100644 index 00000000..277335f7 --- /dev/null +++ b/PARR.Domain/Enums/ChartPeriod.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace PARR.Domain.Enums +{ + /// + /// Периоды для графиков + /// + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum ChartPeriod + { + /// + /// Последние 2 часа + /// + TwoHours = 1, + + /// + /// Последние 24 часа + /// + TwentyFourHours = 2, + + /// + /// Последняя неделя + /// + SevenDays = 3 + } +}