KpiDimensionDashboardService.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. using Admin.NET.Core;
  2. using Admin.NET.Plugin.AiDOP.Entity;
  3. using SqlSugar;
  4. namespace Admin.NET.Plugin.AiDOP.SmartOps;
  5. /// <summary>维度看板解析结果(供 Dashboard 受管码筛选取数;绝不回落旧 Atomic)。</summary>
  6. public sealed class KpiDimensionDashboardResult
  7. {
  8. /// <summary>SUCCESS / NO_DATA / NOT_CONFIGURED / VERSION_MISMATCH。</summary>
  9. public string Status { get; set; } = "NO_DATA";
  10. /// <summary>前端每指标 filterMode:configurable_dimension / dimension_no_data / filter_not_configured / version_mismatch。</summary>
  11. public string FilterMode { get; set; } = "dimension_no_data";
  12. public decimal? MetricValue { get; set; }
  13. public List<(string Date, decimal? Value)> Trend { get; set; } = new();
  14. public int? SummaryConfigVersion { get; set; }
  15. public int? DimensionConfigVersion { get; set; }
  16. }
  17. /// <summary>
  18. /// 维度看板取数服务:为受管 KPI(本批 S7_L1_001)在有筛选时从 ado_smart_ops_kpi_dimension_value_day
  19. /// 读当前激活维度版本结果,按 AggregationType 复算筛选值 + 逐日趋势。校验绑定汇总版本一致;
  20. /// 无配置/版本不匹配/无数据均返回明确状态,**不回落旧 Atomic**。
  21. /// </summary>
  22. public sealed class KpiDimensionDashboardService : ITransient
  23. {
  24. public const string FilterModeConfigured = "configurable_dimension";
  25. public const string FilterModeNoData = "dimension_no_data";
  26. public const string FilterModeNotConfigured = "filter_not_configured";
  27. public const string FilterModeVersionMismatch = "version_mismatch";
  28. private readonly ISqlSugarClient _db;
  29. private readonly AdoSmartOpsKpiDimensionConfigService _dimensionConfig;
  30. private readonly AdoSmartOpsKpiCalcConfigService _summaryConfig;
  31. public KpiDimensionDashboardService(
  32. ISqlSugarClient db, AdoSmartOpsKpiDimensionConfigService dimensionConfig, AdoSmartOpsKpiCalcConfigService summaryConfig)
  33. {
  34. _db = db;
  35. _dimensionConfig = dimensionConfig;
  36. _summaryConfig = summaryConfig;
  37. }
  38. public async Task<KpiDimensionDashboardResult> ResolveAsync(
  39. long tenantId, long factoryId, string metricCode, string moduleCode, SmartOpsDashboardFilter filter)
  40. {
  41. // 维度配置/结果落在 KPI 数据租户(S5/S6/S7→pbxfxp/AIDOP),与看板会话租户可能不同,须显式解析
  42. var kpiTenantId = AdoSmartOpsKpiCalcConfigService.ResolveKpiTenantId(moduleCode);
  43. var active = await _dimensionConfig.GetActiveAsync(kpiTenantId, metricCode);
  44. if (active == null)
  45. return new KpiDimensionDashboardResult { Status = "NOT_CONFIGURED", FilterMode = FilterModeNotConfigured };
  46. var summary = await _summaryConfig.GetActiveAsync(kpiTenantId, metricCode);
  47. if (summary == null || summary.Id != active.SummaryConfigId || summary.VersionNo != active.SummaryConfigVersion)
  48. return new KpiDimensionDashboardResult
  49. {
  50. Status = "VERSION_MISMATCH", FilterMode = FilterModeVersionMismatch,
  51. SummaryConfigVersion = active.SummaryConfigVersion, DimensionConfigVersion = active.DimensionConfigVersion,
  52. };
  53. var result = new KpiDimensionDashboardResult
  54. {
  55. SummaryConfigVersion = active.SummaryConfigVersion,
  56. DimensionConfigVersion = active.DimensionConfigVersion,
  57. };
  58. var ds = filter.DateStart?.Date;
  59. var de = filter.DateEnd?.Date;
  60. // 业务维度值统一落在 DimensionCode(各 KPI 声明维度不同:物料/工单/订单/品类),
  61. // 同时兼容标准列匹配。前端只发该 KPI SupportedDimensions 内的维度。
  62. var orderNo = string.IsNullOrWhiteSpace(filter.OrderNo) ? null : filter.OrderNo!.Trim();
  63. var material = string.IsNullOrWhiteSpace(filter.Material) ? null : filter.Material!.Trim();
  64. var workOrder = string.IsNullOrWhiteSpace(filter.WorkOrder) ? null : filter.WorkOrder!.Trim();
  65. var product = string.IsNullOrWhiteSpace(filter.Product) ? null : filter.Product!.Trim();
  66. var productionLine = string.IsNullOrWhiteSpace(filter.ProductionLine) ? null : filter.ProductionLine!.Trim();
  67. ISugarQueryable<AdoSmartOpsKpiDimensionValueDay> Build() =>
  68. _db.Queryable<AdoSmartOpsKpiDimensionValueDay>().ClearFilter<ITenantIdFilter>()
  69. .Where(x => x.TenantId == kpiTenantId && x.MetricCode == metricCode
  70. && x.DimensionConfigVersion == active.DimensionConfigVersion)
  71. .WhereIF(ds.HasValue, x => x.ValueDate >= ds!.Value)
  72. .WhereIF(de.HasValue, x => x.ValueDate <= de!.Value)
  73. .WhereIF(orderNo != null, x => x.OrderNo == orderNo || x.DimensionCode == orderNo)
  74. .WhereIF(material != null, x => x.MaterialCode == material || x.DimensionCode == material)
  75. .WhereIF(workOrder != null, x => x.WorkOrderNo == workOrder || x.DimensionCode == workOrder)
  76. .WhereIF(product != null, x => x.ProductCode == product || x.DimensionCode == product)
  77. .WhereIF(productionLine != null, x => x.DimensionCode == productionLine || x.DimensionName == productionLine);
  78. result.MetricValue = await AggregateAsync(active.AggregationType, Build);
  79. // 逐日趋势(同 AggregationType 复算)
  80. var grouped = await Build().GroupBy(x => x.ValueDate)
  81. .Select(x => new DimTrendRow
  82. {
  83. D = x.ValueDate,
  84. SumSum = SqlFunc.AggregateSum(x.SumValue),
  85. SumCnt = SqlFunc.AggregateSum(x.SampleCount),
  86. SumNum = SqlFunc.AggregateSum(x.Numerator),
  87. SumDen = SqlFunc.AggregateSum(x.Denominator),
  88. AvgMv = SqlFunc.AggregateAvg(x.MetricValue),
  89. })
  90. .OrderBy(x => x.D).ToListAsync();
  91. result.Trend = grouped
  92. .Select(g => (g.D.ToString("yyyy-MM-dd"), ComputeAggregate(active.AggregationType, g.SumSum, g.SumCnt, g.SumNum, g.SumDen, g.AvgMv)))
  93. .ToList();
  94. if (result.MetricValue == null)
  95. {
  96. result.Status = "NO_DATA";
  97. result.FilterMode = FilterModeNoData;
  98. }
  99. else
  100. {
  101. result.Status = "SUCCESS";
  102. result.FilterMode = FilterModeConfigured;
  103. }
  104. return result;
  105. }
  106. private static async Task<decimal?> AggregateAsync(string aggregationType, Func<ISugarQueryable<AdoSmartOpsKpiDimensionValueDay>> build)
  107. {
  108. return aggregationType switch
  109. {
  110. "AVERAGE_OF_SUMS" => Div(await build().SumAsync(x => x.SumValue), (decimal?)await build().SumAsync(x => x.SampleCount)),
  111. "RATIO_OF_SUMS" => Div(await build().SumAsync(x => x.Numerator), await build().SumAsync(x => x.Denominator)),
  112. "DIRECT_VALUE" => await build().AvgAsync(x => x.MetricValue),
  113. _ => null,
  114. };
  115. }
  116. private static decimal? ComputeAggregate(string aggregationType, decimal? sumSum, decimal? sumCnt, decimal? sumNum, decimal? sumDen, decimal? avgMv)
  117. => aggregationType switch
  118. {
  119. "AVERAGE_OF_SUMS" => Div(sumSum, sumCnt),
  120. "RATIO_OF_SUMS" => Div(sumNum, sumDen),
  121. "DIRECT_VALUE" => avgMv,
  122. _ => null,
  123. };
  124. private static decimal? Div(decimal? a, decimal? b) =>
  125. b.HasValue && b.Value != 0 ? (a ?? 0m) / b.Value : null;
  126. private sealed class DimTrendRow
  127. {
  128. public DateTime D { get; set; }
  129. public decimal? SumSum { get; set; }
  130. public decimal? SumCnt { get; set; }
  131. public decimal? SumNum { get; set; }
  132. public decimal? SumDen { get; set; }
  133. public decimal? AvgMv { get; set; }
  134. }
  135. }