|
|
@@ -3,9 +3,11 @@ using Admin.NET.Plugin.AiDOP.Entity.S8.OrderFlow;
|
|
|
namespace Admin.NET.Plugin.AiDOP.SeedData;
|
|
|
|
|
|
/// <summary>
|
|
|
-/// ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t2b:ORDER_REVIEW_PLAN_CALC 阶段 L2 子节点种子。
|
|
|
-/// 20 单 × 5 子节点 = 100 行;SO-2026-001 按 PPT 第 2 页真值;其余 19 单按 (orderIdx, substepIdx)
|
|
|
-/// 派生 multiplier,确定性、可重入,无随机数 / DateTime.Now。
|
|
|
+/// S8-ORDER-EXECUTION-ORDER-REVIEW-SUBSTEP-PERSIST-1:ORDER_REVIEW_PLAN_CALC 阶段 L2 子节点种子。
|
|
|
+/// 20 单 × 5 子节点 = 100 行;每单的 L2 由该单 ORDER_REVIEW_PLAN_CALC 主节点 plannedDays / actualDays 严格回卷生成:
|
|
|
+/// - L2 piHours 合计 = plannedDays × 8
|
|
|
+/// - L2 actualHours 合计 = actualDays × 8(actualDays = null 时全部 pending)
|
|
|
+/// - 状态由 actualHours 与 piHours 比较得出
|
|
|
/// </summary>
|
|
|
[IncreSeed]
|
|
|
public class S8OrderFlowSubstepSeedData : ISqlSugarEntitySeedData<AdoS8OrderFlowSubstep>
|
|
|
@@ -18,7 +20,10 @@ internal static class S8OrderFlowSubstepDataset
|
|
|
internal const long SubstepIdBase = 1329909120000L;
|
|
|
internal const string FlowCode = "ORDER_REVIEW_PLAN_CALC";
|
|
|
|
|
|
- /// <summary>L2 五子节点固定顺序与 PI 基线工时。</summary>
|
|
|
+ /// <summary>1 工作日 = 8 工时(业务规则常量)。</summary>
|
|
|
+ internal const decimal HoursPerDay = 8m;
|
|
|
+
|
|
|
+ /// <summary>L2 五子节点固定顺序与 PI 基线工时(plannedDays=5 时合计 40h 与主节点对齐)。</summary>
|
|
|
internal static readonly (string Code, string Name, decimal PiHours)[] L2Def =
|
|
|
{
|
|
|
("OPINION_REVIEW", "意见评审", 8m),
|
|
|
@@ -28,29 +33,14 @@ internal static class S8OrderFlowSubstepDataset
|
|
|
("CONTRACT_SEAL", "合同盖章", 2m),
|
|
|
};
|
|
|
|
|
|
- /// <summary>SO-2026-001 PPT 第 2 页 L2 真值:actual_hours / status,与 L2Def 同序。</summary>
|
|
|
- private static readonly (decimal Actual, string Status)[] Ppt001L2 =
|
|
|
- {
|
|
|
- (14.4m, "red"),
|
|
|
- (14.2m, "yellow"),
|
|
|
- ( 7.0m, "green"),
|
|
|
- (12.0m, "yellow"),
|
|
|
- ( 2.0m, "green"),
|
|
|
- };
|
|
|
+ /// <summary>L2 PI 权重(合计 1.00;用于 plannedHours 拆分)。</summary>
|
|
|
+ private static readonly decimal[] PiWeight = { 0.20m, 0.30m, 0.20m, 0.25m, 0.05m };
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 19 个非 PPT 单的 L2 派生 multiplier 桶。
|
|
|
- /// bucketIdx = (idx - 2 + substepIdx) mod 5,对应 5 种 (multiplier, status) 组合。
|
|
|
- /// status 满足:multiplier ≤ 1.00 = green;1.00 < multiplier ≤ 1.25 = yellow;> 1.25 = red。
|
|
|
- /// </summary>
|
|
|
- private static readonly (decimal Mult, string Status)[] L2Buckets =
|
|
|
- {
|
|
|
- (0.90m, "green"),
|
|
|
- (1.10m, "yellow"),
|
|
|
- (1.40m, "red"),
|
|
|
- (1.00m, "green"),
|
|
|
- (1.20m, "yellow"),
|
|
|
- };
|
|
|
+ /// <summary>L2 延误权重(合计 1.00;当 actualHours > plannedHours 时把 delay 分配到各子节点)。</summary>
|
|
|
+ private static readonly decimal[] DelayWeight = { 0.35m, 0.25m, 0.15m, 0.20m, 0.05m };
|
|
|
+
|
|
|
+ /// <summary>L2 行回卷结果,供同批次 unit 种子复用同一份真值。</summary>
|
|
|
+ internal readonly record struct L2Row(decimal PiHours, decimal? ActualHours, string Status);
|
|
|
|
|
|
public static IEnumerable<AdoS8OrderFlowSubstep> BuildSubsteps()
|
|
|
{
|
|
|
@@ -58,27 +48,13 @@ internal static class S8OrderFlowSubstepDataset
|
|
|
foreach (var spec in S8OrderFlowDataset.Specs)
|
|
|
{
|
|
|
var orderId = S8OrderFlowDataset.OrderIdBase + spec.Idx;
|
|
|
- var isPpt = spec.OrderCode == "SO-2026-001";
|
|
|
- var scenario = isPpt ? "PPT" : "DEMO";
|
|
|
+ var scenario = spec.OrderCode == "SO-2026-001" ? "PPT" : "DEMO";
|
|
|
+ var rows = BuildL2Rows(spec);
|
|
|
|
|
|
for (var i = 0; i < L2Def.Length; i++)
|
|
|
{
|
|
|
- var (code, name, piHours) = L2Def[i];
|
|
|
- decimal actual;
|
|
|
- string status;
|
|
|
-
|
|
|
- if (isPpt)
|
|
|
- {
|
|
|
- actual = Ppt001L2[i].Actual;
|
|
|
- status = Ppt001L2[i].Status;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- var bucketIdx = ((spec.Idx - 2) + i) % L2Buckets.Length;
|
|
|
- var bucket = L2Buckets[bucketIdx];
|
|
|
- actual = decimal.Round(piHours * bucket.Mult, 1);
|
|
|
- status = bucket.Status;
|
|
|
- }
|
|
|
+ var (code, name, _) = L2Def[i];
|
|
|
+ var row = rows[i];
|
|
|
|
|
|
yield return new AdoS8OrderFlowSubstep
|
|
|
{
|
|
|
@@ -88,9 +64,9 @@ internal static class S8OrderFlowSubstepDataset
|
|
|
OrderFlowCode = FlowCode,
|
|
|
SubstepCode = code,
|
|
|
SubstepName = name,
|
|
|
- PiHours = piHours,
|
|
|
- ActualHours = actual,
|
|
|
- Status = status,
|
|
|
+ PiHours = row.PiHours,
|
|
|
+ ActualHours = row.ActualHours,
|
|
|
+ Status = row.Status,
|
|
|
SortNo = i + 1,
|
|
|
ScenarioCode = scenario,
|
|
|
DataSource = "SEED",
|
|
|
@@ -104,12 +80,100 @@ internal static class S8OrderFlowSubstepDataset
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// <summary>OPINION_REVIEW 在 100 行 substep 中的全局 substep_id(供 unit seed 反查 FK)。</summary>
|
|
|
+ /// <summary>
|
|
|
+ /// 按订单 ORDER_REVIEW_PLAN_CALC 主节点回卷生成 5 个 L2 行。
|
|
|
+ /// 输入:spec → BuildLifecycleValues(spec)[0] = order_review 一级阶段真值。
|
|
|
+ /// 输出:5 个 L2Row,piHours/actualHours/status 严格满足合计回卷。
|
|
|
+ /// </summary>
|
|
|
+ internal static L2Row[] BuildL2Rows(S8OrderFlowDataset.OrderSpec spec)
|
|
|
+ {
|
|
|
+ var stage = S8OrderFlowStageDataset.BuildLifecycleValues(spec)[0];
|
|
|
+ var plannedHours = stage.kpi * HoursPerDay;
|
|
|
+ decimal? actualHours = stage.status == "pending"
|
|
|
+ ? (decimal?)null
|
|
|
+ : stage.actualDays * HoursPerDay;
|
|
|
+
|
|
|
+ var piHoursArr = AllocateByWeight(plannedHours, PiWeight);
|
|
|
+
|
|
|
+ if (actualHours == null)
|
|
|
+ {
|
|
|
+ return piHoursArr
|
|
|
+ .Select(pi => new L2Row(pi, null, "pending"))
|
|
|
+ .ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ decimal[] actualHoursArr;
|
|
|
+ if (actualHours.Value > plannedHours)
|
|
|
+ {
|
|
|
+ var delay = actualHours.Value - plannedHours;
|
|
|
+ var delayShare = AllocateByWeight(delay, DelayWeight);
|
|
|
+ actualHoursArr = new decimal[L2Def.Length];
|
|
|
+ for (var i = 0; i < L2Def.Length; i++)
|
|
|
+ actualHoursArr[i] = decimal.Round(piHoursArr[i] + delayShare[i], 1);
|
|
|
+ // 末位尾差修正,保证合计严格等于 actualHours
|
|
|
+ FixTail(actualHoursArr, actualHours.Value);
|
|
|
+ }
|
|
|
+ else if (actualHours.Value == plannedHours)
|
|
|
+ {
|
|
|
+ actualHoursArr = (decimal[])piHoursArr.Clone();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // actualHours < plannedHours:按 PI 权重等比例压缩
|
|
|
+ actualHoursArr = AllocateByWeight(actualHours.Value, PiWeight);
|
|
|
+ }
|
|
|
+
|
|
|
+ return Enumerable.Range(0, L2Def.Length)
|
|
|
+ .Select(i => new L2Row(piHoursArr[i], actualHoursArr[i], ClassifyByPi(actualHoursArr[i], piHoursArr[i])))
|
|
|
+ .ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 按权重把 total 拆成与 weights 等长的 decimal 数组:
|
|
|
+ /// - 前 N-1 项 = Round(total × weights[i], 1)
|
|
|
+ /// - 末项 = total - 前 N-1 项之和(保留 1 位)
|
|
|
+ /// 保证返回数组合计严格等于 total。
|
|
|
+ /// </summary>
|
|
|
+ internal static decimal[] AllocateByWeight(decimal total, decimal[] weights)
|
|
|
+ {
|
|
|
+ var result = new decimal[weights.Length];
|
|
|
+ decimal headSum = 0m;
|
|
|
+ for (var i = 0; i < weights.Length - 1; i++)
|
|
|
+ {
|
|
|
+ var v = decimal.Round(total * weights[i], 1);
|
|
|
+ result[i] = v;
|
|
|
+ headSum += v;
|
|
|
+ }
|
|
|
+ result[weights.Length - 1] = decimal.Round(total - headSum, 1);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>把末项调整为 total - 前 N-1 项之和,吸收 Round 累积误差。</summary>
|
|
|
+ private static void FixTail(decimal[] arr, decimal total)
|
|
|
+ {
|
|
|
+ decimal headSum = 0m;
|
|
|
+ for (var i = 0; i < arr.Length - 1; i++) headSum += arr[i];
|
|
|
+ arr[arr.Length - 1] = decimal.Round(total - headSum, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>状态判定:≤PI=green / 超出 ≤20%=yellow / 否则 red。</summary>
|
|
|
+ internal static string ClassifyByPi(decimal actual, decimal pi)
|
|
|
+ {
|
|
|
+ if (pi <= 0m) return "green";
|
|
|
+ if (actual <= pi) return "green";
|
|
|
+ return (actual - pi) / pi <= 0.20m ? "yellow" : "red";
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>按 (orderIdx, substepIdx) 反查 substep_id(与 BuildSubsteps 的 seq 编号严格一致)。</summary>
|
|
|
+ public static long SubstepIdAt(int orderIdx, int substepIdx)
|
|
|
+ {
|
|
|
+ // 每单 5 个 substep,按 spec.Idx 顺序连续编号;spec.Idx 从 1 起。
|
|
|
+ return SubstepIdBase + (orderIdx - 1) * L2Def.Length + substepIdx + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>OPINION_REVIEW(sort_no=1)在 100 行 substep 中的全局 substep_id。保留以兼容现有调用。</summary>
|
|
|
public static long OpinionReviewSubstepId(int orderIdx)
|
|
|
{
|
|
|
- // 每单 5 个 substep,OPINION_REVIEW 是第 1 个(sort_no=1)。
|
|
|
- // seq 顺序:(idx=1 的 5 个) → (idx=2 的 5 个) → ...
|
|
|
- // 第 idx 单的 OPINION_REVIEW seq = (idx - 1) * 5 + 1。
|
|
|
- return SubstepIdBase + (orderIdx - 1) * 5L + 1L;
|
|
|
+ return SubstepIdAt(orderIdx, 0);
|
|
|
}
|
|
|
}
|