Просмотр исходного кода

feat(s8): add demo baseline values for S9 result KPIs

YY968XX 1 неделя назад
Родитель
Сommit
a3ccbd2292

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 	"name": "admin.net",
 	"type": "module",
-	"version": "2.4.147",
+	"version": "2.4.148",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 10 - 2
Web/src/views/aidop/s8/api/s8MonitoringApi.ts

@@ -173,7 +173,14 @@ export interface S8QdcSummaryData {
 	items: S8QdcSummaryItem[];
 }
 
-// TASK-012-S9-QDC-RATIO-FRONTEND-1:result KPI(达成率,与 exception-side qdc-summary 不同语义)
+// S9-RESULT-KPI-DEMO-BASELINE-AND-ORDER-FLOW-CALC-1:source 字段标识每项来源;顶层 source 扩展 MIXED_BASELINE。
+export type S8ResultKpiItemSource =
+	| 'DEMO_BASELINE'
+	| 'ORDER_FLOW_CALC'
+	| 'ORDER_FLOW_CALC_EMPTY'
+	| 'PENDING_REAL'
+	| string;
+
 export interface S8ResultKpiItem {
 	metricCode: string;
 	metricName: string;
@@ -183,11 +190,12 @@ export interface S8ResultKpiItem {
 	targetRatio: number | null;
 	dictionaryEnabled: boolean;
 	remark: string | null;
+	source: S8ResultKpiItemSource;
 }
 
 export interface S8ResultKpiSummary {
 	items: S8ResultKpiItem[];
-	source: 'DICTIONARY_MOCK' | 'REAL' | string;
+	source: 'DICTIONARY_MOCK' | 'MIXED_BASELINE' | 'REAL' | string;
 }
 
 export const s8MonitoringApi = {

+ 59 - 5
Web/src/views/aidop/s8/monitoring/components/S9QdcSummaryCard.vue

@@ -23,6 +23,38 @@ const resultKpiList = computed(() => props.resultKpiItems ?? []);
 const hasResultKpi = computed(() => resultKpiList.value.length > 0);
 const hasException = computed(() => props.items.length > 0);
 
+// S9-RESULT-KPI-DEMO-BASELINE-AND-ORDER-FLOW-CALC-1:source 文案映射
+const resultKpiSourceText = computed(() => {
+	switch (props.resultKpiSource) {
+		case 'DICTIONARY_MOCK':
+			return '字典占位 · 待接入真实计算';
+		case 'MIXED_BASELINE':
+			return '演示基线 · 部分订单链路计算';
+		default:
+			return '';
+	}
+});
+
+function itemSourceText(source: string | undefined): string {
+	switch (source) {
+		case 'DEMO_BASELINE':
+			return '演示基线';
+		case 'ORDER_FLOW_CALC':
+			return '订单链路计算';
+		case 'ORDER_FLOW_CALC_EMPTY':
+			return '订单链路计算 · 无样本';
+		case 'PENDING_REAL':
+			return '待真实接入';
+		default:
+			return '';
+	}
+}
+
+function shouldShowDisabledBadge(item: S8ResultKpiItem): boolean {
+	if (item.dictionaryEnabled) return false;
+	return props.resultKpiSource === 'DICTIONARY_MOCK';
+}
+
 function fmtHours(val: number | null | undefined): string {
 	if (val === null || val === undefined) return '--';
 	return `${val.toFixed(1)}h`;
@@ -52,9 +84,10 @@ function fmtTarget(item: S8ResultKpiItem): string {
 			<span class="s9-qdc-card__code">S9</span>
 			<span class="s9-qdc-card__title">运营指标</span>
 			<span
-				v-if="activeView === 'resultKpi' && isMock"
+				v-if="activeView === 'resultKpi' && resultKpiSourceText"
 				class="s9-qdc-card__sub"
-			>字典占位 · 待接入真实计算</span>
+				:class="{ 's9-qdc-card__sub--warn': isMock }"
+			>{{ resultKpiSourceText }}</span>
 			<div class="s9-qdc-card__tabs">
 				<button
 					type="button"
@@ -82,13 +115,16 @@ function fmtTarget(item: S8ResultKpiItem): string {
 				<div v-for="item in resultKpiList" :key="item.metricCode" class="s9-qdc-track s9-qdc-track--kpi">
 					<div class="s9-qdc-track__kpi-head">
 						<span class="s9-qdc-track__name">{{ item.metricName }}</span>
-						<span v-if="!item.dictionaryEnabled" class="s9-qdc-card__badge">未启用</span>
+						<span v-if="shouldShowDisabledBadge(item)" class="s9-qdc-card__badge">未启用</span>
 					</div>
 					<div
 						class="s9-qdc-track__main-val"
 						:class="{ 's9-qdc-track__main-val--mock': item.currentValue === null }"
 					>{{ fmtCurrent(item) }}</div>
-					<div class="s9-qdc-track__foot">{{ fmtTarget(item) }}</div>
+					<div class="s9-qdc-track__foot">
+						<span>{{ fmtTarget(item) }}</span>
+						<span v-if="itemSourceText(item.source)" class="s9-qdc-track__src-tag">{{ itemSourceText(item.source) }}</span>
+					</div>
 				</div>
 			</div>
 		</template>
@@ -160,11 +196,15 @@ function fmtTarget(item: S8ResultKpiItem): string {
 
 .s9-qdc-card__sub {
 	font-size: 10px;
-	color: rgba(255, 193, 7, 0.75);
+	color: rgba(123, 208, 255, 0.7);
 	margin-left: 2px;
 	letter-spacing: 0.02em;
 }
 
+.s9-qdc-card__sub--warn {
+	color: rgba(255, 193, 7, 0.75);
+}
+
 .s9-qdc-card__tabs {
 	margin-left: auto;
 	display: flex;
@@ -286,5 +326,19 @@ function fmtTarget(item: S8ResultKpiItem): string {
 .s9-qdc-track__foot {
 	font-size: 9px;
 	color: #909097;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	gap: 4px;
+}
+
+.s9-qdc-track__src-tag {
+	padding: 1px 6px;
+	border-radius: 999px;
+	font-size: 9px;
+	color: rgba(123, 208, 255, 0.85);
+	background: rgba(123, 208, 255, 0.08);
+	border: 1px solid rgba(123, 208, 255, 0.2);
+	white-space: nowrap;
 }
 </style>

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.112</AssemblyVersion>
-    <FileVersion>1.0.112</FileVersion>
-    <Version>1.0.112</Version>
+    <AssemblyVersion>1.0.113</AssemblyVersion>
+    <FileVersion>1.0.113</FileVersion>
+    <Version>1.0.113</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 4 - 8
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs

@@ -297,9 +297,7 @@ public class AdoS8QdcSummaryDto
 }
 
 /// <summary>
-/// TASK-012-S9-QDC-RATIO-MIGRATION-2:S9 result KPI 单项(订单交付率/到货达成率/检验合格率/工单完工率)。
-/// 与 S9 QDC exception-side 卡片是不同语义;本期仅返回字典基础信息 + 占位 currentValue/targetRatio。
-/// 真实计算入口待 RATIO-REAL-1 后续任务接入。
+/// S9 result KPI 单项。currentValue/targetRatio 由 service 按 metricCode 决定。
 /// </summary>
 public class AdoS8ResultKpiItemDto
 {
@@ -308,20 +306,18 @@ public class AdoS8ResultKpiItemDto
     public string MetricName { get; set; } = string.Empty;
     public string ObjectCode { get; set; } = string.Empty;
     public string Unit { get; set; } = "%";
-    /// <summary>当前值占位;本期为 NULL 表示未计算</summary>
     public decimal? CurrentValue { get; set; }
-    /// <summary>目标值;取自 ado_s8_monitor_metric.default_target_ratio</summary>
     public decimal? TargetRatio { get; set; }
-    /// <summary>S8 dictionary 是否启用(本期均为 false,因 RATIO 已迁出 S8 watch_rule)</summary>
     public bool DictionaryEnabled { get; set; }
     public string? Remark { get; set; }
+    /// <summary>每项来源:DEMO_BASELINE / ORDER_FLOW_CALC / ORDER_FLOW_CALC_EMPTY / PENDING_REAL。</summary>
+    public string Source { get; set; } = "DEMO_BASELINE";
 }
 
-/// <summary>TASK-012-S9-QDC-RATIO-MIGRATION-2:result KPI summary 出参(mock 阶段)</summary>
+/// <summary>S9 result KPI summary 出参。Source 顶层:DICTIONARY_MOCK / MIXED_BASELINE / REAL。</summary>
 public class AdoS8ResultKpiSummaryDto
 {
     public List<AdoS8ResultKpiItemDto> Items { get; set; } = new();
-    /// <summary>数据来源标记:DICTIONARY_MOCK = 字典 + 占位值;REAL = 真实计算(待后续任务)</summary>
     public string Source { get; set; } = "DICTIONARY_MOCK";
 }
 

+ 97 - 17
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8MonitoringService.cs

@@ -1,6 +1,7 @@
 using Admin.NET.Plugin.AiDOP.Dto.S8;
 using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
 using Admin.NET.Plugin.AiDOP.Entity.S8;
+using Admin.NET.Plugin.AiDOP.Entity.S8.OrderFlow;
 using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
 
 namespace Admin.NET.Plugin.AiDOP.Service.S8;
@@ -15,17 +16,21 @@ public class S8MonitoringService : ITransient
     private readonly SqlSugarRepository<AdoS0DepartmentMaster> _deptRep;
     // TASK-012-S9-QDC-RATIO-MIGRATION-2:result KPI summary 读取 RATIO 指标字典
     private readonly SqlSugarRepository<AdoS8MonitorMetric> _metricRep;
+    // S9-RESULT-KPI-DEMO-BASELINE-AND-ORDER-FLOW-CALC-1:ORDER_FLOW_CYCLE_RATIO 半真实计算依赖订单链路阶段表
+    private readonly SqlSugarRepository<AdoS8OrderFlowStage> _orderFlowStageRep;
 
     public S8MonitoringService(
         SqlSugarRepository<AdoS8Exception> rep,
         SqlSugarRepository<AdoS8ExceptionType> typeRep,
         SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
-        SqlSugarRepository<AdoS8MonitorMetric> metricRep)
+        SqlSugarRepository<AdoS8MonitorMetric> metricRep,
+        SqlSugarRepository<AdoS8OrderFlowStage> orderFlowStageRep)
     {
         _rep = rep;
         _typeRep = typeRep;
         _deptRep = deptRep;
         _metricRep = metricRep;
+        _orderFlowStageRep = orderFlowStageRep;
     }
 
     /// <summary>
@@ -741,10 +746,11 @@ public class S8MonitoringService : ITransient
     }
 
     /// <summary>
-    /// TASK-012-S9-QDC-RATIO-MIGRATION-2:S9 result KPI summary(mock 阶段)。
-    /// 来源:ado_s8_monitor_metric WHERE mechanism='RATIO' AND is_result_kpi=1。
-    /// 本期 currentValue 始终 NULL;前端展示需标识"待接入真实计算"。
-    /// 合并策略:baseline (tenant_id=0/factory_id=0) + 当前 tenant/factory 覆盖。
+    /// S9-RESULT-KPI-DEMO-BASELINE-AND-ORDER-FLOW-CALC-1:A+B 混合方案。
+    /// 4 个 KPI(ORDER_DELIVERY_RATE/PO_DELIVERY_RATE/IQC_PASS_RATE/WO_COMPLETION_RATE)→ DEMO_BASELINE 演示基线;
+    /// 1 个 KPI(ORDER_FLOW_CYCLE_RATIO)→ ORDER_FLOW_CALC 基于 ado_s8_order_flow_stage 半真实计算;
+    /// 不写 DB / 不接 period / 不启用 watch_rule。
+    /// 来源:ado_s8_monitor_metric WHERE mechanism='RATIO' AND is_result_kpi=1(保留字典顺序与 metricName)。
     /// </summary>
     public async Task<AdoS8ResultKpiSummaryDto> GetResultKpiSummaryAsync(long tenantId, long factoryId)
     {
@@ -754,7 +760,6 @@ public class S8MonitoringService : ITransient
                     || (x.TenantId == tenantId && x.FactoryId == factoryId)))
             .ToListAsync();
 
-        // tenant/factory 覆盖 baseline
         var resolved = rows
             .GroupBy(x => x.MetricCode)
             .Select(g => g.OrderByDescending(x => x.FactoryId).ThenByDescending(x => x.TenantId).First())
@@ -762,22 +767,97 @@ public class S8MonitoringService : ITransient
             .ThenBy(x => x.MetricCode)
             .ToList();
 
-        var items = resolved.Select(m => new AdoS8ResultKpiItemDto
+        var items = new List<AdoS8ResultKpiItemDto>(resolved.Count);
+        foreach (var m in resolved)
         {
-            MetricCode = m.MetricCode,
-            MetricName = m.MetricName,
-            ObjectCode = m.ObjectCode,
-            Unit = string.IsNullOrWhiteSpace(m.Unit) ? "%" : m.Unit!,
-            CurrentValue = null,
-            TargetRatio = m.DefaultTargetRatio,
-            DictionaryEnabled = m.Enabled,
-            Remark = m.Remark,
-        }).ToList();
+            var unit = string.IsNullOrWhiteSpace(m.Unit) ? "%" : m.Unit!;
+            if (_demoBaselineMap.TryGetValue(m.MetricCode, out var baseline))
+            {
+                items.Add(new AdoS8ResultKpiItemDto
+                {
+                    MetricCode = m.MetricCode,
+                    MetricName = m.MetricName,
+                    ObjectCode = m.ObjectCode,
+                    Unit = unit,
+                    CurrentValue = baseline.CurrentValue,
+                    TargetRatio = baseline.TargetRatio,
+                    DictionaryEnabled = true,
+                    Remark = baseline.Remark,
+                    Source = "DEMO_BASELINE",
+                });
+            }
+            else if (m.MetricCode == "ORDER_FLOW_CYCLE_RATIO")
+            {
+                var (cv, source) = await ComputeOrderFlowCycleRatioAsync(tenantId, factoryId);
+                items.Add(new AdoS8ResultKpiItemDto
+                {
+                    MetricCode = m.MetricCode,
+                    MetricName = m.MetricName,
+                    ObjectCode = m.ObjectCode,
+                    Unit = unit,
+                    CurrentValue = cv,
+                    TargetRatio = 90.0m,
+                    DictionaryEnabled = true,
+                    Remark = "基于已完成订单链路阶段 actual_days/planned_days 计算,pending 阶段暂不纳入",
+                    Source = source,
+                });
+            }
+            else
+            {
+                items.Add(new AdoS8ResultKpiItemDto
+                {
+                    MetricCode = m.MetricCode,
+                    MetricName = m.MetricName,
+                    ObjectCode = m.ObjectCode,
+                    Unit = unit,
+                    CurrentValue = null,
+                    TargetRatio = m.DefaultTargetRatio,
+                    DictionaryEnabled = false,
+                    Remark = m.Remark,
+                    Source = "PENDING_REAL",
+                });
+            }
+        }
 
         return new AdoS8ResultKpiSummaryDto
         {
             Items = items,
-            Source = "DICTIONARY_MOCK",
+            Source = "MIXED_BASELINE",
         };
     }
+
+    private static readonly Dictionary<string, (decimal CurrentValue, decimal TargetRatio, string Remark)> _demoBaselineMap = new()
+    {
+        ["ORDER_DELIVERY_RATE"] = (92.0m, 95.0m, "演示基线,待真实订单交付口径接入"),
+        ["PO_DELIVERY_RATE"]    = (88.0m, 95.0m, "演示基线,待采购到货真实表接入"),
+        ["IQC_PASS_RATE"]       = (96.0m, 98.0m, "演示基线,待 IQC 检验真实表接入"),
+        ["WO_COMPLETION_RATE"]  = (90.0m, 95.0m, "演示基线,待工单完工真实表接入"),
+    };
+
+    /// <summary>
+    /// 候选 A:actual_days IS NOT NULL 的已完成阶段中 actual_days <= planned_days 的占比。
+    /// pending 阶段不纳入 denominator;不依赖 status 字段;按 tenant/factory 过滤。
+    /// </summary>
+    private async Task<(decimal? CurrentValue, string Source)> ComputeOrderFlowCycleRatioAsync(long tenantId, long factoryId)
+    {
+        try
+        {
+            var stages = await _orderFlowStageRep.AsQueryable()
+                .Where(x => !x.IsDeleted && x.TenantId == tenantId && x.FactoryId == factoryId
+                    && x.ActualDays != null)
+                .Select(x => new { x.ActualDays, x.PlannedDays })
+                .ToListAsync();
+
+            var denominator = stages.Count;
+            if (denominator == 0) return (null, "ORDER_FLOW_CALC_EMPTY");
+
+            var numerator = stages.Count(s => s.ActualDays!.Value <= s.PlannedDays);
+            var value = Math.Round(numerator * 100m / denominator, 1);
+            return (value, "ORDER_FLOW_CALC");
+        }
+        catch
+        {
+            return (null, "ORDER_FLOW_CALC_EMPTY");
+        }
+    }
 }