S8OrderFlowFinalAssemblySeedData.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. using Admin.NET.Plugin.AiDOP.Entity.S8.OrderFlow;
  2. namespace Admin.NET.Plugin.AiDOP.SeedData;
  3. /// <summary>
  4. /// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:
  5. /// 总装发货「主门禁里程碑 / 并行准备」两表 seed 算法 SOT。
  6. ///
  7. /// ──────────────────────────────────────────────────────────────────────
  8. /// SEED 落地路径(关键):
  9. /// 本项目 SEED 数据落地走 UpdateScripts SQL(DELETE+INSERT),不是 SqlSugar IncreSeed 自动写入。
  10. /// 这与 procurement 1.0.148.sql / UpdateScripts SQL 模式一致。
  11. /// 本类只作为算法 SOT,本批次实际行数据由
  12. /// UpdateScripts/1.0.153.sql
  13. /// 的 INSERT 语句落地(DDL 创建表 + DELETE 幂等清理 + INSERT 144 行)。
  14. /// 因此本类不挂 [IncreSeed];保留算法 owner 身份,便于未来 IMPORT/AGG 接入对账。
  15. /// ──────────────────────────────────────────────────────────────────────
  16. ///
  17. /// 业务口径(拍板版本):
  18. /// 父级:ado_s8_order_flow_stage 中 stage_code=FINAL_ASSEMBLY_DELIVERY 行的 planned_days=3.0、actual_days/status。
  19. /// 非 pending 订单 = 14 green (actual=3.0) + 1 yellow (SO-2026-011 actual=4.0);5 pending 不生成 ORDER_LEVEL 行。
  20. ///
  21. /// Gate baseline 5 行:
  22. /// planned_offset_days = (0.6, 1.2, 1.8, 2.4, 3.0)(累计里程碑)。
  23. /// actual_offset_days = planned_offset_days、variance_days = 0、status = green(清洁 PPT 基线)。
  24. /// impacted_order_count = 0、risk_order_count = 0(baseline 视图不暴露单订单分布)。
  25. /// top_risk_type/label = NULL(baseline 行无风险摘要)。
  26. ///
  27. /// Gate order-level:每个非 pending 单 5 行:
  28. /// scale = stage.actual_days / 3.0
  29. /// 前 4 个门禁 actual_offset_days = planned_offset_days × scale,
  30. /// 末位 SHIPMENT_CONFIRMATION 尾差修正:actual_offset_days = stage.actual_days。
  31. /// variance_days = actual_offset_days - planned_offset_days。
  32. /// status:variance≤0 green / ≤0.3 yellow / 否则 red(seed 时固化)。
  33. /// impacted_order_count = (status != green) ? 1 : 0。
  34. /// risk_order_count = (status == red) ? 1 : 0。
  35. /// top_risk_type/label:status != green 时取 GateRiskMap,否则 NULL。
  36. ///
  37. /// Parallel baseline 4 行:
  38. /// kitting_rate = (0.9500, 0.9700, 0.9200, 0.9600)。
  39. /// status 按 ≥0.95 green / ≥0.85 yellow / 否则 red 派生(SHIPPING_PLAN 0.92 = yellow,其它 3 项 green)。
  40. /// impacted/risk = 0,top_risk_type/label = NULL。
  41. ///
  42. /// Parallel order-level:每个非 pending 单 4 行:
  43. /// stage.status == "green" → kitting_rate = baseline(无调整)。
  44. /// stage.status == "yellow" → kitting_rate = baseline - 0.0500(4 项统一下调,保 yellow 范围)。
  45. /// status 按相同阈值派生。
  46. /// impacted/risk/top_risk_type/label 同 Gate 规则。
  47. ///
  48. /// 真实数据源(IMPORT / AGG)接入后,service 优先采用 IMPORT/AGG,同 (order_code, gate/prep_code) SEED 行仅兜底。
  49. /// </summary>
  50. // 不挂 [IncreSeed]:实际数据落地路径见 1.0.153.sql。
  51. public class S8OrderFlowFinalAssemblyGateSeedData : ISqlSugarEntitySeedData<AdoS8OrderFlowFinalAssemblyGate>
  52. {
  53. public IEnumerable<AdoS8OrderFlowFinalAssemblyGate> HasData()
  54. => S8OrderFlowFinalAssemblyDataset.BuildGateBaselineRows()
  55. .Concat(S8OrderFlowFinalAssemblyDataset.BuildGateOrderLevelRows());
  56. }
  57. // 不挂 [IncreSeed]:实际数据落地路径见 1.0.153.sql。
  58. public class S8OrderFlowFinalAssemblyParallelSeedData : ISqlSugarEntitySeedData<AdoS8OrderFlowFinalAssemblyParallel>
  59. {
  60. public IEnumerable<AdoS8OrderFlowFinalAssemblyParallel> HasData()
  61. => S8OrderFlowFinalAssemblyDataset.BuildParallelBaselineRows()
  62. .Concat(S8OrderFlowFinalAssemblyDataset.BuildParallelOrderLevelRows());
  63. }
  64. internal static class S8OrderFlowFinalAssemblyDataset
  65. {
  66. // ─────────────── ID base ranges(接续 manufacturing 1329909210000 段) ───────────────
  67. internal const long GateBaselineIdBase = 1329909220000L;
  68. internal const long GateOrderLevelIdBase = 1329909230000L;
  69. internal const long ParallelBaselineIdBase = 1329909240000L;
  70. internal const long ParallelOrderLevelIdBase = 1329909250000L;
  71. internal const string ScenarioBaseline = "BASELINE_PPT";
  72. internal const string ScenarioOrderLevel = "ORDER_LEVEL";
  73. // FINAL_ASSEMBLY_DELIVERY 在 5 阶段流水中固定为索引 4(order_review→product_design→material_procurement→body_production→final_assembly_shipping)。
  74. private const int FinalAssemblyStageIndex = 4;
  75. private const decimal StageBaselineDays = 3.0m;
  76. // ─────────────── Gate baseline 真值(累计里程碑) ───────────────
  77. private sealed record GateBaseline(
  78. int SortNo, string Code, string Name, decimal PlannedOffsetDays, string OwnerSideText,
  79. string RiskType, string RiskLabel);
  80. private static readonly GateBaseline[] GateBaselines =
  81. {
  82. new(10, "ASSEMBLY_COMPLETION", "总装完工", 0.6m, "总装线", "ASSEMBLY_DELAY", "总装节拍偏差"),
  83. new(20, "QUALITY_RELEASE", "质量放行", 1.2m, "质量·终检", "QUALITY_RELEASE_DELAY", "质量放行等待"),
  84. new(30, "PACKAGING_COMPLETION", "包装完成", 1.8m, "包装线", "PACKAGING_DELAY", "包装完成偏差"),
  85. new(40, "GOODS_HANDOVER", "成品交接", 2.4m, "成品库", "HANDOVER_DELAY", "成品交接等待"),
  86. new(50, "SHIPMENT_CONFIRMATION", "发运确认", 3.0m, "发运协调", "SHIPMENT_DELAY", "发运确认等待"),
  87. };
  88. // ─────────────── Parallel baseline 真值(齐套率) ───────────────
  89. private sealed record ParallelBaseline(
  90. int SortNo, string Code, string Name, decimal KittingRate, string OwnerSideText,
  91. string RiskType, string RiskLabel);
  92. private static readonly ParallelBaseline[] ParallelBaselines =
  93. {
  94. new(10, "PACKAGING_MATERIAL_PREP", "包装材料准备", 0.9500m, "包装线·物料", "PACKAGING_MATERIAL_SHORTAGE", "包装材料齐套"),
  95. new(20, "LABEL_DOC_PREP", "标签随箱资料准备", 0.9700m, "技术·质量", "LABEL_DOC_PENDING", "标签随箱资料齐套"),
  96. new(30, "SHIPPING_PLAN", "发运计划准备", 0.9200m, "发运协调", "SHIPPING_PLAN_PENDING", "发运计划齐套"),
  97. new(40, "SHIPPING_DOC_PREP", "出货单据准备", 0.9600m, "计划·商务", "SHIPPING_DOC_PENDING", "出货单据齐套"),
  98. };
  99. /// <summary>yellow 单 kitting_rate 统一下调量(保 ≥0.85 yellow 阈值)。</summary>
  100. private const decimal YellowKittingPenalty = 0.0500m;
  101. // ────────────────────────────────────────────────────────────
  102. // Gate 行
  103. // ────────────────────────────────────────────────────────────
  104. public static IEnumerable<AdoS8OrderFlowFinalAssemblyGate> BuildGateBaselineRows()
  105. {
  106. long seq = 0;
  107. foreach (var g in GateBaselines)
  108. {
  109. yield return new AdoS8OrderFlowFinalAssemblyGate
  110. {
  111. Id = GateBaselineIdBase + (++seq),
  112. OrderId = null,
  113. OrderCode = null,
  114. GateCode = g.Code,
  115. GateName = g.Name,
  116. PlannedOffsetDays = g.PlannedOffsetDays,
  117. ActualOffsetDays = g.PlannedOffsetDays,
  118. VarianceDays = 0m,
  119. Status = "green",
  120. ImpactedOrderCount = 0,
  121. RiskOrderCount = 0,
  122. TopRiskType = null,
  123. TopRiskLabel = null,
  124. OwnerSideText = g.OwnerSideText,
  125. SortNo = g.SortNo,
  126. ScenarioCode = ScenarioBaseline,
  127. DataSource = "SEED",
  128. TenantId = 1,
  129. FactoryId = 1,
  130. CreatedAt = S8OrderFlowDataset.CreatedAt,
  131. UpdatedAt = null,
  132. IsDeleted = false,
  133. };
  134. }
  135. }
  136. public static IEnumerable<AdoS8OrderFlowFinalAssemblyGate> BuildGateOrderLevelRows()
  137. {
  138. long seq = 0;
  139. foreach (var spec in S8OrderFlowDataset.Specs)
  140. {
  141. var lifecycle = S8OrderFlowStageDataset.BuildLifecycleValues(spec);
  142. var stage = lifecycle[FinalAssemblyStageIndex];
  143. if (stage.status == "pending") continue;
  144. var A = stage.actualDays;
  145. var scale = A / StageBaselineDays;
  146. var orderId = S8OrderFlowDataset.OrderIdBase + spec.Idx;
  147. for (var i = 0; i < GateBaselines.Length; i++)
  148. {
  149. var g = GateBaselines[i];
  150. decimal actualOffset;
  151. if (i == GateBaselines.Length - 1)
  152. {
  153. // 末位门禁尾差修正,确保 SHIPMENT_CONFIRMATION.actual_offset_days = stage.actual_days。
  154. actualOffset = decimal.Round(A, 3);
  155. }
  156. else
  157. {
  158. actualOffset = decimal.Round(g.PlannedOffsetDays * scale, 3);
  159. }
  160. var variance = decimal.Round(actualOffset - g.PlannedOffsetDays, 3);
  161. var status = ClassifyGateStatus(variance);
  162. var (riskType, riskLabel) = status == "green"
  163. ? (null, null)
  164. : ((string?)g.RiskType, (string?)g.RiskLabel);
  165. yield return new AdoS8OrderFlowFinalAssemblyGate
  166. {
  167. Id = GateOrderLevelIdBase + (++seq),
  168. OrderId = orderId,
  169. OrderCode = spec.OrderCode,
  170. GateCode = g.Code,
  171. GateName = g.Name,
  172. PlannedOffsetDays = g.PlannedOffsetDays,
  173. ActualOffsetDays = actualOffset,
  174. VarianceDays = variance,
  175. Status = status,
  176. ImpactedOrderCount = status == "green" ? 0 : 1,
  177. RiskOrderCount = status == "red" ? 1 : 0,
  178. TopRiskType = riskType,
  179. TopRiskLabel = riskLabel,
  180. OwnerSideText = g.OwnerSideText,
  181. SortNo = g.SortNo,
  182. ScenarioCode = ScenarioOrderLevel,
  183. DataSource = "SEED",
  184. TenantId = 1,
  185. FactoryId = 1,
  186. CreatedAt = S8OrderFlowDataset.CreatedAt,
  187. UpdatedAt = null,
  188. IsDeleted = false,
  189. };
  190. }
  191. }
  192. }
  193. // ────────────────────────────────────────────────────────────
  194. // Parallel 行
  195. // ────────────────────────────────────────────────────────────
  196. public static IEnumerable<AdoS8OrderFlowFinalAssemblyParallel> BuildParallelBaselineRows()
  197. {
  198. long seq = 0;
  199. foreach (var p in ParallelBaselines)
  200. {
  201. yield return new AdoS8OrderFlowFinalAssemblyParallel
  202. {
  203. Id = ParallelBaselineIdBase + (++seq),
  204. OrderId = null,
  205. OrderCode = null,
  206. PrepCode = p.Code,
  207. PrepName = p.Name,
  208. KittingRate = p.KittingRate,
  209. Status = ClassifyParallelStatus(p.KittingRate),
  210. ImpactedOrderCount = 0,
  211. RiskOrderCount = 0,
  212. TopRiskType = null,
  213. TopRiskLabel = null,
  214. OwnerSideText = p.OwnerSideText,
  215. SortNo = p.SortNo,
  216. ScenarioCode = ScenarioBaseline,
  217. DataSource = "SEED",
  218. TenantId = 1,
  219. FactoryId = 1,
  220. CreatedAt = S8OrderFlowDataset.CreatedAt,
  221. UpdatedAt = null,
  222. IsDeleted = false,
  223. };
  224. }
  225. }
  226. public static IEnumerable<AdoS8OrderFlowFinalAssemblyParallel> BuildParallelOrderLevelRows()
  227. {
  228. long seq = 0;
  229. foreach (var spec in S8OrderFlowDataset.Specs)
  230. {
  231. var lifecycle = S8OrderFlowStageDataset.BuildLifecycleValues(spec);
  232. var stage = lifecycle[FinalAssemblyStageIndex];
  233. if (stage.status == "pending") continue;
  234. var orderId = S8OrderFlowDataset.OrderIdBase + spec.Idx;
  235. var stageStatus = stage.status;
  236. foreach (var p in ParallelBaselines)
  237. {
  238. var kittingRate = stageStatus switch
  239. {
  240. "yellow" => decimal.Round(p.KittingRate - YellowKittingPenalty, 4),
  241. _ => p.KittingRate, // green:保持 baseline
  242. };
  243. var status = ClassifyParallelStatus(kittingRate);
  244. var (riskType, riskLabel) = status == "green"
  245. ? (null, null)
  246. : ((string?)p.RiskType, (string?)p.RiskLabel);
  247. yield return new AdoS8OrderFlowFinalAssemblyParallel
  248. {
  249. Id = ParallelOrderLevelIdBase + (++seq),
  250. OrderId = orderId,
  251. OrderCode = spec.OrderCode,
  252. PrepCode = p.Code,
  253. PrepName = p.Name,
  254. KittingRate = kittingRate,
  255. Status = status,
  256. ImpactedOrderCount = status == "green" ? 0 : 1,
  257. RiskOrderCount = status == "red" ? 1 : 0,
  258. TopRiskType = riskType,
  259. TopRiskLabel = riskLabel,
  260. OwnerSideText = p.OwnerSideText,
  261. SortNo = p.SortNo,
  262. ScenarioCode = ScenarioOrderLevel,
  263. DataSource = "SEED",
  264. TenantId = 1,
  265. FactoryId = 1,
  266. CreatedAt = S8OrderFlowDataset.CreatedAt,
  267. UpdatedAt = null,
  268. IsDeleted = false,
  269. };
  270. }
  271. }
  272. }
  273. // ────────────────────────────────────────────────────────────
  274. // status classifiers(seed-time 一次性,运行期不再重判)
  275. // ────────────────────────────────────────────────────────────
  276. /// <summary>gate status:variance ≤ 0 green / ≤ 0.3 yellow / 否则 red。</summary>
  277. private static string ClassifyGateStatus(decimal variance)
  278. {
  279. if (variance <= 0m) return "green";
  280. if (variance <= 0.3m) return "yellow";
  281. return "red";
  282. }
  283. /// <summary>parallel status:kitting_rate ≥ 0.95 green / ≥ 0.85 yellow / 否则 red。</summary>
  284. private static string ClassifyParallelStatus(decimal kittingRate)
  285. {
  286. if (kittingRate >= 0.95m) return "green";
  287. if (kittingRate >= 0.85m) return "yellow";
  288. return "red";
  289. }
  290. }