feat(api,core): Методы в API для получения метрик заданий роботам, StatRobotMetricsController.
This commit is contained in:
@@ -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 Наряды
|
||||
|
||||
@@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// Шаг группировки в минутах (например, 2, 30, 60, 1440)
|
||||
/// </summary>
|
||||
public int IntervalMinutes { get; set; } = 30;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace PARR.API.Contracts.V1.Responses.Statistics
|
||||
{
|
||||
public record StatFilteredChartPoint
|
||||
{
|
||||
public DateTimeOffset Timestamp { get; init; }
|
||||
public int Count { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Метрики заданий роботам
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Статистика по Заданиям Роботу, график
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.StatRobotMetrics.GetRobotStatusMetrics)]
|
||||
public async Task<IActionResult> GetRobotStatusMetrics([FromRoute] RobotsEnum robotCode, [FromRoute] ChartPeriod period, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await _robotMetricsService.GetRobotStatusMetricsAsync(robotCode, period, cancellationToken);
|
||||
|
||||
var response = _mapper.Map<List<StatRobotStatusChartPoint>>(data);
|
||||
|
||||
return Ok(new Response<List<StatRobotStatusChartPoint>>(response, true));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Статистика по Статусам Заданий, график
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.StatRobotMetrics.GetTaskStatusMetrics)]
|
||||
public async Task<IActionResult> GetTaskStatusMetrics([FromRoute] RobotsEnum robotCode, [FromRoute] ChartPeriod period, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await _robotMetricsService.GetTaskStatusMetricsAsync(robotCode, period, cancellationToken);
|
||||
|
||||
var response = _mapper.Map<List<StatTaskStatusChartPoint>>(data);
|
||||
|
||||
return Ok(new Response<List<StatTaskStatusChartPoint>>(response, true));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Статистика по статусам заданий, гибкий фильтр
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.StatRobotMetrics.GetFilteredMetrics)]
|
||||
public async Task<IActionResult> GetFilteredMetrics([FromQuery] RobotFilteredMetricsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// ------- Правильность расчетов этого метода доконца не проверена -------
|
||||
|
||||
var filter = _mapper.Map<MetricFilter>(request);
|
||||
|
||||
var data = await _robotMetricsService.GetFilteredMetricsAsync(filter, cancellationToken);
|
||||
|
||||
var response = _mapper.Map<List<StatFilteredChartPoint>>(data);
|
||||
|
||||
return Ok(new Response<List<StatFilteredChartPoint>>(response, true));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<TaskStatusChartPoint, StatTaskStatusChartPoint>();
|
||||
CreateMap<RobotStatusChartPoint, StatRobotStatusChartPoint>();
|
||||
CreateMap<FilteredChartPoint, StatFilteredChartPoint>();
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<StatRobotSnapshotQuery, RobotSnapshotQuery>();
|
||||
|
||||
|
||||
CreateMap<RobotFilteredMetricsQuery, MetricFilter>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IUnitService, UnitService>();
|
||||
services.AddScoped<UnitCacheService>();
|
||||
|
||||
services.AddScoped<IRobotMetricsService, RobotMetricsService>();
|
||||
|
||||
//services.AddScoped<IUserService, UserService>();
|
||||
|
||||
#endregion
|
||||
|
||||
10
PARR.Core/Extensions/EnumerableExtensions.cs
Normal file
10
PARR.Core/Extensions/EnumerableExtensions.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace PARR.Core.Extensions
|
||||
{
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
public static int MaxOrDefault(this IEnumerable<int> source)
|
||||
{
|
||||
return source.Any() ? source.Max() : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
PARR.Core/Services/RobotMetrics/IRobotMetricsService.cs
Normal file
37
PARR.Core/Services/RobotMetrics/IRobotMetricsService.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using PARR.Domain.DTOs.RobotMetrics;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.RobotMetrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Отчетность по метрикам заданий и работы роботов.
|
||||
/// </summary>
|
||||
public interface IRobotMetricsService
|
||||
{
|
||||
/// <summary>
|
||||
/// Статистика по Заданиям Роботу
|
||||
/// </summary>
|
||||
/// <param name="robotCode"></param>
|
||||
/// <param name="period"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<RobotStatusChartPoint>> GetRobotStatusMetricsAsync(RobotsEnum robotCode, ChartPeriod period, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Статистика по Статусам Заданий
|
||||
/// </summary>
|
||||
/// <param name="robotCode"></param>
|
||||
/// <param name="period"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<TaskStatusChartPoint>> GetTaskStatusMetricsAsync(RobotsEnum robotCode, ChartPeriod period, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Статистика с применением гибких фильтров
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<FilteredChartPoint>> GetFilteredMetricsAsync(MetricFilter filter, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
260
PARR.Core/Services/RobotMetrics/RobotMetricsService.cs
Normal file
260
PARR.Core/Services/RobotMetrics/RobotMetricsService.cs
Normal file
@@ -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<RobotMetricsService> _logger;
|
||||
|
||||
public RobotMetricsService(
|
||||
IRobotConfigurationSnapshotRepository snapshotRepository,
|
||||
IRobotConfigurationRepository configurationRepository,
|
||||
ILogger<RobotMetricsService> logger
|
||||
)
|
||||
{
|
||||
_snapshotRepository = snapshotRepository;
|
||||
_configurationRepository = configurationRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<FilteredChartPoint>> 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<List<RobotStatusChartPoint>> 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<List<TaskStatusChartPoint>> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генератор сетки времени
|
||||
/// </summary>
|
||||
/// <param name="fromDate"></param>
|
||||
/// <param name="toDate"></param>
|
||||
/// <param name="intervalMinutes"></param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<DateTimeOffset> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
9
PARR.Domain/DTOs/RobotMetrics/FilteredChartPoint.cs
Normal file
9
PARR.Domain/DTOs/RobotMetrics/FilteredChartPoint.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace PARR.Domain.DTOs.RobotMetrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Статистика по роботам, согласно гибким фильтрам.
|
||||
/// </summary>
|
||||
/// <param name="Timestamp"></param>
|
||||
/// <param name="Count"></param>
|
||||
public record FilteredChartPoint(DateTimeOffset Timestamp, int Count);
|
||||
}
|
||||
16
PARR.Domain/DTOs/RobotMetrics/MetricFilter.cs
Normal file
16
PARR.Domain/DTOs/RobotMetrics/MetricFilter.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
7
PARR.Domain/DTOs/RobotMetrics/RobotStatusChartPoint.cs
Normal file
7
PARR.Domain/DTOs/RobotMetrics/RobotStatusChartPoint.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace PARR.Domain.DTOs.RobotMetrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Статистика по Робот Сатусам, на графике
|
||||
/// </summary>
|
||||
public record RobotStatusChartPoint(DateTimeOffset Timestamp, int Wait, int InProgress, int Error, int Complete);
|
||||
}
|
||||
11
PARR.Domain/DTOs/RobotMetrics/TaskStatusChartPoint.cs
Normal file
11
PARR.Domain/DTOs/RobotMetrics/TaskStatusChartPoint.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace PARR.Domain.DTOs.RobotMetrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Статистика по Статусам заданий роботам, на графике
|
||||
/// </summary>
|
||||
/// <param name="Timestamp"></param>
|
||||
/// <param name="Creating"></param>
|
||||
/// <param name="Updating"></param>
|
||||
/// <param name="Ok"></param>
|
||||
public record TaskStatusChartPoint(DateTimeOffset Timestamp, int Creating, int Updating, int Ok);
|
||||
}
|
||||
26
PARR.Domain/Enums/ChartPeriod.cs
Normal file
26
PARR.Domain/Enums/ChartPeriod.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PARR.Domain.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// Периоды для графиков
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum ChartPeriod
|
||||
{
|
||||
/// <summary>
|
||||
/// Последние 2 часа
|
||||
/// </summary>
|
||||
TwoHours = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Последние 24 часа
|
||||
/// </summary>
|
||||
TwentyFourHours = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Последняя неделя
|
||||
/// </summary>
|
||||
SevenDays = 3
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user