Selaa lähdekoodia

docs(ac15): §17 改「输入来源」口径 —— target 整库为空,两侧都是按 case 造

team-lead 纠正的口径我核过并全部成立:yuansiwei_2026 **整个库 0 行**(含资金表),
不是"活动表空"。所以"资金侧真实 / 活动侧种子"这个说法不准 —— target 从来没有真实数据。

新增 §17.0:把区别改成**输入来源**
  资金/机构/校区/人:原值复制真实行(INSERT…SELECT FROM finance_bk,同一套 ROUND 口径)
  活动/分账/课酬:构造行(C1ActivityPlan 之类的字面值,id 取 max+1 可控)
并补一处精度:两侧读的甚至不是同一个库(target ← finance_bk、legacy SP ← legacy_oracle),
能比靠的是每个 case 都跑的「两侧基线逐分相等」断言 —— "原值复制"也有前提断言,不是天然相等。

对外必带的一句:target 库无任何迁移数据,双跑验证的是「同一操作在两种实现下的等价性」,
**不验证迁移后的数据分布**;活动侧为构造行不是"没找真实数据",而是活动域未迁移、真实活动数据测不了。

同时补 §17.2:W4 的写入面是**三个域两个用户**
  资金(balance + total_recommended_amount + 107×2)
  活动(used_log + signed(status=2))
  课酬(嵌套 CALL sp_courses_student_counteract_arrears_course ×2 → t_courses_student)
⇒ 现有 SideSnapshot 三块全是 c.LegacyUserID 的,缺"推荐人账户"与"课酬应收"两个维度。
MDEOF
git log --oneline -1 && git status --porcelain | wc -l
Q 2 tuntia sitten
vanhempi
sitoutus
f62796335f
1 muutettua tiedostoa jossa 65 lisäystä ja 8 poistoa
  1. 65 8
      internal/academic/dualrun/stage2/README.md

+ 65 - 8
internal/academic/dualrun/stage2/README.md

@@ -1069,12 +1069,51 @@ hash / DefaultCases / 实跑 / PASS / FAIL / 崩溃 / Skipped
 
 ## 17. 活动链 9 支的**证据强度上限**(写在这里,因为它是这个批次的先天约束)
 
-三条实测事实(`spdump sql` / `spdump routines` / PHP 仓 `grep`,都可复跑):
+### 17.0 先说清"两侧的数据是怎么来的"——「输入来源」≠「真实度」
+
+**`yuansiwei_2026` 整个库一行数据都没有**(实测):
+
+```
+表                                 target   legacy
+t_user_expense                          0     8918
+t_user_expense_school                   0     5287
+t_user_expense_log                      0    90007
+t_campus                                0       63
+t_user                                  0     68107
+t_courses_activity                      0       90
+t_courses_activity_signed               0     1288
+t_user_divide_into                      0       86
+t_user_divide_into_log                  0      474
+t_courses_activity_user_discount        0       81
+t_courses_activity_used_log             0     8185
+t_course_payment                        0        7
+```
+
+所以**不能**说"资金侧是真实构型、活动侧是种子"——target 侧**从来没有真实数据**,两侧都是按 case 现造的。
+真正有区别的是**输入来源**:
+
+| 维度 | 资金 / 机构 / 校区 / 人 | 活动 / 分账 / 课酬 |
+|---|---|---|
+| 造法 | **原值复制真实行**:`INSERT … SELECT … FROM finance_bk.…`,同一套 ROUND 口径 | **构造行**:`C1ActivityPlan` 之类的字面值,id 取 `max+1` 可控 |
+| 为什么 | 真实行可用,复制最省事也最可信 | 活动域**未迁移**(target 侧虽有 schema 但 0 行)+ id 空间要可控 |
+
+⚠ 一处必须记住的精度:两侧读的**不是同一个库**——target 的基线来自只读源 **`finance_bk`**,
+legacy 侧 SP 读克隆 **`legacy_oracle`**。两者能比,靠的是每个 case 都跑的
+`两侧基线逐分相等` 那条显式断言。**"原值复制"也有前提断言,不是天然相等。**
+
+**对外必带的一句证据强度说明**:
+
+> target 库**无任何迁移数据**。双跑验证的是「**同一操作在两种实现下的等价性**」,
+> **不验证**迁移后的数据分布。活动侧为构造行 —— 这不是"没找真实数据",而是活动域未迁移,
+> **真实活动数据没测、也测不了**。
+
+### 17.1 三条实测事实
 
 1. **target 侧活动域 schema 齐、数据空**:
