Эх сурвалжийг харах

docs(ac15): §18.3 第 3 条补注 —— 小数位要按表查,本仓已两种形态并存

课酬链 3→2 收敛、分账链 2→2 无收敛,两种都实测过。写第三张表的金额列时
必须先去 `spdump columns` 查位数,不能沿用"一律 2 位"或"一律要收敛"的习惯。

顺带修掉 §18.3 表格里两处被早前编辑打断的空行(第 7/8、9/10 行之间),
它们会让 markdown 把第 8、10 条渲染成表格外的段落。

验证:`go build ./...` OK | `go test ./internal/academic/dualrun/stage2/` ok
Q 7 цаг өмнө
parent
commit
33a8d4c08c

+ 13 - 3
internal/academic/dualrun/stage2/README.md

@@ -650,6 +650,7 @@ if out.HasCampusAccount { out.Campus = sc.account() }   // 无行 → 保持零
 | `campus>0` | 49 | 36 | 13 |
 | `campus>0 且 total>0`(第三条守卫**真正可达**) | **21** | **11** | **10** |
 
+
 `49` 里有 28 行 `total<=0`,那些 legacy 是被**第二条**守卫挡的,而 target 的 `ClearBalance`
 同样有 `totalBalance<=0 → 3120` ⇒ 两侧一致、**不分叉**。所以真正会分叉的只有 **10 行**:
 
@@ -1245,17 +1246,26 @@ $sql = "SELECT SUM(t1.discount_amount-t1.discount_amount_used) as amount FROM ..
 | **0** | **断言键写错 ⇒ 断言块整体不执行**(批 3a 真踩:`dKey` 用 `c.ActivityID`,而 `user_discount_refund` 没有 activity 入参 ⇒ ActivityID 恒 0 ⇒ 键永不匹配) | **零症状,且连绿色行都不生成** —— 它不跑,所以既不红也不绿,报告里那一栏干脆不存在 | ① 每个"按 key 取快照"的断言块**打一行"本块比对了 N 项"**,`N == 0` 判 **FAIL**(同存量哨兵 `nonzeroSamples > 0`);② 反向自检:把 key 换成不存在的值,该块必须**报"取不到快照"而不是静默跳过** |
 | 1 | **新字段对旧 case 为空,却被当成"两侧不等/相等"** | 旧 case 集体红或集体多一条假绿 | 空值必须映射成 `Skipped`;`nil` 与 `nil` **不许**比出"相等"。锁在 `TestActivityDomainApplicableNilIsNotEqual` |
 | 2 | **字段加错结构体**(`SideResult` 而不是 `SideSnapshot`,或反过来) | 编译期偶尔报 `undefined`;**若那个字段恰好没人读,就永远为 nil,零症状** | 每个"本 case 声明了就必须读到"的快照块,配一条**基建断言**:"两侧都读到了 + 条数 == 预期" |
-| 3 | **两个不同口径的列共用一个读函数** | 比对变松,永不报错(例:`float(10,2)` 与 `float(10,3)` 同表) | 读函数按口径**分成两个**(`readRound2` / `readRound3`),并加单测断言两者字符串不同 |
+| 3 | **两个不同口径的列共用一个读函数** | 比对变松,永不报错(例:`float(10,2)` 与 `float(10,3)` 同表) | 读函数按口径**分成两个**(`readRound2` / `readRound3`),并加单测断言两者字符串不同 |。
 | 4 | **行集"靠键配对"** | 少写一条 + 多写一条落在同一键上 ⇒ 互相抵消,绿 | 多重集(`map[tuple]int`),并把自增 id / 时间戳排除在 tuple 外 |
 | 5 | **闸门只数"非 Skipped 断言"** | 基建断言(残留 / 基线)永远为真 ⇒ 闸恒过 | 断言分 `Business` / `Infrastructure`,闸门只认业务;`Kind == 0` 算非业务(§18.2) |
 | **6** | **一个对象挂多个分类标签,然后"按标签计数"**(批 3a 真踩:`activityChainVerdict` 把命中的标签用 `" + "` 拼起来 ⇒ SA-2 / SC-2 同时是 `intentional-strict` 与 `零写入守卫`) | **两份数各自都对,加起来不等于总数** —— 报出 `12 + 2 + 1 + 2 = 17`,而该批只有 **15** 条。没有任何一条断言会红,因为它错在**记账**不在**判定** | 分类**唯一化**:`Case.ActivityShape()` 按优先级(`legacy-defect > strict > 零写入 > equality`)取**第一个**命中;统计只走 `ActivityShapeTally`,并把 `Σ N == len(cases)` 与"判定文案恰好命中 1 个标签"写成单测 |
 | **7** | **需要读库的断言写进"通用断言块"**(批 3c 真踩:精度口径那条要在原始 3 位精度上读一次种子行) | **红的是断言自己,而 case 是绿的。** 通用断言块跑在两侧各自 `Rollback()` **之后** ⇒ 种子行已随事务回滚,查回来 **0 行**、`SUM(...)` 返回 **NULL**(`sql: Scan error: converting NULL to int64 is unsupported`)。报告上是一条红,看着像"实现错了",实际是**断言读了一个已经不存在的数据** | 需要读库的断言只能放在 **per-side 快照**(事务内)或 **`xxxCompare` 阶段**(它拿到的是 `SideResult.Before/After` 快照)。本批的落地方式:在 `readArrears*` 的 SELECT 里**多读两列原始精度**(`ArrearsRow.RemainderRaw3/NetRaw3`,**不参与比对口径**),断言搬到 `arrearsCompare` |
