Parcourir la source

fix(s8): remove seed-derived headline kpi claims | server 1.0.561

四个结果 KPI 以「订单链路计算」的名义对外宣称当前业务实算,实际算的是种子数据。

ORDER_DELIVERY_RATE / PO_DELIVERY_RATE / WO_COMPLETION_RATE / ORDER_FLOW_CYCLE_RATIO
分别读 ado_s8_order_flow_order / _procurement_pivot / _manufacturing_process / _stage,
发射 Source="ORDER_FLOW_CALC",前端 S9QdcSummaryCard 渲染为「订单链路计算」。
但这四张表唯一的写入方是 SeedData(全仓无任何 Insertable/Updateable 命中),
实库 25 / 1152 / 110 / 126 行全部 data_source='SEED',四个查询也从不过滤 SEED。
数字不是业务事实,那句 provenance 是错的。

逐项核对可替代的真实中台 Authority,四项均不成立,因此不做「改读中台」:
· ORDER_DELIVERY_RATE —— dwd_t8_order_shipment_fulfillment 是行级发运及时率/按 biz_date,
  与「订单周期 actual<=target」口径不同,换算须自行发明时间窗并改分母粒度;
· PO_DELIVERY_RATE —— mdp_std_purchase_order_completion 既无承诺到期日也无实际到货日,
  其 observed_at / source_update_time 在表注释中被明确标注「禁止冒充业务完成时间」,
  按期到货率在现有中台里根本不可算;
· WO_COMPLETION_RATE —— dwd_t8_work_order_mfg_fulfillment 粒度为 (biz_date,工单,物料)
  的窗口完成率,与「工序 achievement_rate 算术平均」不同,换算须自行选定 biz_date;
· ORDER_FLOW_CYCLE_RATIO —— 中台没有任何对象承载 S8 五阶段 planned/actual days。

按「不得为了保住卡片而发明公式」的口径,四项一律不再出当前值,落既有 PENDING_REAL
(前端既有文案「待真实接入」),与 IQC_PASS_RATE 的 DEMO_BASELINE/「当前基线」同范式。
四个计算方法与四个 SEED 仓储依赖一并移除——S8MonitoringService 从此与
ado_s8_order_flow_* 无任何运行期事实依赖(不删表、不删 seed,仅断开事实链路)。

同时修 envelope:Source 原无条件写死 "MIXED_BASELINE"(前端「当前基线 · 部分订单链路计算」),
items 为空时照样宣称「部分算了」——而字典 5 行当前全部 enabled=0,items 恒为空,
这正是当前唯一真正显示给用户的那句假陈述。改为按 items 真实构成推出。

另修正一处长期与实现不符的文档注释(原写「4 个 → DEMO_BASELINE / 1 个 → ORDER_FLOW_CALC」,
实际相反),避免后续据旧文案推断行为。

验证:新增 S8KpiProvenanceTests 8 例(含反射元数据层守卫:构造函数不得再注入 SEED 仓储),
对修复前实现 5/8 失败(RED 验证);插件 S8 用例 828 passed / 1 failed,
该失败为既有基线 MutationActions_DoNotUseReadOnlyCapabilities,净新增失败 0;
Admin.NET.Test S8 190/4 与基线一致;dotnet build 0 error。
YY968XX il y a 2 jours
Parent
commit
ee4dbd4355

+ 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.560</AssemblyVersion>
-    <FileVersion>1.0.560</FileVersion>
-    <Version>1.0.560</Version>
+    <AssemblyVersion>1.0.561</AssemblyVersion>
+    <FileVersion>1.0.561</FileVersion>
+    <Version>1.0.561</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 192 - 0
server/Plugins/Admin.NET.Plugin.AiDOP.Tests/S8/S8KpiProvenanceTests.cs

