|
|
@@ -0,0 +1,140 @@
|
|
|
+using Admin.NET.Core;
|
|
|
+using Admin.NET.Plugin.AiDOP.Entity;
|
|
|
+using SqlSugar;
|
|
|
+
|
|
|
+namespace Admin.NET.Plugin.AiDOP.SmartOps;
|
|
|
+
|
|
|
+/// <summary>维度看板解析结果(供 Dashboard 受管码筛选取数;绝不回落旧 Atomic)。</summary>
|
|
|
+public sealed class KpiDimensionDashboardResult
|
|
|
+{
|
|
|
+ /// <summary>SUCCESS / NO_DATA / NOT_CONFIGURED / VERSION_MISMATCH。</summary>
|
|
|
+ public string Status { get; set; } = "NO_DATA";
|
|
|
+ /// <summary>前端每指标 filterMode:configurable_dimension / dimension_no_data / filter_not_configured / version_mismatch。</summary>
|
|
|
+ public string FilterMode { get; set; } = "dimension_no_data";
|
|
|
+ public decimal? MetricValue { get; set; }
|
|
|
+ public List<(string Date, decimal? Value)> Trend { get; set; } = new();
|
|
|
+ public int? SummaryConfigVersion { get; set; }
|
|
|
+ public int? DimensionConfigVersion { get; set; }
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 维度看板取数服务:为受管 KPI(本批 S7_L1_001)在有筛选时从 ado_smart_ops_kpi_dimension_value_day
|
|
|
+/// 读当前激活维度版本结果,按 AggregationType 复算筛选值 + 逐日趋势。校验绑定汇总版本一致;
|
|
|
+/// 无配置/版本不匹配/无数据均返回明确状态,**不回落旧 Atomic**。
|
|
|
+/// </summary>
|
|
|
+public sealed class KpiDimensionDashboardService : ITransient
|
|
|
+{
|
|
|
+ public const string FilterModeConfigured = "configurable_dimension";
|
|
|
+ public const string FilterModeNoData = "dimension_no_data";
|
|
|
+ public const string FilterModeNotConfigured = "filter_not_configured";
|
|
|
+ public const string FilterModeVersionMismatch = "version_mismatch";
|
|
|
+
|
|
|
+ private readonly ISqlSugarClient _db;
|
|
|
+ private readonly AdoSmartOpsKpiDimensionConfigService _dimensionConfig;
|
|
|
+ private readonly AdoSmartOpsKpiCalcConfigService _summaryConfig;
|
|
|
+
|
|
|
+ public KpiDimensionDashboardService(
|
|
|
+ ISqlSugarClient db, AdoSmartOpsKpiDimensionConfigService dimensionConfig, AdoSmartOpsKpiCalcConfigService summaryConfig)
|
|
|
+ {
|
|
|
+ _db = db;
|
|
|
+ _dimensionConfig = dimensionConfig;
|
|
|
+ _summaryConfig = summaryConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task<KpiDimensionDashboardResult> ResolveAsync(
|
|
|
+ long tenantId, long factoryId, string metricCode, string moduleCode, SmartOpsDashboardFilter filter)
|
|
|
+ {
|
|
|
+ // 维度配置/结果落在 KPI 数据租户(S5/S6/S7→pbxfxp/AIDOP),与看板会话租户可能不同,须显式解析
|
|
|
+ var kpiTenantId = AdoSmartOpsKpiCalcConfigService.ResolveKpiTenantId(moduleCode);
|
|
|
+ var active = await _dimensionConfig.GetActiveAsync(kpiTenantId, metricCode);
|
|
|
+ if (active == null)
|
|
|
+ return new KpiDimensionDashboardResult { Status = "NOT_CONFIGURED", FilterMode = FilterModeNotConfigured };
|
|
|
+
|
|
|
+ var summary = await _summaryConfig.GetActiveAsync(kpiTenantId, metricCode);
|
|
|
+ if (summary == null || summary.Id != active.SummaryConfigId || summary.VersionNo != active.SummaryConfigVersion)
|
|
|
+ return new KpiDimensionDashboardResult
|
|
|
+ {
|
|
|
+ Status = "VERSION_MISMATCH", FilterMode = FilterModeVersionMismatch,
|
|
|
+ SummaryConfigVersion = active.SummaryConfigVersion, DimensionConfigVersion = active.DimensionConfigVersion,
|
|
|
+ };
|
|
|
+
|
|
|
+ var result = new KpiDimensionDashboardResult
|
|
|
+ {
|
|
|
+ SummaryConfigVersion = active.SummaryConfigVersion,
|
|
|
+ DimensionConfigVersion = active.DimensionConfigVersion,
|
|
|
+ };
|
|
|
+
|
|
|
+ var ds = filter.DateStart?.Date;
|
|
|
+ var de = filter.DateEnd?.Date;
|
|
|
+ var orderNo = string.IsNullOrWhiteSpace(filter.OrderNo) ? null : filter.OrderNo!.Trim();
|
|
|
+ ISugarQueryable<AdoSmartOpsKpiDimensionValueDay> Build() =>
|
|
|
+ _db.Queryable<AdoSmartOpsKpiDimensionValueDay>().ClearFilter<ITenantIdFilter>()
|
|
|
+ .Where(x => x.TenantId == kpiTenantId && x.MetricCode == metricCode
|
|
|
+ && x.DimensionConfigVersion == active.DimensionConfigVersion)
|
|
|
+ .WhereIF(ds.HasValue, x => x.ValueDate >= ds!.Value)
|
|
|
+ .WhereIF(de.HasValue, x => x.ValueDate <= de!.Value)
|
|
|
+ .WhereIF(orderNo != null, x => x.OrderNo == orderNo || x.DimensionCode == orderNo);
|
|
|
+
|
|
|
+ result.MetricValue = await AggregateAsync(active.AggregationType, Build);
|
|
|
+ // 逐日趋势(同 AggregationType 复算)
|
|
|
+ var grouped = await Build().GroupBy(x => x.ValueDate)
|
|
|
+ .Select(x => new DimTrendRow
|
|
|
+ {
|
|
|
+ D = x.ValueDate,
|
|
|
+ SumSum = SqlFunc.AggregateSum(x.SumValue),
|
|
|
+ SumCnt = SqlFunc.AggregateSum(x.SampleCount),
|
|
|
+ SumNum = SqlFunc.AggregateSum(x.Numerator),
|
|
|
+ SumDen = SqlFunc.AggregateSum(x.Denominator),
|
|
|
+ AvgMv = SqlFunc.AggregateAvg(x.MetricValue),
|
|
|
+ })
|
|
|
+ .OrderBy(x => x.D).ToListAsync();
|
|
|
+ result.Trend = grouped
|
|
|
+ .Select(g => (g.D.ToString("yyyy-MM-dd"), ComputeAggregate(active.AggregationType, g.SumSum, g.SumCnt, g.SumNum, g.SumDen, g.AvgMv)))
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (result.MetricValue == null)
|
|
|
+ {
|
|
|
+ result.Status = "NO_DATA";
|
|
|
+ result.FilterMode = FilterModeNoData;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ result.Status = "SUCCESS";
|
|
|
+ result.FilterMode = FilterModeConfigured;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static async Task<decimal?> AggregateAsync(string aggregationType, Func<ISugarQueryable<AdoSmartOpsKpiDimensionValueDay>> build)
|
|
|
+ {
|
|
|
+ return aggregationType switch
|
|
|
+ {
|
|
|
+ "AVERAGE_OF_SUMS" => Div(await build().SumAsync(x => x.SumValue), (decimal?)await build().SumAsync(x => x.SampleCount)),
|
|
|
+ "RATIO_OF_SUMS" => Div(await build().SumAsync(x => x.Numerator), await build().SumAsync(x => x.Denominator)),
|
|
|
+ "DIRECT_VALUE" => await build().AvgAsync(x => x.MetricValue),
|
|
|
+ _ => null,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static decimal? ComputeAggregate(string aggregationType, decimal? sumSum, decimal? sumCnt, decimal? sumNum, decimal? sumDen, decimal? avgMv)
|
|
|
+ => aggregationType switch
|
|
|
+ {
|
|
|
+ "AVERAGE_OF_SUMS" => Div(sumSum, sumCnt),
|
|
|
+ "RATIO_OF_SUMS" => Div(sumNum, sumDen),
|
|
|
+ "DIRECT_VALUE" => avgMv,
|
|
|
+ _ => null,
|
|
|
+ };
|
|
|
+
|
|
|
+ private static decimal? Div(decimal? a, decimal? b) =>
|
|
|
+ b.HasValue && b.Value != 0 ? (a ?? 0m) / b.Value : null;
|
|
|
+
|
|
|
+ private sealed class DimTrendRow
|
|
|
+ {
|
|
|
+ public DateTime D { get; set; }
|
|
|
+ public decimal? SumSum { get; set; }
|
|
|
+ public decimal? SumCnt { get; set; }
|
|
|
+ public decimal? SumNum { get; set; }
|
|
|
+ public decimal? SumDen { get; set; }
|
|
|
+ public decimal? AvgMv { get; set; }
|
|
|
+ }
|
|
|
+}
|