S8OrderFlowFinalAssemblySeedData.cs 16 KB

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