@@ -0,0 +1,192 @@
+using Admin.NET.Plugin.AiDOP.Service.S8;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using Xunit;
+
+namespace Admin.NET.Plugin.AiDOP.Tests.S8;
+
+/// <summary>
+/// S8-REAL-DATA-PRESENTATION-1:结果 KPI 的来源标注守卫。
+///
+/// <para><b>修复前的事实</b>(2026-09-21 取证,代码 + 实库双证):
+/// <c>ORDER_DELIVERY_RATE</c> / <c>PO_DELIVERY_RATE</c> / <c>WO_COMPLETION_RATE</c> /
+/// <c>ORDER_FLOW_CYCLE_RATIO</c> 四项分别读 <c>ado_s8_order_flow_order</c> /
+/// <c>_procurement_pivot</c> / <c>_manufacturing_process</c> / <c>_stage</c>,
+/// 并以 <c>Source="ORDER_FLOW_CALC"</c>(前端「订单链路计算」)对外宣称是当前实算。
+/// 而这四张表唯一的写入方是 SeedData,实库全部行 <c>data_source='SEED'</c>,查询也不过滤 SEED。
+/// 另有 envelope 无条件写死 <c>MIXED_BASELINE</c>(前端「当前基线 · 部分订单链路计算」),
+/// 即便 items 为空也照样宣称「部分算了」。</para>
+///
+/// <para><b>为什么是源码契约测试</b>:<see cref="S8MonitoringService"/> 依赖 SqlSugar 实库,
+/// 进程内无法构造;沿用本仓既有手法(反射元数据 + 去注释源码契约),
+/// 与 <c>S8DebugTenantGuardTests</c> / <c>S8LegacySymbolsRemovedTests</c> 同范式。
+/// 断言一律在**剥离注释后**的源码上进行,避免文档里提到某个词就把守卫变成噪音。</para>
+/// </summary>
+public class S8KpiProvenanceTests
+{
+    private static string PluginRoot => Path.GetFullPath(
+        Path.Combine(AppContext.BaseDirectory, "../../../../Admin.NET.Plugin.AiDOP"));
+
+    private static string ReadCode(string relativePath)
+    {
+        var raw = File.ReadAllText(Path.Combine(PluginRoot, relativePath));
+        var noBlock = Regex.Replace(raw, @"/\*.*?\*/", string.Empty, RegexOptions.Singleline);
+        return Regex.Replace(noBlock, @"^\s*//.*$", string.Empty, RegexOptions.Multiline);
+    }
+
+    private static string MonitoringCode => ReadCode("Service/S8/S8MonitoringService.cs");
+
+    /// <summary>SEED 订单链路表对应的实体名——它们不得出现在监控/KPI 服务的运行期取数里。</summary>
+    private static readonly string[] SeedOrderFlowEntities =
+    {
+        "AdoS8OrderFlowOrder",
+        "AdoS8OrderFlowProcurementPivot",
+        "AdoS8OrderFlowManufacturingProcess",
+        "AdoS8OrderFlowStage",
+    };
+
+    // ───────────── ⑧ HeadlineKpi_DoesNotReadSeedAsCurrentFact ─────────────
+
+    /// <summary>KPI 服务不得再依赖任何 ado_s8_order_flow_* SEED 仓储。</summary>
+    [Fact]
+    public void HeadlineKpi_DoesNotReadSeedAsCurrentFact()
+    {
+        var code = MonitoringCode;
+        foreach (var entity in SeedOrderFlowEntities)
+            Assert.DoesNotContain(entity, code);
+
+        Assert.DoesNotContain("ado_s8_order_flow", code);
+    }
+
+    /// <summary>
+    /// 反射元数据层的同一条守卫:构造函数不得再注入 SEED 订单链路仓储。
+    /// 改文案绕不过这一条。
+    /// </summary>
+    [Fact]
+    public void MonitoringService_TakesNoSeedOrderFlowRepository()
+    {
+        var ctor = Assert.Single(typeof(S8MonitoringService).GetConstructors());
+        var injected = ctor.GetParameters()
+            .Select(p => p.ParameterType.IsGenericType
+                ? p.ParameterType.GetGenericArguments()[0].Name
+                : p.ParameterType.Name)
+            .ToList();
+
+        foreach (var entity in SeedOrderFlowEntities)
+            Assert.DoesNotContain(entity, injected);
+    }
+
+    /// <summary>
+    /// 不得再发射 ORDER_FLOW_CALC / ORDER_FLOW_CALC_EMPTY 这两个「实算」provenance。
+    /// 判据精确到赋值形态,避免误伤文档中对历史行为的说明。
+    /// </summary>
+    [Fact]
+    public void NoOrderFlowCalcProvenanceIsEmitted()
+    {
+        var code = MonitoringCode;
+
+        Assert.DoesNotContain("\"ORDER_FLOW_CALC_EMPTY\"", code);
+        // 唯一允许保留的是 envelope 的**判定**(读 items 的 Source),不是发射点。
+        var occurrences = Regex.Matches(code, "\"ORDER_FLOW_CALC\"").Count;
+        Assert.Equal(1, occurrences);
+        Assert.Contains("i.Source == \"ORDER_FLOW_CALC\"", code);
+    }
+
+    // ───────────── ⑩ MissingRealAuthority_ReturnsNoData ─────────────
+
+    /// <summary>
+    /// 四项无真实 Authority 的 KPI 必须走「不出当前值」的路径:
+    /// 分发器不再按 metricCode 分支,一律返回 null → 调用方落 PENDING_REAL。
+    /// </summary>
+    [Fact]
+    public void MissingRealAuthority_ReturnsNoData()
+    {
+        var code = MonitoringCode;
+
+        // 四个指标码不得再出现在任何 switch 分发分支里
+        foreach (var metric in new[]
+                 {
+                     "\"ORDER_DELIVERY_RATE\"    =>",
+                     "\"PO_DELIVERY_RATE\"       =>",
+                     "\"WO_COMPLETION_RATE\"     =>",
+                     "\"ORDER_FLOW_CYCLE_RATIO\" =>",
+                 })
+            Assert.DoesNotContain(metric, code);
+
+        // 旧的四个计算方法必须整体消失
+        foreach (var m in new[]
+                 {
+                     "ComputeOrderDeliveryRateAsync",
+                     "ComputePoDeliveryRateAsync",
+                     "ComputeWoCompletionRateAsync",
+                     "ComputeOrderFlowCycleRatioAsync",
+                 })
+            Assert.DoesNotContain(m, code);
+
+        // PENDING_REAL 兜底路径仍在
+        Assert.Contains("\"PENDING_REAL\"", code);
+    }
+
+    /// <summary>
+    /// IQC_PASS_RATE 作为如实标注的正向样板必须保持不变:
+    /// 常量值 + DEMO_BASELINE 标注(前端「当前基线」),不得被顺手改成实算。
+    /// </summary>
+    [Fact]
+    public void ReferenceKpi_IsExplicitlyLabeledReference()
+    {
+        var code = MonitoringCode;
+        Assert.Contains("IQC_PASS_RATE", code);
+        Assert.Contains("\"DEMO_BASELINE\"", code);
+    }
+
+    // ───────────── envelope provenance ─────────────
+
+    /// <summary>
+    /// envelope 的 Source 必须由 items 真实构成推出,不得写死。
+    /// 旧实现无条件 MIXED_BASELINE —— items 为空时照样宣称「部分订单链路计算」。
+    /// </summary>
+    [Fact]
+    public void EnvelopeProvenance_IsDerivedFromItems_NotHardcoded()
+    {
+        var code = MonitoringCode;
+
+        Assert.DoesNotContain("Source = \"MIXED_BASELINE\",", code);
+        Assert.Contains("hasRealCalc", code);
+        Assert.Contains("hasRealCalc ? \"MIXED_BASELINE\" : \"DICTIONARY_MOCK\"", code);
+    }
+
+    // ───────────── ⑬ NoDirectBusinessSourceRead ─────────────
+
+    /// <summary>
+    /// 去 seed 不得演变成「直读业务源表」。监控/KPI 服务里不允许出现源系统表名。
+    /// </summary>
+    [Fact]
+    public void NoDirectBusinessSourceRead()
+    {
+        var code = MonitoringCode;
+        foreach (var src in new[]
+                 {
+                     "PurOrdMaster", "PurOrdDetail", "WorkOrdMaster",
+                     "NbrMaster", "NbrDetail", "qms_", "crm_", "mdp_stg_",
+                 })
+            Assert.DoesNotContain(src, code);
+    }
+
+    // ───────────── ⑫ TenantA_KpiCannotAggregateTenantB ─────────────
+
+    /// <summary>
+    /// 结果 KPI 的字典查询仍须按租户收口(平台默认行 tenant_id=0 与本租户行),
+    /// 且作用域一律服务端解析,不接受调用方传入。
+    /// </summary>
+    [Fact]
+    public void ResultKpi_StaysTenantScoped()
+    {
+        var code = MonitoringCode;
+        Assert.Contains("x.TenantId == S8ConfigScope.GlobalTenantId", code);
+        Assert.Contains("x.TenantId == tenantId", code);
+
+        var controller = ReadCode("Controllers/S8/AdoS8MonitoringController.cs");
+        Assert.Contains("_scope.ResolveAsync()", controller);
+        Assert.DoesNotContain("ClearFilter", controller);
+    }
+}