-
 | **8** | **报数自伤:把两个门禁写成一句"全绿"**(批 3d 真踩:加了 case 却没改 `c2_refund_test.go` 的分母闸,`go test` FAIL,而提交信息写"53/53 PASS") | 双跑绿 + 单测红。**双跑红=实现有问题;单测红=分母闸在叫("分母变了就要重新核一遍")**。写成"全绿"等于**把一个还在叫的警报报成安静** —— 而这条闸存在的唯一目的,就是逼"加 case 的人解释一遍" | ①**任何数字一律从落盘输出 grep 出来再贴,不许回忆**;②**"全绿"这个词只允许用于刚刚贴出来的那段输出**;③提交信息里"go test"与"双跑"必须分两行写 |
 | **9** | **零区分力的第二种方向:假红**(批 3d 真踩:`trc.Recommended - trc.Recommended` 被写成"target 校区 Δ",恒 0) | **恒真断言**(F7)永远绿;**恒假/恒 0 断言**永远红 —— 两者都是**零区分力**,只是方向相反。 这次的红伪装成了 `TARGET-DEFECT`:我据此报了 P0 嫌疑,team-lead 读了三层实现、我跑了一次 probe,全部作废 | ①**凡是减法,先问"两个操作数是不是同一个来源"**;②**报红之前先证明这条断言能"红得对"** —— 拿一个"已知会变"的构型跑一遍,必须非 0(`TestDeltaOrderAndSelfSubtraction`);③Δ 类 detail **必须同时打 before / after / Δ 三个数**(打印形状本身就是判据:`Δ+0` 若伴随 `5000 → 5000`,读的人看到的是"没变"而不是"没加") |
-
 | **10** | **用 `Δ = After − Before` 断言,却不断言 Before 就是声明的基线**(批 3d 的 W4-B 逼出来的) | **`Δ = 0` 有两种相反含义:「没改」与「本来就是那个值」。** 后者等于**这条 case 根本没测到东西**——它全绿,但什么都没验。同一形态还有第三种脏:残留行恰好也是 0,Δ 全对而"建行分支"从未执行 | 任何 Δ 断言都要**同时**断言 Before 的形状(行是否存在、各列值是否等于声明)。本批落地:①种子改成**按 user 全删**(不带 campus 条件,残留行 campus 不同就删不掉);②`run.go` 加前置断言「声明 `Absent` ⇒ 两侧 Before 必须**读不到行**」;③断言 detail 里打 `Before:…行=%v`,让"Δ=0"一眼能看出是哪种 |
 
+> **第 3 条的补注(3d 之后):小数位要按表查,不能按习惯。**
+> 本仓现在**两种形态并存**,都实测过:
+>
+> | 表 | legacy | target | 形态 |
+> |---|---|---|---|
+> | `t_courses_student.remainder_receivable` / `net_receipts` | `decimal(10,3)` | `decimal(12,2)` | **3 → 2 收敛**(课酬链) |
+> | `t_user_divide_into.quota` / `total_income` / `total_income_payed` | `float(10,2)` | `decimal(12,2)` | **2 → 2 无收敛**(分账链) |
+>
+> ⇒ 做第三张表的金额列时,**先去 `spdump columns` 查位数**,不要沿用"我们一律按 2 位比"或"一律要收敛"的习惯。
+> 走 `readRound3` 只会多留一位、不会错;走 `readRound2` 而列是 3 位,就**静默丢掉分辨力**(见 §21.8 的 2694 行实测)
+
 第 10 条是本清单里**覆盖面最广**的一条:它对**所有**基于 Δ 的 case 成立,不只 W4-B。
 判据可以压成一句:**先证明"起点是对的",再谈"走了多远"。**