|
|
@@ -117,3 +117,76 @@ dualrun stage1 --all --idempotency
|
|
|
为什么需要它:`computeRecordReward` / `distributeAmount` 是包内私有,harness 在另一个包。
|
|
|
为什么不能让 harness 自己再写一份公式:那会变成"用第二份实现校验第一份",
|
|
|
一旦两份都错、错法一致,双跑会给出全绿结论 —— 这正是要避免的自证。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 13. 两个必踩坑(合成 case 专属)
|
|
|
+
|
|
|
+这两条是本轮实测踩出来的,**踩中时给出的数字看起来极像真缺陷**,务必先怀疑它们再怀疑算法。
|
|
|
+
|
|
|
+### 13.1 造数之后必须重读输入
|
|
|
+
|
|
|
+合成 case 会在事务内改数据(`UPDATE ... SET cost=0` / `SET teacher_reward_type=2` / 插规则)。
|
|
|
+PHP 结算用的是**造数之后**的世界,harness 若沿用造数之前的 `cost` / `teacher_reward_type` / `income`,
|
|
|
+target 侧就会拿着旧基数去算,得到一个"基数不同"的假 DEFECT。
|
|
|
+
|
|
|
+实战症状(都是 harness 的错,不是算法差异):
|
|
|
+
|
|
|
+| case | 假 DEFECT 的差值 | 真实原因 |
|
|
|
+|---|---|---|
|
|
|
+| `syn-income-zero-min-*` | `+110.000` | target 用造数前的 `income=1200`,PHP 用造数后的 `income=0`(应夹下限 130) |
|
|
|
+| `syn-rewardtype2-*` | `+122.500` | target 用造数前的 `teacher_reward_type=1`,PHP 用造数后的 `2`(应走固定课酬 70) |
|
|
|
+
|
|
|
+**修法**:探针在造数后重读 `income` / `student_number` / `teacher_reward_type` / 打卡行,作为 `inputsEffective`;
|
|
|
+造数前的快照只留作溯源(`inputsBeforeMutation`),target 一律用 `inputsEffective`。
|
|
|
+
|
|
|
+### 13.2 规则必须在事务内、造数之后读
|
|
|
+
|
|
|
+同一个坑的另一半:`getTeacherPaymentDetailStoredProcedure` 若在事务之前调用,拿到的是**造数前的旧规则**。
|
|
|
+于是 PHP 用注入的新规则算、target 用旧规则算 ⇒ 假 DEFECT。
|
|
|
+
|
|
|
+实战症状:
|
|
|
+
|
|
|
+| case | 假 DEFECT 的差值 | 真实原因 |
|
|
|
+|---|---|---|
|
|
|
+| `syn-tier-teacher_classify-*` | `+11.000` | PHP 命中注入的 `pct=33` 规则(181.5),target 用旧的 `pct=35`(192.5) |
|
|
|
+| `syn-tier-cert_tier-*` | `-93.680` | PHP 命中注入的 `cert` 规则(163.68),target 用旧的通用规则(70) |
|
|
|
+
|
|
|
+**修法**:规则读取挪进事务、造数之后;造数 case 的 `expectTier` 不再与"造数前的复刻判定"比对(必然不同),
|
|
|
+而由实返规则的字段自证。
|
|
|
+
|
|
|
+> 这两条合起来意味着:**合成 case 的"输入"不是 case 定义,而是造数之后的库状态。**
|
|
|
+> 任何在造数之前抓取的量(收入、规则、人数、结算方式)都可能是毒药。
|
|
|
+
|
|
|
+## 14. 两个环境坑(与业务无关,但会让人查半天)
|
|
|
+
|
|
|
+### 14.1 `SET SESSION timestamp` 不能用绑定参数
|
|
|
+
|
|
|
+```php
|
|
|
+// ✗ 报错:SQLSTATE[42000] 1232 Incorrect argument type to variable 'timestamp'
|
|
|
+$db->createCommand('SET SESSION timestamp = :ts', [':ts' => (string) $epoch])->execute();
|
|
|
+
|
|
|
+// ✓ timestamp 是"数值型会话变量",必须给数值字面量
|
|
|
+$db->createCommand('SET SESSION timestamp = ' . (int) $epoch)->execute();
|
|
|
+```
|
|
|
+
|
|
|
+`time_zone` 是字符串变量,用绑定没问题;`timestamp` 不行。
|
|
|
+
|
|
|
+### 14.2 子进程输出必须加显式分隔标记
|
|
|
+
|
|
|
+PHP CLI 的 notice/warning 会混进 stdout(legacy 的 `config/params.php` 在 CLI 下就有 `Undefined index: REQUEST_SCHEME`)。
|
|
|
+探针因此以 `##AC15_JSON_BEGIN##` 标记 JSON 起点,Go 侧按标记切。
|
|
|
+
|
|
|
+**不能用"最后一个 `{`"**:JSON 内部有嵌套对象(打卡行、规则),会切在中间,报
|
|
|
+`invalid character ']' after top-level value` —— 而且报出来的片段看起来像"探针输出被截断",
|
|
|
+很容易误判成 PHP 侧的问题。
|
|
|
+
|
|
|
+## 15. 容差口径
|
|
|
+
|
|
|
+三层判据的容差 = **`0.005 + |Δ预期|`**:
|
|
|
+
|
|
|
+- `0.005` = 舍入到"分"的最大误差(半分)
|
|
|
+- `|Δ预期|` = `|Δincome| × pct/100`,即基数差传递过来的量
|
|
|
+- 两者相加 = 理论上允许的最大差
|
|
|
+
|
|
|
+**不要为了消红放宽容差。** 容差是判据,不是目标;1.5 分容差会把一个真实的 1 分级算法 bug 吞掉。
|