+ 55 - 117
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8MonitoringService.cs

@@ -17,32 +17,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;
-    // S8-STEP4-KPI-CURRENT-VALUE-CLOSURE-1:ORDER_DELIVERY_RATE / PO_DELIVERY_RATE / WO_COMPLETION_RATE
-    // 由 DEMO_BASELINE 常量改为订单链路真实聚合,各自依赖以下三张表。
-    private readonly SqlSugarRepository<AdoS8OrderFlowOrder> _orderFlowOrderRep;
-    private readonly SqlSugarRepository<AdoS8OrderFlowProcurementPivot> _procurementPivotRep;
-    private readonly SqlSugarRepository<AdoS8OrderFlowManufacturingProcess> _manufacturingProcessRep;
+    // S8-REAL-DATA-PRESENTATION-1:原先这里注入了 ado_s8_order_flow_order / _procurement_pivot /
+    // _manufacturing_process / _stage 四个 SEED 仓储,用于把种子数据算成「当前 KPI」。
+    // 四项 KPI 已改为如实 PENDING_REAL(见 ComputeResultKpiCurrentAsync),依赖随之移除 ——
+    // 本服务从此与 ado_s8_order_flow_* **无任何运行期事实依赖**。
 
     public S8MonitoringService(
         SqlSugarRepository<AdoS8Exception> rep,
         SqlSugarRepository<AdoS8ExceptionType> typeRep,
         SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
-        SqlSugarRepository<AdoS8MonitorMetric> metricRep,
-        SqlSugarRepository<AdoS8OrderFlowStage> orderFlowStageRep,
-        SqlSugarRepository<AdoS8OrderFlowOrder> orderFlowOrderRep,
-        SqlSugarRepository<AdoS8OrderFlowProcurementPivot> procurementPivotRep,
-        SqlSugarRepository<AdoS8OrderFlowManufacturingProcess> manufacturingProcessRep)
+        SqlSugarRepository<AdoS8MonitorMetric> metricRep)
     {
         _rep = rep;
         _typeRep = typeRep;
         _deptRep = deptRep;
         _metricRep = metricRep;
-        _orderFlowStageRep = orderFlowStageRep;
-        _orderFlowOrderRep = orderFlowOrderRep;
-        _procurementPivotRep = procurementPivotRep;
-        _manufacturingProcessRep = manufacturingProcessRep;
     }
 
     /// <summary>