-   `t_courses_activity` / `_signed` / `_used_log` / `_user_discount` / `_user_discount_log` 全部 **0 行**;
+   `t_courses_activity` / `_signed` / `_used_log` / `_user_discount` / `_user_discount_log` 全部 **0 行**
+   (见 §17.0,其实**整个 target 库**都是 0 行);
    legacy 侧 `t_courses_activity` = 90、`t_courses_activity_signed` = 1288。
-   ⇒ **任何真实报名/真实赠送额度都只有 legacy 有,target 看不到。活动侧只能靠种子。**
+   ⇒ **任何真实报名 / 真实赠送额度都只有 legacy 有。活动侧只能构造行。**
 2. **9 支里只有 1 支碰资金表**:
    `sp_courses_activity_use_activity_recommend`(`t_user_expense` / `t_user_expense_school` /
    `t_user_expense_log` type=107 / `total_recommended_amount`,共 14 处引用)。
@@ -1091,12 +1130,30 @@ hash / DefaultCases / 实跑 / PASS / FAIL / 崩溃 / Skipped
 
    ⇒ 活动链批次的**证据强度上限**:
 
-   | 支 | 两侧数据 | 能证明什么 |
+   | 支 | **输入来源** | 能证明什么 |
    |---|---|---|
-   | 5 支 A 档(其中 4 支无资金写) | 活动侧种子 + 资金侧 n/a | "照 dump 正文实现对了" + "种下去的活动状态演化对" |
-   | `use_activity_recommend`(A 档唯一碰资金的) | 活动侧种子 + **资金侧可真实**(用户与推荐人都是真人真账户) | 上面两条 + 双表双列 `balance`/`total_recommended_amount` 同额 + 107 流水双发 |
-   | 3 支 B 档 | 两侧都是合成 | **只有**"照 dump 正文实现对了" |
-   | `user_discount_usable`(C 档) | 只读、无写 | 只有"返回值口径对了";且它是**死代码**,建议不进 `DefaultCases` |
+   | 5 支 A 档(其中 4 支无资金写) | 活动:构造行 | "照 dump 正文实现对了" + "构造行上的状态演化对" |
+   | `use_activity_recommend`(A 档唯一碰资金的) | 活动:构造行 | **资金:原值复制真实行 | 课酬:原值复制真实行**(`t_courses_student` 42050 行,其中 24133 行满足 `remainder_receivable>0 AND confirm_discount_userid>0`) | 上面两条 + 双表双列 `balance`/`total_recommended_amount` 同额 + 107 流水双发 + **嵌套 `CALL` 的欠费核销(第三域)** |
+   | 3 支 B 档 | 活动:构造行 | **只有**"照 dump 正文实现对了" |
+   | `user_discount_usable`(C 档) | 只读、无写 | 只有"返回值口径对了";它是**死代码**,建议不进 `DefaultCases` |
 
 **报告里必须逐支标注上表的档位,不能笼统写"9 支全绿"** ——
 B/C 档的绿与 A 档的绿不是同一种绿,混在一张总表里就是把"没验证过生产构型"读成"生产上验证过了"。
+
+### 17.2 W4(`use_activity_recommend`)的写入面是**三个域、两个用户**
+
+这是本批唯一跨域的一支,也是唯一需要新增快照维度的一支:
+
+| 域 | 写入 |
+|---|---|
+| 资金 | `t_user_expense_school` / `t_user_expense`(**可能 INSERT**)、`balance +m`、`total_recommended_amount +m`、`t_user_expense_log` **type=107 ×2**(用户 + 推荐人) |
+| 活动 | `t_courses_activity_used_log` INSERT(−m)、`t_courses_activity_signed`(activity_money_used=m, status=2) |
+| 课酬 | `CALL sp_courses_student_counteract_arrears_course` **×2** → `t_courses_student.remainder_receivable − / net_receipts +`,cursor 判据 `remainder_receivable>0 AND confirm_discount_userid>0 ORDER BY start_time ASC` |
+
+⇒ 现有 `SideSnapshot` 的 3 块(`Global` / `Campus` / `Campus2`)**全是 `c.LegacyUserID` 的**,
+不够用。缺两块:**推荐人账户**(否则"推荐人也 +m"不可断言,而它正是老带新的核心语义)
++ **课酬应收**(否则核销顺序与 `confirm_discount_userid>0` 过滤都验不了)。
+
+⚠ `t_user_expense_school` 的建行语句只写 `(userid, schoolid, campusid)` 三列 ⇒ 金额列
+`DEFAULT NULL` ⇒ `SET balance=balance+m` 仍是 **NULL** ⇒ **钱一分没到账、107 流水照写**。
+与 C1-11 同族但后果更重(不是"少扣",是"用户被告知发了奖励、余额没动"),线上有真实调用点。