1973 lines
81 KiB
C#
1973 lines
81 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.Statistics;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Options;
|
||
using System.Collections.Concurrent;
|
||
using System.Text.Json;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// 统计服务实现。
|
||
/// </summary>
|
||
#if DISABLED_LEGACY_STATISTICS_SERVICE
|
||
public sealed class StatisticsService : IStatisticsService
|
||
{
|
||
private readonly ILogger<StatisticsService> _logger;
|
||
private readonly RuntimeOptions _options;
|
||
private readonly ConcurrentDictionary<string, StatisticsConfig> _statisticsConfigs = new();
|
||
private readonly ConcurrentDictionary<string, object> _statisticsCache = new();
|
||
private readonly object _lock = new();
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public StatisticsService(ILogger<StatisticsService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
InitializeDefaultStatisticsConfigs();
|
||
_logger.LogInformation("统计服务已初始化");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<CycleTimeStatistics>> GetCycleTimeStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取节拍统计信息:开始时间={StartTime},结束时间={EndTime},产品类型={ProductTypeCode}",
|
||
startTime, endTime, productTypeCode);
|
||
|
||
var cacheKey = $"cycle_time_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{productTypeCode}";
|
||
|
||
if (_options.EnableStatisticsCache && _statisticsCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取节拍统计信息");
|
||
return Result.Success((CycleTimeStatistics)cached);
|
||
}
|
||
|
||
var statistics = await GenerateCycleTimeStatisticsAsync(startTime, endTime, productTypeCode, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_statisticsCache.TryAdd(cacheKey, statistics);
|
||
}
|
||
|
||
_logger.LogInformation("节拍统计信息获取完成:总处理数={Total},成功率={SuccessRate:F2}%,平均节拍={AvgCycleTime:F2}秒",
|
||
statistics.TotalProcessedCount, statistics.OverallSuccessRate, statistics.AverageCycleTimeSeconds);
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取节拍统计信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_CYCLE_TIME_STATISTICS_FAILED", "获取节拍统计信息失败", traceId);
|
||
return Result.FailWithTrace<CycleTimeStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<ShiftStatistics>> GetShiftStatisticsAsync(DateTime shiftDate, ShiftType? shiftType = null, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取班次统计信息:日期={ShiftDate},班次类型={ShiftType},产品类型={ProductTypeCode}",
|
||
shiftDate, shiftType, productTypeCode);
|
||
|
||
var cacheKey = $"shift_{shiftDate:yyyyMMdd}_{shiftType}_{productTypeCode}";
|
||
|
||
if (_options.EnableStatisticsCache && _statisticsCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取班次统计信息");
|
||
return Result.Success((ShiftStatistics)cached);
|
||
}
|
||
|
||
var statistics = await GenerateShiftStatisticsAsync(shiftDate, shiftType, productTypeCode, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_statisticsCache.TryAdd(cacheKey, statistics);
|
||
}
|
||
|
||
_logger.LogInformation("班次统计信息获取完成:计划产量={Planned},实际产量={Actual},完成率={CompletionRate:F2}%,合格率={PassRate:F2}%",
|
||
statistics.PlannedProductionCount, statistics.ActualProductionCount, statistics.ProductionCompletionRate, statistics.QualityPassRate);
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取班次统计信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_SHIFT_STATISTICS_FAILED", "获取班次统计信息失败", traceId);
|
||
return Result.FailWithTrace<ShiftStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<ShiftInfo>>> GetShiftListAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取班次列表:开始日期={StartDate},结束日期={EndDate}", startDate, endDate);
|
||
|
||
var shiftList = await GenerateShiftListAsync(startDate, endDate, cancellationToken);
|
||
|
||
_logger.LogInformation("班次列表获取完成:班次数量={ShiftCount}", shiftList.Count);
|
||
return Result.Success<IReadOnlyList<ShiftInfo>>(shiftList);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取班次列表失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_SHIFT_LIST_FAILED", "获取班次列表失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<ShiftInfo>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<ProductionEfficiencyStatistics>> GetProductionEfficiencyStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, StatisticsGranularity granularity = StatisticsGranularity.Hourly, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取生产效率统计:开始时间={StartTime},结束时间={EndTime},产品类型={ProductTypeCode},粒度={Granularity}",
|
||
startTime, endTime, productTypeCode, granularity);
|
||
|
||
var cacheKey = $"efficiency_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{productTypeCode}_{granularity}";
|
||
|
||
if (_options.EnableStatisticsCache && _statisticsCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取生产效率统计");
|
||
return Result.Success((ProductionEfficiencyStatistics)cached);
|
||
}
|
||
|
||
var statistics = await GenerateProductionEfficiencyStatisticsAsync(startTime, endTime, productTypeCode, granularity, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_statisticsCache.TryAdd(cacheKey, statistics);
|
||
}
|
||
|
||
_logger.LogInformation("生产效率统计获取完成:OEE={OEE:F2}%,时间利用率={TimeUtilization:F2}%,设备利用率={EquipmentUtilization:F2}%",
|
||
statistics.OverallEquipmentEffectiveness, statistics.TimeUtilizationRate, statistics.EquipmentUtilizationRate);
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取生产效率统计失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_PRODUCTION_EFFICIENCY_STATISTICS_FAILED", "获取生产效率统计失败", traceId);
|
||
return Result.FailWithTrace<ProductionEfficiencyStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<QualityStatistics>> GetQualityStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取质量统计信息:开始时间={StartTime},结束时间={EndTime},产品类型={ProductTypeCode}",
|
||
startTime, endTime, productTypeCode);
|
||
|
||
var cacheKey = $"quality_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{productTypeCode}";
|
||
|
||
if (_options.EnableStatisticsCache && _statisticsCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取质量统计信息");
|
||
return Result.Success((QualityStatistics)cached);
|
||
}
|
||
|
||
var statistics = await GenerateQualityStatisticsAsync(startTime, endTime, productTypeCode, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_statisticsCache.TryAdd(cacheKey, statistics);
|
||
}
|
||
|
||
_logger.LogInformation("质量统计信息获取完成:总检测数={Total},合格数={Qualified},合格率={PassRate:F2}%",
|
||
statistics.TotalInspectionCount, statistics.QualifiedCount, statistics.OverallPassRate);
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取质量统计信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_QUALITY_STATISTICS_FAILED", "获取质量统计信息失败", traceId);
|
||
return Result.FailWithTrace<QualityStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<EquipmentUtilizationStatistics>> GetEquipmentUtilizationStatisticsAsync(DateTime startTime, DateTime endTime, string? equipmentId = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取设备利用率统计:开始时间={StartTime},结束时间={EndTime},设备ID={EquipmentId}",
|
||
startTime, endTime, equipmentId);
|
||
|
||
var cacheKey = $"equipment_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{equipmentId}";
|
||
|
||
if (_options.EnableStatisticsCache && _statisticsCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取设备利用率统计");
|
||
return Result.Success((EquipmentUtilizationStatistics)cached);
|
||
}
|
||
|
||
var statistics = await GenerateEquipmentUtilizationStatisticsAsync(startTime, endTime, equipmentId, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_statisticsCache.TryAdd(cacheKey, statistics);
|
||
}
|
||
|
||
_logger.LogInformation("设备利用率统计获取完成:总体利用率={Overall:F2}%,生产利用率={Production:F2}%,可用率={Availability:F2}%",
|
||
statistics.OverallUtilizationRate, statistics.ProductionUtilizationRate, statistics.AvailabilityRate);
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取设备利用率统计失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_EQUIPMENT_UTILIZATION_STATISTICS_FAILED", "获取设备利用率统计失败", traceId);
|
||
return Result.FailWithTrace<EquipmentUtilizationStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<AlarmStatisticsSummary>> GetAlarmStatisticsSummaryAsync(DateTime startTime, DateTime endTime, OrpaonVision.Core.Common.AlarmLevel? alarmLevel = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取报警统计摘要:开始时间={StartTime},结束时间={EndTime},报警级别={AlarmLevel}",
|
||
startTime, endTime, alarmLevel);
|
||
|
||
var cacheKey = $"alarm_summary_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{alarmLevel}";
|
||
|
||
if (_options.EnableStatisticsCache && _statisticsCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取报警统计摘要");
|
||
return Result.Success((AlarmStatisticsSummary)cached);
|
||
}
|
||
|
||
var statistics = await GenerateAlarmStatisticsSummaryAsync(startTime, endTime, alarmLevel, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_statisticsCache.TryAdd(cacheKey, statistics);
|
||
}
|
||
|
||
_logger.LogInformation("报警统计摘要获取完成:总报警数={Total},活跃数={Active},确认数={Confirmed},清除数={Cleared}",
|
||
statistics.TotalAlarmCount, statistics.ActiveAlarmCount, statistics.ConfirmedAlarmCount, statistics.ClearedAlarmCount);
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取报警统计摘要失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_ALARM_STATISTICS_SUMMARY_FAILED", "获取报警统计摘要失败", traceId);
|
||
return Result.FailWithTrace<AlarmStatisticsSummary>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<ComprehensiveProductionReport>> GetComprehensiveProductionReportAsync(OrpaonVision.Core.Statistics.ProductionReportType reportType, DateTime startTime, DateTime endTime, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取综合生产报告:报告类型={ReportType},开始时间={StartTime},结束时间={EndTime},产品类型={ProductTypeCode}",
|
||
reportType, startTime, endTime, productTypeCode);
|
||
|
||
var report = await GenerateComprehensiveProductionReportAsync(reportType, startTime, endTime, productTypeCode, cancellationToken);
|
||
|
||
_logger.LogInformation("综合生产报告获取完成:报告ID={ReportId},总体评分={OverallScore:F2}",
|
||
report.ReportId, report.ExecutiveSummary.OverallScore);
|
||
|
||
return Result.Success(report);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取综合生产报告失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_COMPREHENSIVE_PRODUCTION_REPORT_FAILED", "获取综合生产报告失败", traceId);
|
||
return Result.FailWithTrace<ComprehensiveProductionReport>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<RealTimeStatisticsDashboard>> GetRealTimeStatisticsDashboardAsync(string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取实时统计仪表板:产品类型={ProductTypeCode}", productTypeCode);
|
||
|
||
var dashboard = await GenerateRealTimeStatisticsDashboardAsync(productTypeCode, cancellationToken);
|
||
|
||
_logger.LogInformation("实时统计仪表板获取完成:更新时间={UpdateTime},当前OEE={OEE:F2}%",
|
||
dashboard.LastUpdatedUtc, dashboard.Efficiency.CurrentOEE);
|
||
|
||
return Result.Success(dashboard);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取实时统计仪表板失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_REAL_TIME_STATISTICS_DASHBOARD_FAILED", "获取实时统计仪表板失败", traceId);
|
||
return Result.FailWithTrace<RealTimeStatisticsDashboard>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<HistoricalTrendData>> GetHistoricalTrendDataAsync(MetricType metricType, DateTime startTime, DateTime endTime, string? productTypeCode = null, StatisticsGranularity granularity = StatisticsGranularity.Hourly, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取历史趋势数据:指标类型={MetricType},开始时间={StartTime},结束时间={EndTime},产品类型={ProductTypeCode},粒度={Granularity}",
|
||
metricType, startTime, endTime, productTypeCode, granularity);
|
||
|
||
var cacheKey = $"trend_{metricType}_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{productTypeCode}_{granularity}";
|
||
|
||
if (_options.EnableStatisticsCache && _statisticsCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取历史趋势数据");
|
||
return Result.Success((HistoricalTrendData)cached);
|
||
}
|
||
|
||
var trendData = await GenerateHistoricalTrendDataAsync(metricType, startTime, endTime, productTypeCode, granularity, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_statisticsCache.TryAdd(cacheKey, trendData);
|
||
}
|
||
|
||
_logger.LogInformation("历史趋势数据获取完成:指标类型={MetricType},数据点数量={DataPointCount},趋势方向={Trend}",
|
||
metricType, trendData.DataPoints.Count, trendData.TrendAnalysis.TrendDescription);
|
||
|
||
return Result.Success(trendData);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取历史趋势数据失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_HISTORICAL_TREND_DATA_FAILED", "获取历史趋势数据失败", traceId);
|
||
return Result.FailWithTrace<HistoricalTrendData>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<StatisticsConfig>> CreateStatisticsConfigAsync(StatisticsConfig config, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("创建统计配置:配置类型={ConfigType},产品类型={ProductTypeCode},配置名称={ConfigName}",
|
||
config.ConfigType, config.ProductTypeCode, config.ConfigName);
|
||
|
||
lock (_lock)
|
||
{
|
||
config.ConfigId = Guid.NewGuid();
|
||
config.CreatedAtUtc = DateTime.UtcNow;
|
||
config.UpdatedAtUtc = DateTime.UtcNow;
|
||
config.Version = "1.0";
|
||
|
||
var key = $"{config.ConfigType}_{config.ProductTypeCode}_{config.ConfigName}";
|
||
_statisticsConfigs[key] = config;
|
||
}
|
||
|
||
_logger.LogInformation("统计配置创建成功:配置ID={ConfigId},配置名称={ConfigName}",
|
||
config.ConfigId, config.ConfigName);
|
||
|
||
return Result.Success(config);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "创建统计配置失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CREATE_STATISTICS_CONFIG_FAILED", "创建统计配置失败", traceId);
|
||
return Result.FailWithTrace<StatisticsConfig>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result> UpdateStatisticsConfigAsync(StatisticsConfig config, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("更新统计配置:配置ID={ConfigId},配置名称={ConfigName}",
|
||
config.ConfigId, config.ConfigName);
|
||
|
||
lock (_lock)
|
||
{
|
||
var key = $"{config.ConfigType}_{config.ProductTypeCode}_{config.ConfigName}";
|
||
if (_statisticsConfigs.ContainsKey(key))
|
||
{
|
||
config.UpdatedAtUtc = DateTime.UtcNow;
|
||
_statisticsConfigs[key] = config;
|
||
}
|
||
else
|
||
{
|
||
return Result.Fail("CONFIG_NOT_FOUND", $"未找到配置:{config.ConfigId}");
|
||
}
|
||
}
|
||
|
||
_logger.LogInformation("统计配置更新成功:配置ID={ConfigId}", config.ConfigId);
|
||
return Result.Success();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "更新统计配置失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "UPDATE_STATISTICS_CONFIG_FAILED", "更新统计配置失败", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<StatisticsConfig>> GetStatisticsConfigAsync(StatisticsConfigType configType, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取统计配置:配置类型={ConfigType},产品类型={ProductTypeCode}", configType, productTypeCode);
|
||
|
||
var key = $"{configType}_{productTypeCode}";
|
||
if (_statisticsConfigs.TryGetValue(key, out var config))
|
||
{
|
||
return Result.Success(config);
|
||
}
|
||
|
||
// 尝试获取默认配置
|
||
var defaultKey = $"{configType}_default";
|
||
if (_statisticsConfigs.TryGetValue(defaultKey, out var defaultConfig))
|
||
{
|
||
return Result.Success(defaultConfig);
|
||
}
|
||
|
||
return Result.FailWithTrace<StatisticsConfig>("CONFIG_NOT_FOUND", $"未找到配置:{configType}", string.Empty);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取统计配置失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_STATISTICS_CONFIG_FAILED", "获取统计配置失败", traceId);
|
||
return Result.FailWithTrace<StatisticsConfig>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<StatisticsExportResult>> ExportStatisticsAsync(StatisticsExportRequest exportRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("导出统计数据:导出类型={ExportType},格式={ExportFormat},统计类型={StatisticsType}",
|
||
exportRequest.ExportType, exportRequest.ExportFormat, exportRequest.StatisticsType);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var result = await GenerateStatisticsExportAsync(exportRequest, cancellationToken);
|
||
var elapsed = DateTime.UtcNow - startTime;
|
||
|
||
var exportResult = new StatisticsExportResult
|
||
{
|
||
RequestId = exportRequest.RequestId,
|
||
IsSuccess = true,
|
||
FilePath = result.FilePath,
|
||
FileSizeBytes = result.FileSizeBytes,
|
||
RecordCount = result.RecordCount,
|
||
ExportElapsedMs = (long)elapsed.TotalMilliseconds,
|
||
ExportTimeUtc = DateTime.UtcNow,
|
||
ResultDescription = $"导出完成:{result.RecordCount}条记录,文件大小{result.FileSizeBytes}字节",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["export_type"] = exportRequest.ExportType,
|
||
["export_format"] = exportRequest.ExportFormat,
|
||
["statistics_type"] = exportRequest.StatisticsType
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("统计数据导出完成:文件路径={FilePath},记录数量={RecordCount},耗时={ElapsedMs}ms",
|
||
exportResult.FilePath, exportResult.RecordCount, exportResult.ExportElapsedMs);
|
||
|
||
return Result.Success(exportResult);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "导出统计数据失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "EXPORT_STATISTICS_FAILED", "导出统计数据失败", traceId);
|
||
return Result.FailWithTrace<StatisticsExportResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 初始化默认统计配置。
|
||
/// </summary>
|
||
private void InitializeDefaultStatisticsConfigs()
|
||
{
|
||
var defaultConfigs = new List<StatisticsConfig>
|
||
{
|
||
new()
|
||
{
|
||
ConfigId = Guid.NewGuid(),
|
||
ConfigType = StatisticsConfigType.CycleTime,
|
||
ProductTypeCode = "default",
|
||
ConfigName = "默认节拍统计配置",
|
||
ConfigDescription = "系统默认的节拍统计配置",
|
||
Parameters = new Dictionary<string, object>
|
||
{
|
||
["target_cycle_time_seconds"] = 60.0,
|
||
["tolerance_percentage"] = 10.0,
|
||
["enable_trend_analysis"] = true
|
||
},
|
||
IsEnabled = true,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
ConfigId = Guid.NewGuid(),
|
||
ConfigType = StatisticsConfigType.Shift,
|
||
ProductTypeCode = "default",
|
||
ConfigName = "默认班次统计配置",
|
||
ConfigDescription = "系统默认的班次统计配置",
|
||
Parameters = new Dictionary<string, object>
|
||
{
|
||
["shift_duration_hours"] = 8.0,
|
||
["target_production_rate"] = 100,
|
||
["enable_anomaly_detection"] = true
|
||
},
|
||
IsEnabled = true,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
ConfigId = Guid.NewGuid(),
|
||
ConfigType = StatisticsConfigType.Quality,
|
||
ProductTypeCode = "default",
|
||
ConfigName = "默认质量统计配置",
|
||
ConfigDescription = "系统默认的质量统计配置",
|
||
Parameters = new Dictionary<string, object>
|
||
{
|
||
["target_pass_rate"] = 95.0,
|
||
["enable_defect_analysis"] = true,
|
||
["enable_improvement_suggestions"] = true
|
||
},
|
||
IsEnabled = true,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
}
|
||
};
|
||
|
||
foreach (var config in defaultConfigs)
|
||
{
|
||
var key = $"{config.ConfigType}_{config.ProductTypeCode}_{config.ConfigName}";
|
||
_statisticsConfigs[key] = config;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成节拍统计信息。
|
||
/// </summary>
|
||
private async Task<CycleTimeStatistics> GenerateCycleTimeStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
// 简化处理:生成模拟数据
|
||
var random = new Random();
|
||
var totalProcessed = random.Next(100, 500);
|
||
var successCount = (int)(totalProcessed * (0.85 + random.NextDouble() * 0.1));
|
||
var failedCount = totalProcessed - successCount;
|
||
|
||
var cycleTimes = Enumerable.Range(0, totalProcessed)
|
||
.Select(_ => 45 + random.NextDouble() * 30) // 45-75秒
|
||
.ToList();
|
||
|
||
var timeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime };
|
||
|
||
return new CycleTimeStatistics
|
||
{
|
||
TimeRange = timeRange,
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
TotalProcessedCount = totalProcessed,
|
||
SuccessfulProcessedCount = successCount,
|
||
FailedProcessedCount = failedCount,
|
||
AverageCycleTimeSeconds = cycleTimes.Average(),
|
||
MinCycleTimeSeconds = cycleTimes.Min(),
|
||
MaxCycleTimeSeconds = cycleTimes.Max(),
|
||
CycleTimeStandardDeviationSeconds = CalculateStandardDeviation(cycleTimes),
|
||
TargetCycleTimeSeconds = 60.0,
|
||
CountAchievedCycleTime = cycleTimes.Count(t => t <= 60.0),
|
||
ByHour = GenerateHourlyCycleTimeStatistics(startTime, endTime, cycleTimes),
|
||
ByProductType = GenerateProductTypeCycleTimeStatistics(productTypeCode, totalProcessed, successCount, cycleTimes.Average()),
|
||
Distribution = GenerateCycleTimeDistribution(cycleTimes, 60.0),
|
||
Trend = GenerateCycleTimeTrend(cycleTimes),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 50,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成班次统计信息。
|
||
/// </summary>
|
||
private async Task<ShiftStatistics> GenerateShiftStatisticsAsync(DateTime shiftDate, ShiftType? shiftType, string? productTypeCode, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var shiftInfo = GenerateShiftInfo(shiftDate, shiftType);
|
||
var plannedProduction = random.Next(200, 400);
|
||
var actualProduction = (int)(plannedProduction * (0.9 + random.NextDouble() * 0.15));
|
||
var qualifiedCount = (int)(actualProduction * (0.92 + random.NextDouble() * 0.06));
|
||
|
||
return new ShiftStatistics
|
||
{
|
||
ShiftInfo = shiftInfo,
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
PlannedProductionCount = plannedProduction,
|
||
ActualProductionCount = actualProduction,
|
||
QualifiedProductionCount = qualifiedCount,
|
||
UnqualifiedProductionCount = actualProduction - qualifiedCount,
|
||
ShiftStartTimeUtc = shiftDate.Add(shiftInfo.StartTime),
|
||
ShiftEndTimeUtc = shiftDate.Add(shiftInfo.EndTime),
|
||
ActualWorkingHours = shiftInfo.DurationHours * (0.85 + random.NextDouble() * 0.1),
|
||
EquipmentDowntimeHours = shiftInfo.DurationHours * (0.05 + random.NextDouble() * 0.05),
|
||
AverageCycleTimeSeconds = 55 + random.NextDouble() * 10,
|
||
ByHour = GenerateHourlyShiftStatistics(shiftInfo, actualProduction, qualifiedCount),
|
||
ByProductType = GenerateProductTypeShiftStatistics(productTypeCode, actualProduction, qualifiedCount, 60.0),
|
||
AnomalyStatistics = GenerateShiftAnomalyStatistics(random),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 60,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成班次列表。
|
||
/// </summary>
|
||
private async Task<IReadOnlyList<ShiftInfo>> GenerateShiftListAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var shiftList = new List<ShiftInfo>();
|
||
var currentDate = startDate;
|
||
|
||
while (currentDate <= endDate)
|
||
{
|
||
// 生成当天的班次
|
||
var shifts = new[] { ShiftType.Morning, ShiftType.Afternoon, ShiftType.Night };
|
||
|
||
foreach (var shiftType in shifts)
|
||
{
|
||
var shiftInfo = GenerateShiftInfo(currentDate, shiftType);
|
||
shiftList.Add(shiftInfo);
|
||
}
|
||
|
||
currentDate = currentDate.AddDays(1);
|
||
}
|
||
|
||
return shiftList.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成生产效率统计。
|
||
/// </summary>
|
||
private async Task<ProductionEfficiencyStatistics> GenerateProductionEfficiencyStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode, StatisticsGranularity granularity, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var timeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime };
|
||
var plannedHours = (endTime - startTime).TotalHours;
|
||
var actualHours = plannedHours * (0.85 + random.NextDouble() * 0.1);
|
||
var equipmentHours = actualHours * (0.9 + random.NextDouble() * 0.08);
|
||
var plannedProduction = (int)(plannedHours * 50); // 每小时50个
|
||
var actualProduction = (int)(plannedProduction * (0.88 + random.NextDouble() * 0.1));
|
||
var qualifiedCount = (int)(actualProduction * (0.94 + random.NextDouble() * 0.04));
|
||
|
||
return new ProductionEfficiencyStatistics
|
||
{
|
||
TimeRange = timeRange,
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
Granularity = granularity,
|
||
PlannedWorkingHours = plannedHours,
|
||
ActualWorkingHours = actualHours,
|
||
EquipmentRunningHours = equipmentHours,
|
||
PlannedProductionCount = plannedProduction,
|
||
ActualProductionCount = actualProduction,
|
||
QualifiedProductionCount = qualifiedCount,
|
||
ByTimeSlot = GenerateTimeSlotEfficiency(startTime, endTime, actualProduction, qualifiedCount, equipmentHours),
|
||
Trend = GenerateEfficiencyTrend(),
|
||
Benchmark = GenerateEfficiencyBenchmark(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 70,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成质量统计信息。
|
||
/// </summary>
|
||
private async Task<QualityStatistics> GenerateQualityStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var timeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime };
|
||
var totalInspection = random.Next(500, 1000);
|
||
var qualifiedCount = (int)(totalInspection * (0.92 + random.NextDouble() * 0.05));
|
||
var unqualifiedCount = totalInspection - qualifiedCount;
|
||
|
||
return new QualityStatistics
|
||
{
|
||
TimeRange = timeRange,
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
TotalInspectionCount = totalInspection,
|
||
QualifiedCount = qualifiedCount,
|
||
UnqualifiedCount = unqualifiedCount,
|
||
ByDefectType = GenerateDefectStatistics(unqualifiedCount),
|
||
BySeverity = GenerateSeverityStatistics(unqualifiedCount),
|
||
ByProductType = GenerateProductTypeQualityStatistics(productTypeCode, totalInspection, qualifiedCount),
|
||
ByTimeSlot = GenerateQualityTimeSlotStatistics(startTime, endTime, totalInspection, qualifiedCount),
|
||
Trend = GenerateQualityTrend(),
|
||
ImprovementSuggestions = GenerateQualityImprovementSuggestions(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 80,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成设备利用率统计。
|
||
/// </summary>
|
||
private async Task<EquipmentUtilizationStatistics> GenerateEquipmentUtilizationStatisticsAsync(DateTime startTime, DateTime endTime, string? equipmentId, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var timeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime };
|
||
var plannedHours = (endTime - startTime).TotalHours;
|
||
var actualHours = plannedHours * (0.8 + random.NextDouble() * 0.15);
|
||
var productionHours = actualHours * (0.85 + random.NextDouble() * 0.1);
|
||
var maintenanceHours = plannedHours * (0.02 + random.NextDouble() * 0.03);
|
||
var failureHours = plannedHours * (0.01 + random.NextDouble() * 0.02);
|
||
var idleHours = actualHours - productionHours - maintenanceHours - failureHours;
|
||
|
||
return new EquipmentUtilizationStatistics
|
||
{
|
||
TimeRange = timeRange,
|
||
EquipmentId = equipmentId ?? "EQ001",
|
||
EquipmentName = "主检测设备",
|
||
PlannedRunningHours = plannedHours,
|
||
ActualRunningHours = actualHours,
|
||
ProductionHours = productionHours,
|
||
MaintenanceHours = maintenanceHours,
|
||
FailureHours = failureHours,
|
||
IdleHours = idleHours,
|
||
MeanTimeBetweenFailuresHours = 100 + random.NextDouble() * 50,
|
||
MeanTimeToRepairHours = 2 + random.NextDouble() * 3,
|
||
ByDate = GenerateDailyUtilizationStatistics(startTime, endTime, plannedHours, actualHours),
|
||
Trend = GenerateUtilizationTrend(),
|
||
MaintenanceSuggestions = GenerateMaintenanceSuggestions(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 90,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成报警统计摘要。
|
||
/// </summary>
|
||
private async Task<AlarmStatisticsSummary> GenerateAlarmStatisticsSummaryAsync(DateTime startTime, DateTime endTime, AlarmLevel? alarmLevel, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var timeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime };
|
||
var totalAlarms = random.Next(10, 50);
|
||
var activeAlarms = random.Next(0, 5);
|
||
var confirmedAlarms = (int)(totalAlarms * 0.8);
|
||
var clearedAlarms = (int)(totalAlarms * 0.7);
|
||
|
||
var byAlarmLevel = new Dictionary<AlarmLevel, int>
|
||
{
|
||
[AlarmLevel.Info] = random.Next(5, 15),
|
||
[AlarmLevel.Warning] = random.Next(3, 10),
|
||
[AlarmLevel.Error] = random.Next(2, 8),
|
||
[AlarmLevel.Critical] = random.Next(1, 5),
|
||
[AlarmLevel.Fatal] = random.Next(0, 2)
|
||
};
|
||
|
||
var byAlarmType = new Dictionary<AlarmType, int>
|
||
{
|
||
[AlarmType.System] = random.Next(2, 8),
|
||
[AlarmType.Equipment] = random.Next(3, 10),
|
||
[AlarmType.Process] = random.Next(2, 6),
|
||
[AlarmType.Quality] = random.Next(1, 5),
|
||
[AlarmType.Safety] = random.Next(0, 3)
|
||
};
|
||
|
||
return new AlarmStatisticsSummary
|
||
{
|
||
TimeRange = timeRange,
|
||
TotalAlarmCount = totalAlarms,
|
||
ActiveAlarmCount = activeAlarms,
|
||
ConfirmedAlarmCount = confirmedAlarms,
|
||
ClearedAlarmCount = clearedAlarms,
|
||
ByAlarmLevel = byAlarmLevel,
|
||
ByAlarmType = byAlarmType,
|
||
AverageConfirmTimeMinutes = 5 + random.NextDouble() * 10,
|
||
AverageClearTimeMinutes = 10 + random.NextDouble() * 20,
|
||
AlarmFrequencyPerHour = totalAlarms / Math.Max((endTime - startTime).TotalHours, 1),
|
||
Trend = GenerateAlarmTrend(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 100,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成综合生产报告。
|
||
/// </summary>
|
||
private async Task<ComprehensiveProductionReport> GenerateComprehensiveProductionReportAsync(ProductionReportType reportType, DateTime startTime, DateTime endTime, string? productTypeCode, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var timeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime };
|
||
var reportId = Guid.NewGuid();
|
||
|
||
return new ComprehensiveProductionReport
|
||
{
|
||
ReportId = reportId,
|
||
ReportType = reportType,
|
||
TimeRange = timeRange,
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
GeneratedAtUtc = DateTime.UtcNow,
|
||
ExecutiveSummary = GenerateExecutiveSummary(),
|
||
Production = GenerateProductionStatistics(),
|
||
Quality = await GenerateQualityStatisticsAsync(startTime, endTime, productTypeCode, cancellationToken),
|
||
Equipment = GenerateEquipmentStatistics(),
|
||
Alarms = await GenerateAlarmStatisticsSummaryAsync(startTime, endTime, null, cancellationToken),
|
||
Trends = GenerateTrendAnalysis(),
|
||
Recommendations = GenerateImprovementRecommendations(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 200,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成实时统计仪表板。
|
||
/// </summary>
|
||
private async Task<RealTimeStatisticsDashboard> GenerateRealTimeStatisticsDashboardAsync(string? productTypeCode, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var now = DateTime.UtcNow;
|
||
|
||
return new RealTimeStatisticsDashboard
|
||
{
|
||
LastUpdatedUtc = now,
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
CurrentShift = GenerateShiftInfo(now.Date, GetCurrentShiftType(now)),
|
||
Production = GenerateRealTimeProductionMetrics(random),
|
||
Quality = GenerateRealTimeQualityMetrics(random),
|
||
Equipment = GenerateRealTimeEquipmentMetrics(random),
|
||
Alarms = GenerateRealTimeAlarmMetrics(random),
|
||
Efficiency = GenerateRealTimeEfficiencyMetrics(random),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 30,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成历史趋势数据。
|
||
/// </summary>
|
||
private async Task<HistoricalTrendData> GenerateHistoricalTrendDataAsync(MetricType metricType, DateTime startTime, DateTime endTime, string? productTypeCode, StatisticsGranularity granularity, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var timeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime };
|
||
var dataPoints = GenerateTrendDataPoints(metricType, startTime, endTime, granularity, random);
|
||
|
||
return new HistoricalTrendData
|
||
{
|
||
MetricType = metricType,
|
||
Granularity = granularity,
|
||
TimeRange = timeRange,
|
||
DataPoints = dataPoints,
|
||
TrendAnalysis = GenerateTrendAnalysis(),
|
||
Statistics = GenerateTrendStatisticsSummary(dataPoints),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 120,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成统计数据导出。
|
||
/// </summary>
|
||
private async Task<(string FilePath, long FileSizeBytes, int RecordCount)> GenerateStatisticsExportAsync(StatisticsExportRequest exportRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var fileName = $"statistics_export_{exportRequest.RequestId:N}.{exportRequest.ExportFormat.ToString().ToLower()}";
|
||
var filePath = Path.Combine(Path.GetTempPath(), fileName);
|
||
|
||
// 简化处理:生成模拟文件
|
||
var recordCount = 1000;
|
||
var fileContent = GenerateExportFileContent(exportRequest, recordCount);
|
||
await File.WriteAllTextAsync(filePath, fileContent, cancellationToken);
|
||
|
||
var fileInfo = new FileInfo(filePath);
|
||
|
||
return (filePath, fileInfo.Length, recordCount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 计算标准差。
|
||
/// </summary>
|
||
private double CalculateStandardDeviation(IEnumerable<double> values)
|
||
{
|
||
var valuesList = values.ToList();
|
||
var mean = valuesList.Average();
|
||
var variance = valuesList.Sum(x => Math.Pow(x - mean, 2)) / valuesList.Count;
|
||
return Math.Sqrt(variance);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按小时分组的节拍统计。
|
||
/// </summary>
|
||
private Dictionary<int, HourlyCycleTimeStatistics> GenerateHourlyCycleTimeStatistics(DateTime startTime, DateTime endTime, List<double> cycleTimes)
|
||
{
|
||
var hourlyStats = new Dictionary<int, HourlyCycleTimeStatistics>();
|
||
var random = new Random();
|
||
|
||
for (int hour = 0; hour < 24; hour++)
|
||
{
|
||
var count = random.Next(5, 20);
|
||
var successCount = (int)(count * (0.85 + random.NextDouble() * 0.1));
|
||
var avgCycleTime = 50 + random.NextDouble() * 20;
|
||
|
||
hourlyStats[hour] = new HourlyCycleTimeStatistics
|
||
{
|
||
Hour = hour,
|
||
ProcessedCount = count,
|
||
SuccessCount = successCount,
|
||
AverageCycleTimeSeconds = avgCycleTime
|
||
};
|
||
}
|
||
|
||
return hourlyStats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按产品类型分组的节拍统计。
|
||
/// </summary>
|
||
private Dictionary<string, ProductTypeCycleTimeStatistics> GenerateProductTypeCycleTimeStatistics(string? productTypeCode, int totalProcessed, int successCount, double avgCycleTime)
|
||
{
|
||
return new Dictionary<string, ProductTypeCycleTimeStatistics>
|
||
{
|
||
[productTypeCode ?? "default"] = new ProductTypeCycleTimeStatistics
|
||
{
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
ProcessedCount = totalProcessed,
|
||
SuccessCount = successCount,
|
||
AverageCycleTimeSeconds = avgCycleTime
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成节拍时间分布。
|
||
/// </summary>
|
||
private CycleTimeDistribution GenerateCycleTimeDistribution(List<double> cycleTimes, double targetTime)
|
||
{
|
||
var belowTarget = cycleTimes.Count(t => t < targetTime * 0.9);
|
||
var withinRange = cycleTimes.Count(t => t >= targetTime * 0.9 && t <= targetTime * 1.1);
|
||
var aboveRange = cycleTimes.Count - belowTarget - withinRange;
|
||
var total = cycleTimes.Count;
|
||
|
||
return new CycleTimeDistribution
|
||
{
|
||
BelowTargetCount = belowTarget,
|
||
WithinTargetRangeCount = withinRange,
|
||
AboveTargetRangeCount = aboveRange,
|
||
BelowTargetPercentage = (double)belowTarget / total * 100,
|
||
WithinTargetRangePercentage = (double)withinRange / total * 100,
|
||
AboveTargetRangePercentage = (double)aboveRange / total * 100
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成节拍趋势分析。
|
||
/// </summary>
|
||
private CycleTimeTrend GenerateCycleTimeTrend(List<double> cycleTimes)
|
||
{
|
||
var random = new Random();
|
||
var direction = random.NextDouble() > 0.5 ? TrendDirection.Increasing : TrendDirection.Decreasing;
|
||
var changeRate = random.NextDouble() * 10 - 5; // -5% to +5%
|
||
|
||
return new CycleTimeTrend
|
||
{
|
||
Direction = direction,
|
||
ChangeRatePercentage = changeRate,
|
||
TrendDescription = $"节拍时间呈{direction}趋势,变化率{changeRate:F2}%",
|
||
PredictedNextCycleTimeSeconds = cycleTimes.Average() * (1 + changeRate / 100),
|
||
Confidence = 0.7 + random.NextDouble() * 0.2
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成班次信息。
|
||
/// </summary>
|
||
private ShiftInfo GenerateShiftInfo(DateTime shiftDate, ShiftType? shiftType)
|
||
{
|
||
var type = shiftType ?? ShiftType.Morning;
|
||
var (startTime, endTime) = type switch
|
||
{
|
||
ShiftType.Morning => (new TimeSpan(8, 0, 0), new TimeSpan(16, 0, 0)),
|
||
ShiftType.Afternoon => (new TimeSpan(16, 0, 0), new TimeSpan(0, 0, 0)),
|
||
ShiftType.Night => (new TimeSpan(0, 0, 0), new TimeSpan(8, 0, 0)),
|
||
_ => (new TimeSpan(8, 0, 0), new TimeSpan(16, 0, 0))
|
||
};
|
||
|
||
return new ShiftInfo
|
||
{
|
||
ShiftId = Guid.NewGuid(),
|
||
ShiftDate = shiftDate,
|
||
ShiftType = type,
|
||
ShiftName = $"{type}班",
|
||
StartTime = startTime,
|
||
EndTime = endTime,
|
||
Operators = new[] { "操作员A", "操作员B" },
|
||
Supervisor = "主管A"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按小时分组的班次统计。
|
||
/// </summary>
|
||
private Dictionary<int, HourlyShiftStatistics> GenerateHourlyShiftStatistics(ShiftInfo shiftInfo, int actualProduction, int qualifiedCount)
|
||
{
|
||
var hourlyStats = new Dictionary<int, HourlyShiftStatistics>();
|
||
var random = new Random();
|
||
var startHour = (int)shiftInfo.StartTime.TotalHours;
|
||
var endHour = (int)shiftInfo.EndTime.TotalHours;
|
||
|
||
for (int hour = startHour; hour < endHour; hour++)
|
||
{
|
||
var hourProduction = random.Next(5, 15);
|
||
var hourQualified = (int)(hourProduction * (0.9 + random.NextDouble() * 0.08));
|
||
|
||
hourlyStats[hour] = new HourlyShiftStatistics
|
||
{
|
||
Hour = hour,
|
||
ProductionCount = hourProduction,
|
||
QualifiedCount = hourQualified,
|
||
EquipmentRunningMinutes = 50 + random.NextDouble() * 10,
|
||
EquipmentDowntimeMinutes = random.NextDouble() * 5
|
||
};
|
||
}
|
||
|
||
return hourlyStats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按产品类型分组的班次统计。
|
||
/// </summary>
|
||
private Dictionary<string, ProductTypeShiftStatistics> GenerateProductTypeShiftStatistics(string? productTypeCode, int actualProduction, int qualifiedCount, double avgCycleTime)
|
||
{
|
||
return new Dictionary<string, ProductTypeShiftStatistics>
|
||
{
|
||
[productTypeCode ?? "default"] = new ProductTypeShiftStatistics
|
||
{
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
ProductionCount = actualProduction,
|
||
QualifiedCount = qualifiedCount,
|
||
AverageCycleTimeSeconds = avgCycleTime
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成班次异常统计。
|
||
/// </summary>
|
||
private ShiftAnomalyStatistics GenerateShiftAnomalyStatistics(Random random)
|
||
{
|
||
var totalAnomalies = random.Next(0, 10);
|
||
|
||
return new ShiftAnomalyStatistics
|
||
{
|
||
TotalAnomalyCount = totalAnomalies,
|
||
EquipmentFailureCount = random.Next(0, totalAnomalies / 2),
|
||
QualityAnomalyCount = random.Next(0, totalAnomalies / 2),
|
||
ProcessAnomalyCount = random.Next(0, totalAnomalies / 2),
|
||
DowntimeMinutes = random.NextDouble() * 30,
|
||
AnomalyRate = totalAnomalies > 0 ? random.NextDouble() * 5 : 0.0
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成时间段效率。
|
||
/// </summary>
|
||
private Dictionary<DateTime, TimeSlotEfficiency> GenerateTimeSlotEfficiency(DateTime startTime, DateTime endTime, int actualProduction, int qualifiedCount, double equipmentHours)
|
||
{
|
||
var timeSlotEfficiency = new Dictionary<DateTime, TimeSlotEfficiency>();
|
||
var random = new Random();
|
||
var current = startTime;
|
||
|
||
while (current < endTime)
|
||
{
|
||
var slotProduction = random.Next(5, 20);
|
||
var slotQualified = (int)(slotProduction * (0.9 + random.NextDouble() * 0.08));
|
||
var slotEfficiency = 0.7 + random.NextDouble() * 0.2;
|
||
|
||
timeSlotEfficiency[current] = new TimeSlotEfficiency
|
||
{
|
||
TimeSlot = current,
|
||
ProductionCount = slotProduction,
|
||
QualifiedCount = slotQualified,
|
||
EquipmentRunningMinutes = 50 + random.NextDouble() * 10,
|
||
OverallEfficiency = slotEfficiency * 100
|
||
};
|
||
|
||
current = current.AddHours(1);
|
||
}
|
||
|
||
return timeSlotEfficiency;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成效率趋势分析。
|
||
/// </summary>
|
||
private EfficiencyTrend GenerateEfficiencyTrend()
|
||
{
|
||
var random = new Random();
|
||
var direction = random.NextDouble() > 0.5 ? TrendDirection.Increasing : TrendDirection.Decreasing;
|
||
var changeRate = random.NextDouble() * 8 - 4; // -4% to +4%
|
||
|
||
return new EfficiencyTrend
|
||
{
|
||
Direction = direction,
|
||
ChangeRatePercentage = changeRate,
|
||
TrendDescription = $"效率呈{direction}趋势,变化率{changeRate:F2}%",
|
||
PredictedNextEfficiency = 75 + random.NextDouble() * 15,
|
||
Confidence = 0.6 + random.NextDouble() * 0.3
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成效率基准对比。
|
||
/// </summary>
|
||
private EfficiencyBenchmark GenerateEfficiencyBenchmark()
|
||
{
|
||
var random = new Random();
|
||
|
||
return new EfficiencyBenchmark
|
||
{
|
||
HistoricalBestEfficiency = 85 + random.NextDouble() * 10,
|
||
HistoricalAverageEfficiency = 75 + random.NextDouble() * 8,
|
||
IndustryBenchmarkEfficiency = 80 + random.NextDouble() * 10,
|
||
TargetEfficiency = 82 + random.NextDouble() * 8
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成缺陷统计。
|
||
/// </summary>
|
||
private Dictionary<DefectType, DefectStatistics> GenerateDefectStatistics(int totalDefects)
|
||
{
|
||
var random = new Random();
|
||
var defectStats = new Dictionary<DefectType, DefectStatistics>();
|
||
|
||
var defectTypes = new[] { DefectType.Appearance, DefectType.Dimension, DefectType.Function, DefectType.Material, DefectType.Process };
|
||
var remainingDefects = totalDefects;
|
||
|
||
foreach (var defectType in defectTypes)
|
||
{
|
||
if (remainingDefects <= 0) break;
|
||
|
||
var count = random.Next(0, remainingDefects / 2 + 1);
|
||
remainingDefects -= count;
|
||
|
||
defectStats[defectType] = new DefectStatistics
|
||
{
|
||
DefectType = defectType,
|
||
DefectCount = count,
|
||
Percentage = totalDefects > 0 ? (double)count / totalDefects * 100 : 0,
|
||
AverageSeverity = 1 + random.NextDouble() * 2
|
||
};
|
||
}
|
||
|
||
return defectStats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成严重程度统计。
|
||
/// </summary>
|
||
private Dictionary<DefectSeverity, SeverityStatistics> GenerateSeverityStatistics(int totalDefects)
|
||
{
|
||
var random = new Random();
|
||
var severityStats = new Dictionary<DefectSeverity, SeverityStatistics>();
|
||
|
||
var severities = new[] { DefectSeverity.Minor, DefectSeverity.Normal, DefectSeverity.Serious, DefectSeverity.Critical };
|
||
var remainingDefects = totalDefects;
|
||
|
||
foreach (var severity in severities)
|
||
{
|
||
if (remainingDefects <= 0) break;
|
||
|
||
var count = random.Next(0, remainingDefects / 2 + 1);
|
||
remainingDefects -= count;
|
||
|
||
severityStats[severity] = new SeverityStatistics
|
||
{
|
||
Severity = severity,
|
||
DefectCount = count,
|
||
Percentage = totalDefects > 0 ? (double)count / totalDefects * 100 : 0
|
||
};
|
||
}
|
||
|
||
return severityStats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按产品类型分组的质量统计。
|
||
/// </summary>
|
||
private Dictionary<string, ProductTypeQualityStatistics> GenerateProductTypeQualityStatistics(string? productTypeCode, int totalInspection, int qualifiedCount)
|
||
{
|
||
return new Dictionary<string, ProductTypeQualityStatistics>
|
||
{
|
||
[productTypeCode ?? "default"] = new ProductTypeQualityStatistics
|
||
{
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
InspectionCount = totalInspection,
|
||
QualifiedCount = qualifiedCount
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按检测时间的质量统计。
|
||
/// </summary>
|
||
private Dictionary<DateTime, QualityTimeSlotStatistics> GenerateQualityTimeSlotStatistics(DateTime startTime, DateTime endTime, int totalInspection, int qualifiedCount)
|
||
{
|
||
var timeSlotStats = new Dictionary<DateTime, QualityTimeSlotStatistics>();
|
||
var random = new Random();
|
||
var current = startTime;
|
||
|
||
while (current < endTime)
|
||
{
|
||
var slotInspection = random.Next(10, 30);
|
||
var slotQualified = (int)(slotInspection * (0.9 + random.NextDouble() * 0.08));
|
||
|
||
timeSlotStats[current] = new QualityTimeSlotStatistics
|
||
{
|
||
InspectionTime = current,
|
||
InspectionCount = slotInspection,
|
||
QualifiedCount = slotQualified
|
||
};
|
||
|
||
current = current.AddHours(2);
|
||
}
|
||
|
||
return timeSlotStats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成质量趋势分析。
|
||
/// </summary>
|
||
private QualityTrend GenerateQualityTrend()
|
||
{
|
||
var random = new Random();
|
||
var direction = random.NextDouble() > 0.3 ? TrendDirection.Stable : (random.NextDouble() > 0.5 ? TrendDirection.Increasing : TrendDirection.Decreasing);
|
||
var changeRate = random.NextDouble() * 6 - 3; // -3% to +3%
|
||
|
||
return new QualityTrend
|
||
{
|
||
Direction = direction,
|
||
ChangeRatePercentage = changeRate,
|
||
TrendDescription = $"质量呈{direction}趋势,变化率{changeRate:F2}%",
|
||
PredictedNextPassRate = 92 + random.NextDouble() * 6,
|
||
Confidence = 0.7 + random.NextDouble() * 0.2
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成质量改进建议。
|
||
/// </summary>
|
||
private IReadOnlyList<QualityImprovementSuggestion> GenerateQualityImprovementSuggestions()
|
||
{
|
||
var random = new Random();
|
||
var suggestions = new List<QualityImprovementSuggestion>();
|
||
|
||
var suggestionTypes = new[] { SuggestionType.ProcessImprovement, SuggestionType.EquipmentMaintenance, SuggestionType.OperatorTraining };
|
||
var difficulties = new[] { ImplementationDifficulty.Easy, ImplementationDifficulty.Medium, ImplementationDifficulty.Difficult };
|
||
|
||
foreach (var type in suggestionTypes.Take(2))
|
||
{
|
||
suggestions.Add(new QualityImprovementSuggestion
|
||
{
|
||
SuggestionId = Guid.NewGuid(),
|
||
SuggestionType = type,
|
||
Title = $"{type}建议",
|
||
Description = $"通过{type}提升产品质量",
|
||
Priority = random.Next(1, 5),
|
||
ExpectedImpact = "预计提升合格率2-5%",
|
||
Difficulty = difficulties[random.Next(difficulties.Length)]
|
||
});
|
||
}
|
||
|
||
return suggestions.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按日期分组的利用率统计。
|
||
/// </summary>
|
||
private Dictionary<DateTime, DailyUtilizationStatistics> GenerateDailyUtilizationStatistics(DateTime startTime, DateTime endTime, double plannedHours, double actualHours)
|
||
{
|
||
var dailyStats = new Dictionary<DateTime, DailyUtilizationStatistics>();
|
||
var random = new Random();
|
||
var current = startTime.Date;
|
||
|
||
while (current <= endTime.Date)
|
||
{
|
||
dailyStats[current] = new DailyUtilizationStatistics
|
||
{
|
||
Date = current,
|
||
PlannedRunningHours = 24,
|
||
ActualRunningHours = 20 + random.NextDouble() * 4
|
||
};
|
||
|
||
current = current.AddDays(1);
|
||
}
|
||
|
||
return dailyStats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成利用率趋势分析。
|
||
/// </summary>
|
||
private UtilizationTrend GenerateUtilizationTrend()
|
||
{
|
||
var random = new Random();
|
||
var direction = random.NextDouble() > 0.4 ? TrendDirection.Stable : TrendDirection.Fluctuating;
|
||
var changeRate = random.NextDouble() * 4 - 2; // -2% to +2%
|
||
|
||
return new UtilizationTrend
|
||
{
|
||
Direction = direction,
|
||
ChangeRatePercentage = changeRate,
|
||
TrendDescription = $"设备利用率呈{direction}趋势,变化率{changeRate:F2}%",
|
||
PredictedNextUtilization = 80 + random.NextDouble() * 15,
|
||
Confidence = 0.6 + random.NextDouble() * 0.3
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成维护建议。
|
||
/// </summary>
|
||
private IReadOnlyList<MaintenanceSuggestion> GenerateMaintenanceSuggestions()
|
||
{
|
||
var random = new Random();
|
||
var suggestions = new List<MaintenanceSuggestion>();
|
||
|
||
var maintenanceTypes = new[] { MaintenanceType.Preventive, MaintenanceType.Predictive, MaintenanceType.Corrective };
|
||
|
||
foreach (var type in maintenanceTypes.Take(2))
|
||
{
|
||
suggestions.Add(new MaintenanceSuggestion
|
||
{
|
||
SuggestionId = Guid.NewGuid(),
|
||
MaintenanceType = type,
|
||
Title = $"{type}维护建议",
|
||
Description = $"建议进行{type}维护",
|
||
Priority = random.Next(1, 5),
|
||
EstimatedMaintenanceHours = 2 + random.NextDouble() * 4,
|
||
EstimatedCost = 1000 + random.NextDouble() * 3000,
|
||
SuggestedMaintenanceTime = DateTime.UtcNow.AddDays(random.Next(1, 7))
|
||
});
|
||
}
|
||
|
||
return suggestions.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成报警趋势分析。
|
||
/// </summary>
|
||
private AlarmTrend GenerateAlarmTrend()
|
||
{
|
||
var random = new Random();
|
||
var direction = random.NextDouble() > 0.6 ? TrendDirection.Stable : (random.NextDouble() > 0.5 ? TrendDirection.Increasing : TrendDirection.Decreasing);
|
||
var changeRate = random.NextDouble() * 15 - 7.5; // -7.5% to +7.5%
|
||
|
||
return new AlarmTrend
|
||
{
|
||
Direction = direction,
|
||
ChangeRatePercentage = changeRate,
|
||
TrendDescription = $"报警数量呈{direction}趋势,变化率{changeRate:F2}%",
|
||
PredictedNextAlarmCount = random.Next(5, 25),
|
||
Confidence = 0.5 + random.NextDouble() * 0.4
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成执行摘要。
|
||
/// </summary>
|
||
private ExecutiveSummary GenerateExecutiveSummary()
|
||
{
|
||
var random = new Random();
|
||
|
||
return new ExecutiveSummary
|
||
{
|
||
OverallScore = 75 + random.NextDouble() * 15,
|
||
KeyMetrics = new Dictionary<string, double>
|
||
{
|
||
["生产完成率"] = 85 + random.NextDouble() * 10,
|
||
["质量合格率"] = 92 + random.NextDouble() * 6,
|
||
["设备利用率"] = 80 + random.NextDouble() * 15,
|
||
["OEE"] = 70 + random.NextDouble() * 20
|
||
},
|
||
Achievements = new[] { "生产效率提升5%", "质量指标达标" },
|
||
Issues = new[] { "设备故障率偏高", "人员操作需优化" },
|
||
SummaryDescription = "整体运行状况良好,部分指标需持续改进"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成生产统计。
|
||
/// </summary>
|
||
private ProductionStatistics GenerateProductionStatistics()
|
||
{
|
||
var random = new Random();
|
||
|
||
return new ProductionStatistics
|
||
{
|
||
PlannedProduction = 1000,
|
||
ActualProduction = (int)(1000 * (0.88 + random.NextDouble() * 0.1)),
|
||
QualifiedProduction = (int)(950 * (0.92 + random.NextDouble() * 0.06)),
|
||
AverageCycleTimeSeconds = 55 + random.NextDouble() * 10
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成设备统计。
|
||
/// </summary>
|
||
private EquipmentStatistics GenerateEquipmentStatistics()
|
||
{
|
||
var random = new Random();
|
||
|
||
return new EquipmentStatistics
|
||
{
|
||
EquipmentCount = 5,
|
||
AverageUtilizationRate = 75 + random.NextDouble() * 15,
|
||
AverageAvailabilityRate = 85 + random.NextDouble() * 10,
|
||
TotalDowntimeHours = 2 + random.NextDouble() * 3
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成趋势分析。
|
||
/// </summary>
|
||
private TrendAnalysis GenerateTrendAnalysis()
|
||
{
|
||
var random = new Random();
|
||
|
||
return new TrendAnalysis
|
||
{
|
||
ProductionTrend = TrendDirection.Stable,
|
||
QualityTrend = TrendDirection.Increasing,
|
||
EquipmentTrend = TrendDirection.Fluctuating,
|
||
AlarmTrend = TrendDirection.Decreasing,
|
||
TrendDescription = "生产稳定,质量提升,设备需关注"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成改进建议。
|
||
/// </summary>
|
||
private IReadOnlyList<ImprovementRecommendation> GenerateImprovementRecommendations()
|
||
{
|
||
var random = new Random();
|
||
var recommendations = new List<ImprovementRecommendation>();
|
||
|
||
var categories = new[] { RecommendationCategory.ProductionOptimization, RecommendationCategory.QualityImprovement, RecommendationCategory.EquipmentMaintenance };
|
||
|
||
foreach (var category in categories.Take(3))
|
||
{
|
||
recommendations.Add(new ImprovementRecommendation
|
||
{
|
||
RecommendationId = Guid.NewGuid(),
|
||
Category = category,
|
||
Title = $"{category}建议",
|
||
Description = $"建议进行{category}改进",
|
||
Priority = random.Next(1, 5),
|
||
ExpectedBenefit = "预计提升效率3-8%",
|
||
ImplementationCost = 5000 + random.NextDouble() * 10000,
|
||
ReturnOnInvestment = 150 + random.NextDouble() * 100
|
||
});
|
||
}
|
||
|
||
return recommendations.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成实时生产指标。
|
||
/// </summary>
|
||
private RealTimeProductionMetrics GenerateRealTimeProductionMetrics(Random random)
|
||
{
|
||
return new RealTimeProductionMetrics
|
||
{
|
||
CurrentHourProduction = random.Next(8, 15),
|
||
CurrentShiftProduction = random.Next(80, 120),
|
||
TodayProduction = random.Next(200, 300),
|
||
CurrentCycleTimeSeconds = 50 + random.NextDouble() * 15,
|
||
CycleTimeAchievementRate = 85 + random.NextDouble() * 10,
|
||
ProductionSpeedTrend = TrendDirection.Stable
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成实时质量指标。
|
||
/// </summary>
|
||
private RealTimeQualityMetrics GenerateRealTimeQualityMetrics(Random random)
|
||
{
|
||
return new RealTimeQualityMetrics
|
||
{
|
||
CurrentHourPassRate = 92 + random.NextDouble() * 6,
|
||
CurrentShiftPassRate = 93 + random.NextDouble() * 5,
|
||
TodayPassRate = 94 + random.NextDouble() * 4,
|
||
RecentDefectCount = random.Next(0, 5),
|
||
QualityTrend = TrendDirection.Stable
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成实时设备指标。
|
||
/// </summary>
|
||
private RealTimeEquipmentMetrics GenerateRealTimeEquipmentMetrics(Random random)
|
||
{
|
||
return new RealTimeEquipmentMetrics
|
||
{
|
||
RunningEquipmentCount = 4,
|
||
DownEquipmentCount = 1,
|
||
CurrentUtilizationRate = 78 + random.NextDouble() * 12,
|
||
CurrentAvailabilityRate = 88 + random.NextDouble() * 8,
|
||
EquipmentStatusTrend = TrendDirection.Stable
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成实时报警指标。
|
||
/// </summary>
|
||
private RealTimeAlarmMetrics GenerateRealTimeAlarmMetrics(Random random)
|
||
{
|
||
return new RealTimeAlarmMetrics
|
||
{
|
||
ActiveAlarmCount = random.Next(0, 3),
|
||
TodayNewAlarmCount = random.Next(5, 15),
|
||
CriticalAlarmCount = random.Next(0, 2),
|
||
LastAlarmTime = random.NextDouble() > 0.3 ? DateTime.UtcNow.AddMinutes(-random.Next(10, 120)) : null,
|
||
AlarmTrend = TrendDirection.Stable
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成实时效率指标。
|
||
/// </summary>
|
||
private RealTimeEfficiencyMetrics GenerateRealTimeEfficiencyMetrics(Random random)
|
||
{
|
||
return new RealTimeEfficiencyMetrics
|
||
{
|
||
CurrentOEE = 72 + random.NextDouble() * 16,
|
||
TimeUtilizationRate = 85 + random.NextDouble() * 10,
|
||
EquipmentUtilizationRate = 80 + random.NextDouble() * 15,
|
||
PerformanceEfficiency = 88 + random.NextDouble() * 8,
|
||
EfficiencyTrend = TrendDirection.Stable
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成趋势数据点。
|
||
/// </summary>
|
||
private IReadOnlyList<TrendDataPoint> GenerateTrendDataPoints(MetricType metricType, DateTime startTime, DateTime endTime, StatisticsGranularity granularity, Random random)
|
||
{
|
||
var dataPoints = new List<TrendDataPoint>();
|
||
var current = startTime;
|
||
var interval = granularity switch
|
||
{
|
||
StatisticsGranularity.Minutely => TimeSpan.FromMinutes(1),
|
||
StatisticsGranularity.Hourly => TimeSpan.FromHours(1),
|
||
StatisticsGranularity.Daily => TimeSpan.FromDays(1),
|
||
_ => TimeSpan.FromHours(1)
|
||
};
|
||
|
||
while (current <= endTime)
|
||
{
|
||
var value = GetMetricValue(metricType, random);
|
||
|
||
dataPoints.Add(new TrendDataPoint
|
||
{
|
||
Timestamp = current,
|
||
Value = value,
|
||
Unit = GetMetricUnit(metricType)
|
||
});
|
||
|
||
current = current.Add(interval);
|
||
}
|
||
|
||
return dataPoints.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指标值。
|
||
/// </summary>
|
||
private double GetMetricValue(MetricType metricType, Random random)
|
||
{
|
||
return metricType switch
|
||
{
|
||
MetricType.Production => 50 + random.NextDouble() * 30,
|
||
MetricType.Quality => 90 + random.NextDouble() * 8,
|
||
MetricType.EquipmentUtilization => 75 + random.NextDouble() * 20,
|
||
MetricType.CycleTime => 45 + random.NextDouble() * 25,
|
||
MetricType.OEE => 70 + random.NextDouble() * 20,
|
||
MetricType.AlarmCount => random.NextDouble() * 10,
|
||
MetricType.Downtime => random.NextDouble() * 5,
|
||
MetricType.Efficiency => 80 + random.NextDouble() * 15,
|
||
_ => random.NextDouble() * 100
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指标单位。
|
||
/// </summary>
|
||
private string GetMetricUnit(MetricType metricType)
|
||
{
|
||
return metricType switch
|
||
{
|
||
MetricType.Production => "个",
|
||
MetricType.Quality => "%",
|
||
MetricType.EquipmentUtilization => "%",
|
||
MetricType.CycleTime => "秒",
|
||
MetricType.OEE => "%",
|
||
MetricType.AlarmCount => "个",
|
||
MetricType.Downtime => "小时",
|
||
MetricType.Efficiency => "%",
|
||
_ => ""
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成趋势统计摘要。
|
||
/// </summary>
|
||
private TrendStatisticsSummary GenerateTrendStatisticsSummary(IReadOnlyList<TrendDataPoint> dataPoints)
|
||
{
|
||
if (!dataPoints.Any())
|
||
{
|
||
return new TrendStatisticsSummary();
|
||
}
|
||
|
||
var values = dataPoints.Select(dp => dp.Value).ToList();
|
||
var sortedValues = values.OrderBy(v => v).ToList();
|
||
|
||
return new TrendStatisticsSummary
|
||
{
|
||
DataPointCount = dataPoints.Count,
|
||
MinValue = sortedValues.First(),
|
||
MaxValue = sortedValues.Last(),
|
||
AverageValue = values.Average(),
|
||
MedianValue = sortedValues[sortedValues.Count / 2],
|
||
StandardDeviation = CalculateStandardDeviation(values)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成导出文件内容。
|
||
/// </summary>
|
||
private string GenerateExportFileContent(StatisticsExportRequest exportRequest, int recordCount)
|
||
{
|
||
var random = new Random();
|
||
var content = new System.Text.StringBuilder();
|
||
|
||
// 简化处理:生成CSV格式内容
|
||
content.AppendLine("ID,时间,数值,单位");
|
||
|
||
for (int i = 0; i < recordCount; i++)
|
||
{
|
||
var timestamp = DateTime.UtcNow.AddMinutes(-random.Next(0, 1440));
|
||
var value = random.NextDouble() * 100;
|
||
var unit = "个";
|
||
|
||
content.AppendLine($"{i + 1},{timestamp:yyyy-MM-dd HH:mm:ss},{value:F2},{unit}");
|
||
}
|
||
|
||
return content.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前班次类型。
|
||
/// </summary>
|
||
private ShiftType GetCurrentShiftType(DateTime dateTime)
|
||
{
|
||
var hour = dateTime.Hour;
|
||
|
||
return hour switch
|
||
{
|
||
>= 8 and < 16 => ShiftType.Morning,
|
||
>= 16 and < 24 => ShiftType.Afternoon,
|
||
_ => ShiftType.Night
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 统计服务实现(最小可编译版本)。
|
||
/// </summary>
|
||
public sealed class StatisticsService : IStatisticsService
|
||
{
|
||
private readonly ILogger<StatisticsService> _logger;
|
||
|
||
public StatisticsService(ILogger<StatisticsService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
}
|
||
|
||
public Task<Result<CycleTimeStatistics>> GetCycleTimeStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var data = new CycleTimeStatistics
|
||
{
|
||
TimeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime },
|
||
ProductTypeCode = productTypeCode ?? string.Empty,
|
||
TotalProcessedCount = 0,
|
||
SuccessfulProcessedCount = 0,
|
||
FailedProcessedCount = 0,
|
||
AverageCycleTimeSeconds = 0,
|
||
MinCycleTimeSeconds = 0,
|
||
MaxCycleTimeSeconds = 0,
|
||
CycleTimeStandardDeviationSeconds = 0,
|
||
TargetCycleTimeSeconds = 0,
|
||
CountAchievedCycleTime = 0
|
||
};
|
||
|
||
return Task.FromResult(Result<CycleTimeStatistics>.Success(data));
|
||
}
|
||
|
||
public Task<Result<ShiftStatistics>> GetShiftStatisticsAsync(DateTime shiftDate, ShiftType? shiftType = null, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var actualShiftType = shiftType ?? ShiftType.Morning;
|
||
var data = new ShiftStatistics
|
||
{
|
||
ShiftInfo = new ShiftInfo
|
||
{
|
||
ShiftId = Guid.NewGuid(),
|
||
ShiftDate = shiftDate.Date,
|
||
ShiftType = actualShiftType,
|
||
ShiftName = actualShiftType.ToString(),
|
||
StartTime = TimeSpan.Zero,
|
||
EndTime = TimeSpan.FromHours(8)
|
||
},
|
||
ProductTypeCode = productTypeCode ?? string.Empty,
|
||
PlannedProductionCount = 0,
|
||
ActualProductionCount = 0,
|
||
QualifiedProductionCount = 0,
|
||
UnqualifiedProductionCount = 0,
|
||
ShiftStartTimeUtc = shiftDate,
|
||
ShiftEndTimeUtc = shiftDate.AddHours(8),
|
||
ActualWorkingHours = 0,
|
||
EquipmentDowntimeHours = 0,
|
||
AverageCycleTimeSeconds = 0
|
||
};
|
||
|
||
return Task.FromResult(Result<ShiftStatistics>.Success(data));
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<ShiftInfo>>> GetShiftListAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||
{
|
||
IReadOnlyList<ShiftInfo> data = Array.Empty<ShiftInfo>();
|
||
return Task.FromResult(Result<IReadOnlyList<ShiftInfo>>.Success(data));
|
||
}
|
||
|
||
public Task<Result<ProductionEfficiencyStatistics>> GetProductionEfficiencyStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, StatisticsGranularity granularity = StatisticsGranularity.Hourly, CancellationToken cancellationToken = default)
|
||
{
|
||
var data = new ProductionEfficiencyStatistics
|
||
{
|
||
TimeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime },
|
||
ProductTypeCode = productTypeCode ?? string.Empty,
|
||
Granularity = granularity,
|
||
PlannedWorkingHours = 0,
|
||
ActualWorkingHours = 0,
|
||
EquipmentRunningHours = 0,
|
||
PlannedProductionCount = 0,
|
||
ActualProductionCount = 0,
|
||
QualifiedProductionCount = 0
|
||
};
|
||
|
||
return Task.FromResult(Result<ProductionEfficiencyStatistics>.Success(data));
|
||
}
|
||
|
||
public Task<Result<QualityStatistics>> GetQualityStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var data = new QualityStatistics
|
||
{
|
||
TimeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime },
|
||
ProductTypeCode = productTypeCode ?? string.Empty,
|
||
TotalInspectionCount = 0,
|
||
QualifiedCount = 0,
|
||
UnqualifiedCount = 0
|
||
};
|
||
|
||
return Task.FromResult(Result<QualityStatistics>.Success(data));
|
||
}
|
||
|
||
public Task<Result<EquipmentUtilizationStatistics>> GetEquipmentUtilizationStatisticsAsync(DateTime startTime, DateTime endTime, string? equipmentId = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var data = new EquipmentUtilizationStatistics
|
||
{
|
||
TimeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime },
|
||
EquipmentId = equipmentId ?? string.Empty,
|
||
PlannedRunningHours = 0,
|
||
ActualRunningHours = 0,
|
||
ProductionHours = 0,
|
||
MaintenanceHours = 0,
|
||
FailureHours = 0,
|
||
IdleHours = 0
|
||
};
|
||
|
||
return Task.FromResult(Result<EquipmentUtilizationStatistics>.Success(data));
|
||
}
|
||
|
||
public Task<Result<AlarmStatisticsSummary>> GetAlarmStatisticsSummaryAsync(DateTime startTime, DateTime endTime, OrpaonVision.Core.Common.AlarmLevel? alarmLevel = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var data = new AlarmStatisticsSummary
|
||
{
|
||
TimeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime },
|
||
TotalAlarmCount = 0,
|
||
ActiveAlarmCount = 0,
|
||
ConfirmedAlarmCount = 0,
|
||
ClearedAlarmCount = 0
|
||
};
|
||
|
||
return Task.FromResult(Result<AlarmStatisticsSummary>.Success(data));
|
||
}
|
||
|
||
public Task<Result<ComprehensiveProductionReport>> GetComprehensiveProductionReportAsync(OrpaonVision.Core.Statistics.ProductionReportType reportType, DateTime startTime, DateTime endTime, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var data = new ComprehensiveProductionReport
|
||
{
|
||
ReportId = Guid.NewGuid(),
|
||
ReportType = reportType,
|
||
TimeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime },
|
||
ProductTypeCode = productTypeCode ?? string.Empty,
|
||
GeneratedAtUtc = DateTime.UtcNow
|
||
};
|
||
|
||
return Task.FromResult(Result<ComprehensiveProductionReport>.Success(data));
|
||
}
|
||
|
||
public Task<Result<RealTimeStatisticsDashboard>> GetRealTimeStatisticsDashboardAsync(string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var data = new RealTimeStatisticsDashboard
|
||
{
|
||
ProductTypeCode = productTypeCode ?? string.Empty,
|
||
LastUpdatedUtc = DateTime.UtcNow
|
||
};
|
||
|
||
return Task.FromResult(Result<RealTimeStatisticsDashboard>.Success(data));
|
||
}
|
||
|
||
public Task<Result<HistoricalTrendData>> GetHistoricalTrendDataAsync(MetricType metricType, DateTime startTime, DateTime endTime, string? productTypeCode = null, StatisticsGranularity granularity = StatisticsGranularity.Hourly, CancellationToken cancellationToken = default)
|
||
{
|
||
var data = new HistoricalTrendData
|
||
{
|
||
MetricType = metricType,
|
||
TimeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime },
|
||
Granularity = granularity
|
||
};
|
||
|
||
return Task.FromResult(Result<HistoricalTrendData>.Success(data));
|
||
}
|
||
|
||
public Task<Result<StatisticsConfig>> CreateStatisticsConfigAsync(StatisticsConfig config, CancellationToken cancellationToken = default)
|
||
{
|
||
return Task.FromResult(Result<StatisticsConfig>.Success(config));
|
||
}
|
||
|
||
public Task<Result> UpdateStatisticsConfigAsync(StatisticsConfig config, CancellationToken cancellationToken = default)
|
||
{
|
||
return Task.FromResult(Result.Success());
|
||
}
|
||
|
||
public Task<Result<StatisticsConfig>> GetStatisticsConfigAsync(StatisticsConfigType configType, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var config = new StatisticsConfig
|
||
{
|
||
ConfigId = Guid.NewGuid(),
|
||
ConfigType = configType,
|
||
ProductTypeCode = productTypeCode ?? string.Empty,
|
||
ConfigName = $"{configType}-default",
|
||
IsEnabled = true,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
};
|
||
|
||
return Task.FromResult(Result<StatisticsConfig>.Success(config));
|
||
}
|
||
|
||
public Task<Result<StatisticsExportResult>> ExportStatisticsAsync(StatisticsExportRequest exportRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
_logger.LogInformation("统计导出请求: {RequestId}", exportRequest.RequestId);
|
||
|
||
var result = new StatisticsExportResult
|
||
{
|
||
RequestId = exportRequest.RequestId,
|
||
IsSuccess = true,
|
||
FilePath = string.Empty,
|
||
FileSizeBytes = 0,
|
||
RecordCount = 0,
|
||
ExportElapsedMs = 0,
|
||
ExportTimeUtc = DateTime.UtcNow,
|
||
ResultDescription = "OK"
|
||
};
|
||
|
||
return Task.FromResult(Result<StatisticsExportResult>.Success(result));
|
||
}
|
||
}
|