@@ -794,11 +783,18 @@ public class S8MonitoringService : ITransient
     }
 
     /// <summary>
-    /// 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)。
+    /// 结果 KPI 汇总。来源:ado_s8_monitor_metric WHERE mechanism='RATIO' AND is_result_kpi=1
+    /// (保留字典顺序与 metricName)。不写 DB / 不接 period / 不启用 watch_rule。
+    ///
+    /// <para>S8-REAL-DATA-PRESENTATION-1 当前口径:
+    /// <c>IQC_PASS_RATE</c> → <c>DEMO_BASELINE</c>(常量,前端「当前基线」,如实标注);
+    /// 其余全部 → <c>PENDING_REAL</c>(无当前值,前端「待真实接入」)。
+    /// 曾经的 <c>ORDER_FLOW_CALC</c> 一路已全部撤除——它算的是 seed,详见
+    /// <see cref="ComputeResultKpiCurrentAsync"/>。</para>
+    ///
+    /// <para>⚠️ 本段历史注释曾写「4 个 KPI → DEMO_BASELINE / 1 个 → ORDER_FLOW_CALC」,
+    /// 与其后的实现长期不符(实际是 4 个走 ORDER_FLOW_CALC、仅 IQC 走 DEMO_BASELINE)。
+    /// 已按现行实现改写,勿再据旧文案推断行为。</para>
     /// </summary>
     /// <summary>
     /// S8-CONFIG-AUTHORITY-FIX-1:结果 KPI 的「覆盖解析 → 启用门禁」。
@@ -888,10 +884,15 @@ public class S8MonitoringService : ITransient
             }
         }
 
+        // S8-REAL-DATA-PRESENTATION-1:envelope 的 provenance 必须由 items 真实构成推出。
+        // 旧实现无条件写死 "MIXED_BASELINE",前端渲染「当前基线 · 部分订单链路计算」——
+        // 在 items 为空(当前实况:字典 5 行全部 enabled=0)或全部为基线/待接入时,
+        // 这句话是一个没有任何计算支撑的断言,属于把「没算」说成「部分算了」。
+        var hasRealCalc = items.Exists(i => i.Source == "ORDER_FLOW_CALC");
         return new AdoS8ResultKpiSummaryDto
         {
             Items = items,
-            Source = "MIXED_BASELINE",
+            Source = hasRealCalc ? "MIXED_BASELINE" : "DICTIONARY_MOCK",
         };
     }
 
@@ -909,79 +910,42 @@ public class S8MonitoringService : ITransient
     };
 
     /// <summary>
-    /// S8-STEP4-KPI-CURRENT-VALUE-CLOSURE-1:结果 KPI 当前值的真实聚合分发。
-    /// 返回 null 表示该指标不由订单链路计算(调用方回落 DEMO_BASELINE / PENDING_REAL)。
-    /// 统一口径:只统计 tenant/factory 内未删除、且 <c>order_code</c> 非空的**订单关联业务记录**;
-    /// order_code 为空的 BASELINE_PPT / DEMO 演示装置行不是业务记录,一律排除。
-    /// </summary>
-    private async Task<(decimal? CurrentValue, string Source, string Remark)?> ComputeResultKpiCurrentAsync(
-        string metricCode, long tenantId) => metricCode switch
-    {
-        "ORDER_DELIVERY_RATE"    => await ComputeOrderDeliveryRateAsync(tenantId),
-        "PO_DELIVERY_RATE"       => await ComputePoDeliveryRateAsync(tenantId),
-        "WO_COMPLETION_RATE"     => await ComputeWoCompletionRateAsync(tenantId),
-        "ORDER_FLOW_CYCLE_RATIO" => await ComputeOrderFlowCycleRatioItemAsync(tenantId),
-        _ => null,
-    };
-
-    /// <summary>
-    /// 订单交付满足率 = 已完工订单中 actual_cycle_days &lt;= target_cycle_days 的占比。
-    /// 分母只含 actual_cycle_days 已回填的订单(在制订单尚无实际周期,纳入会系统性低估)。
-    /// </summary>
-    private async Task<(decimal? CurrentValue, string Source, string Remark)> ComputeOrderDeliveryRateAsync(
-        long tenantId)
-    {
-        var rows = await _orderFlowOrderRep.AsQueryable()
-            .Where(x => !x.IsDeleted && x.TenantId == tenantId
-                && x.ActualCycleDays != null)
-            .Select(x => new { x.ActualCycleDays, x.TargetCycleDays })
-            .ToListAsync();
-        const string remark = "基于订单链路已完工订单 actual_cycle_days/target_cycle_days 计算,在制订单不纳入分母";
-        if (rows.Count == 0) return (null, "ORDER_FLOW_CALC_EMPTY", remark);
-        var numerator = rows.Count(x => x.ActualCycleDays!.Value <= x.TargetCycleDays);
-        return (Math.Round(numerator * 100m / rows.Count, 1), "ORDER_FLOW_CALC", remark);
-    }
-
-    /// <summary>
-    /// 到货达成率 = 采购透视中 status='green'(按期到货)的占比;yellow/red 均计入分母。
-    /// </summary>
-    private async Task<(decimal? CurrentValue, string Source, string Remark)> ComputePoDeliveryRateAsync(
-        long tenantId)
-    {
-        var rows = await _procurementPivotRep.AsQueryable()
-            .Where(x => !x.IsDeleted && x.TenantId == tenantId
-                && x.OrderCode != null && x.OrderCode != "")
-            .Select(x => x.Status)
-            .ToListAsync();
-        const string remark = "基于订单关联采购透视 status 计算:green 为按期到货,yellow/red 计入分母";
-        if (rows.Count == 0) return (null, "ORDER_FLOW_CALC_EMPTY", remark);
-        var numerator = rows.Count(s => s == "green");
-        return (Math.Round(numerator * 100m / rows.Count, 1), "ORDER_FLOW_CALC", remark);
-    }
-
-    /// <summary>
-    /// 工单完工率 = 订单关联制造工序 achievement_rate 的算术平均(存储为 0~1 比率,×100 转百分比)。
+    /// S8-REAL-DATA-PRESENTATION-1:结果 KPI「当前值」的真实 Authority 分发点。
+    /// 返回 null = 该指标当前没有真实计算,调用方回落 DEMO_BASELINE / PENDING_REAL。
+    ///
+    /// <para><b>这里为什么一个计算都没有了</b>:ORDER_DELIVERY_RATE / PO_DELIVERY_RATE /
+    /// WO_COMPLETION_RATE / ORDER_FLOW_CYCLE_RATIO 此前分别读
+    /// <c>ado_s8_order_flow_order</c> / <c>_procurement_pivot</c> / <c>_manufacturing_process</c> /
+    /// <c>_stage</c>,并以 <c>Source="ORDER_FLOW_CALC"</c>(前端渲染「订单链路计算」)
+    /// 宣称是对当前业务的实算。但这四张表**唯一的写入方是 SeedData**,实库全部行
+    /// <c>data_source='SEED'</c>,四个查询也从不过滤 SEED —— 数字不是业务事实,标注是错的。</para>
+    ///
+    /// <para><b>为何不改成读中台而是直接不出值</b>(逐项核对,四项均无等价 Authority):
+    /// <list type="bullet">
+    ///   <item>ORDER_DELIVERY_RATE:<c>dwd_t8_order_shipment_fulfillment</c> 是「行级发运及时率 / 按 biz_date」,
+    ///         与「订单周期 actual &lt;= target」不同口径,换算须自行发明时间窗并改分母粒度;</item>
+    ///   <item>PO_DELIVERY_RATE:<c>mdp_std_purchase_order_completion</c> **既无承诺到期日、也无实际到货日**
+    ///         (<c>observed_at</c> / <c>source_update_time</c> 在表注释中被明确标注「禁止冒充业务完成时间」),
+    ///         按期到货率在现有中台里根本不可算;</item>
+    ///   <item>WO_COMPLETION_RATE:<c>dwd_t8_work_order_mfg_fulfillment</c> 粒度是
+    ///         (biz_date, 工单, 物料) 的窗口完成率,与「工序 achievement_rate 算术平均」不同,
+    ///         换算须自行选定 biz_date 与再聚合规则;</item>
+    ///   <item>ORDER_FLOW_CYCLE_RATIO:中台没有任何对象承载 S8 五阶段的 planned/actual days。</item>
+    /// </list>
+    /// 按「不得为了保住卡片而发明公式」的口径,四项一律不出当前值,落 PENDING_REAL
+    /// (前端「待真实接入」),与 IQC_PASS_RATE 的如实标注是同一范式。</para>
+    ///
+    /// <para>⚠️ 真实 Authority 就位后在此接回并改回具体 Source;
+    /// <b>不要改回读 ado_s8_order_flow_*</b> —— 那是 seed,不是业务事实。</para>
     /// </summary>
-    private async Task<(decimal? CurrentValue, string Source, string Remark)> ComputeWoCompletionRateAsync(
-        long tenantId)
+    private static Task<(decimal? CurrentValue, string Source, string Remark)?> ComputeResultKpiCurrentAsync(
+        string metricCode, long tenantId)
     {
-        var rows = await _manufacturingProcessRep.AsQueryable()
-            .Where(x => !x.IsDeleted && x.TenantId == tenantId
-                && x.OrderCode != null && x.OrderCode != "" && x.AchievementRate != null)
-            .Select(x => x.AchievementRate)
-            .ToListAsync();
-        const string remark = "基于订单关联制造工序 achievement_rate 平均值计算(0~1 比率换算百分比)";
-        if (rows.Count == 0) return (null, "ORDER_FLOW_CALC_EMPTY", remark);
-        return (Math.Round(rows.Sum(x => x!.Value) * 100m / rows.Count, 1), "ORDER_FLOW_CALC", remark);
+        _ = metricCode;
+        _ = tenantId;
+        return Task.FromResult<(decimal? CurrentValue, string Source, string Remark)?>(null);
     }
 
-    /// <summary>ORDER_FLOW_CYCLE_RATIO 适配到统一分发签名;计算逻辑保持不变。</summary>
-    private async Task<(decimal? CurrentValue, string Source, string Remark)> ComputeOrderFlowCycleRatioItemAsync(
-        long tenantId)
-    {
-        var (cv, source) = await ComputeOrderFlowCycleRatioAsync(tenantId);
-        return (cv, source, "基于已完成订单链路阶段 actual_days/planned_days 计算,pending 阶段暂不纳入");
-    }
 
     /// <summary>
     /// 生效目标值:rows 已按 (0,0) 平台默认 ‖ 本租户覆盖取回并按「租户行优先」去重,
@@ -990,30 +954,4 @@ public class S8MonitoringService : ITransient
     /// </summary>
     private static decimal? ResolveEffectiveTarget(AdoS8MonitorMetric m) => m.DefaultTargetRatio;
 
-    /// <summary>
-    /// 候选 A:actual_days IS NOT NULL 的已完成阶段中 actual_days <= planned_days 的占比。
-    /// pending 阶段不纳入 denominator;不依赖 status 字段;按 tenant 过滤(S8-TENANT-ONLY-BATCH6)。
-    /// </summary>
-    private async Task<(decimal? CurrentValue, string Source)> ComputeOrderFlowCycleRatioAsync(long tenantId)
-    {
-        try
-        {
-            var stages = await _orderFlowStageRep.AsQueryable()
-                .Where(x => !x.IsDeleted && x.TenantId == tenantId
-                    && 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");
-        }
-    }
 }