research(rectification): scoring-method scaffold — LR weights, absence, precision follow-up pipelines on v4, verdicts pending v5 (BUG-1091)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-29 19:06:45 +08:00
co-authored by Claude Fable 5.1
parent 5f84637ea9
commit abf2ad6aea
12 changed files with 8092 additions and 1 deletions
+1
View File
@@ -121,3 +121,4 @@ Do not open new product surfaces before at least one of these four lanes is clos
- 定论页(先读这个):`<repo>/docs/research/rectification_minute_resolution_closure_2026_09_14.md`
- 两个已被证伪的假设:加权重能拉开候选;簇合并把候选并没了(合并从未触发)。
- 未解决的只剩「区间内部并列」,目前唯一被证明有效的信息源是用户提供的带年月新经历。
- 2026-09-29 打分方法研究(脚手架,判定待 v5):`<repo>/docs/research/rectification_scoring_research_2026_09_29.md`——似然比校准权重 / 缺席证据 / 精度追问三条流水线已在 v4 上跑通,所有判定 `pending_v5`;任务书 `docs/tasks/TASK-rectification-scoring-research-20260929.md`,前置 `TASK-rectification-holdout-expansion-20260929.md`。
@@ -94,3 +94,8 @@
- 任务书和 BUG-689 邀请语依据里的「1 分钟 ≈ 1.1 天大运边界位移」有误,实测中位 3.8 天(1.3–5.9)。45 天闸门对应 8–34 分钟,不是 40 分钟。
- 新增答错容错:答错 1 题,真值在区间 98–100%;答错 2 题,±30 / ±60 上 7–10% 的回放会挤出真值(两道反答 = 8 分 = 淘汰线)。
- 全文见 `docs/research/rectification_offline_research_2026_09_26.md`。
## 9. 后续(2026-09-29 打分方法研究单,脚手架阶段)
- §1.3「加权重类改法全部不过门」说的是**人拍权重**。2026-09-29 研究单换了路线:每条规则的权重由数据估(似然比,leave-one-case-out),允许负权重(只奖不罚是结构性问题),KP / Pranapada / D60 作为特征进入由数据定权重;另测「缺席证据」与「对已说事件追问月份」。
- 脚手架与 v4 流水线见 `docs/research/rectification_scoring_research_2026_09_29.md`;**判定必须在 v5(≥60 例)上做**,v4 上的数字只是调试参考(BUG-1090 / BUG-1091)。本页 §1–§7 的结论不因此改变。
@@ -0,0 +1,139 @@
# 生时校正打分方法研究:似然比权重 / 缺席证据 / 精度追问(脚手架阶段,2026-09-29)
- 任务书:`docs/tasks/TASK-rectification-scoring-research-20260929.md`;进度:`docs/tasks/PROGRESS-rectification-scoring-research-20260929.md`
- 分支:`codex/rectification-scoring-research-20260929`,基线 `origin/staging` @ `6e393780`(开工时 head;任务书写作时为 `5febb111`,其间只有文档提交)。
- 性质:**离线测量,不改线上代码、打分、阈值、出题闸门、Skill、冻结文件。** 研究计分器是 `precision_gate_lib.score_event_with_policy`(09-14 / 09-26 已证与线上逐分相同)加上不计分的"额外观察",通过 `build_event_contribution_matrix(row_provider=…)` 走线上同一条矩阵 / 出题 / 六题回放链。
- 数据:`references/real_case_calibration/minute_rectification_holdout_v4.json`,20 例公开 Rodden-AA。**这是开放集回放,不是盲测;本页所有数字都是 v4 调试参考,任务书规定判定只能在 v5(≥60 例)上做,所以每一项的判定栏都是 `pending_v5`。**
- 口径:ayanamsa `raman`,node mode `mean`,步长 2 分钟,半径 ±10 / ±30 / ±60;六题回放(`ASK_COUNT = 6`,按真值作答,题目由线上 G0 出题器按**线上矩阵**出一次、各方案共用);交付区间 = 未淘汰且落后头名不足 8 分的簇并集。两种先验都报:`raw`(引擎行原始分,09-14 / 09-26 口径)与 `percent`(线上 `relative_support` 百分比口径;似然比方案用带温度的 softmax 后验,见 R-A)。
- 复跑:`PYTHONHASHSEED=0 python3 scripts/research/scoring_research.py --cache-dir <临时目录>`(约 14 分钟)→ `docs/research/scoring_research_{lr,absence,precision_followup}_2026_09_29.json`。同机两次运行 JSON 逐字节一致(见进度记录)。
- 判门:`precision_gate_lib.gate_verdict`(头名不降**且**宽度下降、覆盖不降);JSON 里以 `debug_verdict_v4` 记录,`verdict` 字段一律 `pending_v5`。
## 结论
| 项 | 一句话结论(v4 调试参考) | 判定 |
| --- | --- | --- |
| R-0 脚手架 | 研究计分器与线上 `score_candidates` 在 20 例 × 3 档 = 60 个 case-radius 上**逐分对账 0 差**(分数与规则 id 都相同);每个 (候选, 事件, 采样日期) 记录 42 个特征(线上规则 25 个 + 额外观察 17 个) | 完成 |
| R-A 似然比权重 | 流水线跑通。v4 上:留一法 `raw` 口径头名 / 覆盖与基线持平或略动(±30 A3 头名 0.55 → 0.65、宽度 33 → 31),引擎自身头名(答题前)±10 0.30 → 0.40–0.45;`percent` 口径的 softmax 后验即使按训练集拟合温度仍过度自信,把真值挤出区间(±10 squeezed 3–5 例)。20 例上 42 个特征里 31–38 个的置信区间跨 0 | `pending_v5` |
| R-B 缺席证据 | 流水线跑通。v4 上按卡片同等强度(2 分)扣分会大量挤出真值(±60 覆盖 1.00 → 0.40)——**v4 的事件表是传记摘录,不是完整时间线,空白年不等于没发生**;真人口述同样不完整。0.5 分档不挤真值但头名下降 | `pending_v5`(v4 信号明确为负,v5 上若不翻转即关闭) |
| R-C 精度追问 | 流水线跑通。把日精度经历降成"只记得年"后,**每例平均只有 0.2 / 0.45 / 0.7 件经历在知道月份后能把候选分到两边**(±10 只有 2/20 例有);对这些例子把那一件恢复到月精度重算先验(`C1_prior`)在 ±30 上 7 例头名 0.71 vs 该 7 例的 C0,其余档位样本太少 | `pending_v5` |
## R-0 · 脚手架与对账
- `scripts/research/scoring_research_lib.py`
- `make_recording_provider`:对每个 legacy 单事件请求,用 `score_event_with_policy`(V0 基线)+ 线上的过运 / Ashtakavarga / Shadbala 辅助(取 context 里线上算好的结果)产出**与线上完全相同**的 evidence,同时把该候选在该事件下的额外观察写进 `FeatureStore`:
- 线上规则(记为特征):`vim_{md,ad,pd}_domain_{house,lord,varga}`、`{label}_functional_{benefic,malefic}_auxiliary`、`narayana_{md,ad}_domain_house`、`{label}_arudha_auxiliary`、`controlled_transit_{jupiter,saturn}_domain_house`、`ashtakavarga_target_house_{support,pressure}_auxiliary`、`shadbala_sthana_drik_naisargika_{support,pressure}_auxiliary`。`event_kind:*`、`no_domain_activation`、`*_auxiliary_not_primary` 不作特征。
- 额外观察(线上算了没计分):`kp_target_cusp_{sublord,starlord}_is_vim_{md,ad,pd}`、`kp_asc_sublord_is_vim_{md,ad,pd}`(`feature.kp_cusps.houses`,Placidus + Krishnamurti)、`vim_{md,ad,pd}_domain_varga_d60`(D60 现算)、`{pranapada,hora_lagna,ghati_lagna,bhava_lagna}_in_target_house`(`_fine_minute_indices`)。
- `reconcile_rows`:逐分比较分数与规则 id。**60/60 case-radius 差异 0。**
- `event_feature_matrix`:特征值 = 该规则在该事件采样日期上命中的比例(年精度 12 个采样日、月 3 个、日 1 个),与线上按采样日平均分数同口径。
- 真值标签:候选属于真值所在的**签名簇**(`raw_signature_clusters`)。
- 测试:`tests/test_scoring_research.py`(10 条:LR 估计、留一法不泄漏、特征矩阵、规则过滤、加权 provider、缩放答案、缺席年、精度升降、百分比、以及 v4 公开案例 ±10 对账 0 差)。
## R-A · 似然比校准权重
**方法。** 对每个特征 f,`log LR_present = log P(f | 真值簇) − log P(f | 非真值簇)`,拉普拉斯平滑 α = 1(`(Σx + 1) / (N + 2)`);`log LR_absent` 为朴素贝叶斯互补项。每档半径单独估。
| 方案 | 特征 | 权重规则 |
| --- | --- | --- |
| A1 | 只用线上 25 条规则 | 只奖不罚:`max(log LR_present, 0)`,不用 absent 项 |
| A2 | 加 17 个额外观察 | 同上 |
| A3 | 同 A2 | 朴素贝叶斯:present + absent 两项都用,允许负权重 |
候选分 = Σ 事件 Σ 特征(x·w_present + (1−x)·w_absent),经线上矩阵的事件类型系数、精度权重、采样日平均与大运边界邻近项(后者按线上原样附加,不重加权)。两个只从**训练折**估的标定量:`scale`(研究分的例内极差中位 → 线上极差中位,让 `raw` 回放的 ±2 / 8 分线意义不变)、`temperature`(Platt 单温度,最大化训练例真值簇的对数后验;网格 0.25–256),`percent` 先验 = softmax(分 / T) × 100。**留一法按案例留**(`loo_folds`),全集拟合另报只作参照。
### 六题回放(留一法;括号内为全集拟合)
`raw` 口径:
| 半径 | 方案 | 头名命中 | 真值在区间 | 宽度中位 | 引擎头名(答题前) | 挤出真值 | debug 判定 |
| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |
| ±10 | 基线 | 0.80 | 1.00 | 15 | 0.30 | 0 | — |
| ±10 | A1 | 0.70 (0.75) | 1.00 | 14 | 0.45 | 0 | no_benefit |
| ±10 | A2 | 0.75 (0.80) | 1.00 | 15 | 0.40 (0.50) | 0 | no_benefit |
| ±10 | A3 | 0.80 (0.85) | 1.00 | 15 | 0.40 (0.50) | 0 | no_benefit |
| ±30 | 基线 | 0.55 | 1.00 | 33 | 0.20 | 0 | — |
| ±30 | A1 | 0.55 (0.70) | 1.00 | 32 | 0.25 | 0 | benefit(宽度 −1) |
| ±30 | A2 | 0.60 (0.65) | 1.00 | 34 | 0.10 (0.30) | 0 | no_benefit |
| ±30 | A3 | 0.65 (0.75) | 1.00 | 31 | 0.20 (0.45) | 0 | benefit(宽度 −2) |
| ±60 | 基线 | 0.40 | 1.00 | 56 | 0.05 | 0 | — |
| ±60 | A1 | 0.45 (0.50) | 1.00 | 61 | 0.15 (0.20) | 0 | no_benefit |
| ±60 | A2 | 0.55 (0.60) | 1.00 | 61 | 0.00 (0.10) | 0 | no_benefit |
| ±60 | A3 | 0.50 (0.70) | **0.95** | 61 | 0.05 (0.30) | 1 | no_benefit |
`percent` 口径(基线 = 线上比例百分比;A* = 拟合温度的 softmax 后验):
| 半径 | 方案 | 头名命中 | 真值在区间 | 宽度中位 | 挤出真值 | 温度(各折) |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| ±10 | 基线 | 0.90 | 1.00 | 11 | 0 | — |
| ±10 | A1 / A2 / A3 | 0.60 / 0.50 / 0.55 | 0.80 / 0.85 / 0.75 | 7 / 7 / 5 | 4 / 3 / 5 | 1–2 / 1 / 0.5–1 |
| ±30 | 基线 | 0.85 | 1.00 | 33 | 0 | — |
| ±30 | A1 / A2 / A3 | 0.65 / 0.55 / 0.55 | 0.95 / 0.80 / 0.90 | 31 / 10 / 13 | 1 / 4 / 2 | 2–4 / 1–2 / 1–2 |
| ±60 | 基线 | 0.80 | 1.00 | 53 | 0 | — |
| ±60 | A1 / A2 / A3 | 0.65 / 0.50 / 0.40 | 1.00 / 0.80 / 0.80 | 57 / 32 / 38 | 0 / 4 / 4 | 4–8 / 2–4 / 2 |
**读法(v4,仅供 v5 前设计参考,不是结论)。**
1. 对账通过后,`raw` 口径的留一法数字与基线基本持平:似然比权重没有让真值掉出区间(±60 A3 一例除外),也没有明显收窄;引擎答题前的头名在 ±10 从 0.30 到 0.40–0.45、±60 A1 从 0.05 到 0.15,全集拟合再高一些——**全集与留一法的差距就是 20 例过拟合的量**。
2. `percent` 口径下 softmax 后验**过度自信**:即使温度按训练折拟合,仍把 15–25% 例子的真值挤出区间。校准曲线(A3 留一法)在 ±10 上 [0.20,0.30) 桶预测 0.24 / 实际 0.40、[0.50,0.70) 预测 0.58 / 实际 0.40、[0.70,1.00) 只有 1 个点且错;±30 / ±60 高桶几乎无样本。要上线必须先解决后验的标定,而不是先验本身。
3. **42 个特征里 31 / 36 / 38 个(±10 / ±30 / ±60)的 5–95% 置信区间跨 0**(案例级 bootstrap 200 次)。±10 上区间不跨 0 的:`ashtakavarga_target_house_pressure_auxiliary`(+0.41)、`vim_ad_domain_varga`(+0.30)、`vim_md_domain_varga`(+0.27)、`ghati_lagna_in_target_house` / `hora_lagna_in_target_house`(+0.31)、`pranapada_in_target_house`(**−0.36**)、`vim_ad_arudha_auxiliary`(−0.21)、`narayana_ad_domain_house`(−0.21)、`kp_target_cusp_sublord_is_vim_ad`(−0.19)。方向与线上手拍权重相反的几条(Arudha、Narayana AD、KP AD 子主为负)正是 20 例上说不清的地方——这就是任务书要先扩集的原因。
4. 稳健性(留一法 A3,`raw`):±7 天抖动与 ±1–3 月平移三档真值在区间 0.95–1.00;答错 1 题 0.96–1.00、答错 2 题 0.91–0.99(与 09-26 基线同量级)。`percent` 口径下三项都更差(0.70–0.90),同样是后验过自信的表现。
## R-B · 缺席证据
**方法。** 只用训练事件(留一件 holdout 不用)。每个领域取第一件与最后一件带年月事件之间的年份,没有该领域事件的年份 = 缺席年。对每个缺席 (领域, 年) 用出题器同一判定函数 `_evaluate_contexts(month=None)` 在代表候选上求"这一年该领域是否激活"的分边;分得开就当作一道答了"没有"的题,按 0.5 / 1.0 / 2.0 分扣(卡片是 2.0;低于卡片强度的扣分不计强冲突、不会淘汰)。之后照常六题。漏说稳健性:随机删掉 1–2 件训练事件再算缺席年(每例 5 次,种子固定)。
`raw` 口径六题回放(`percent` 口径同向,见 JSON):
| 半径 | 方案 | 头名命中 | 真值在区间 | 宽度中位 | 挤出真值 | 漏说 1 件(100 次回放)真值在区间 | 漏说 2 件 |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| ±10 | B0 | 0.80 | 1.00 | 15 | 0 | — | — |
| ±10 | 缺席 @0.5 分 | 0.70 | 1.00 | 15 | 0 | 1.00 | 1.00 |
| ±10 | 缺席 @1.0 分 | 0.70 | 1.00 | 14 | 0 | 1.00 | 1.00 |
| ±10 | 缺席 @2.0 分(=卡片) | 0.55 | **0.85** | 13 | 3 | 0.94 | 0.94 |
| ±30 | B0 | 0.55 | 1.00 | 33 | 0 | — | — |
| ±30 | 缺席 @0.5 分 | 0.50 | 1.00 | 34 | 0 | 1.00 | 1.00 |
| ±30 | 缺席 @1.0 分 | 0.45 | 1.00 | 33 | 0 | 1.00 | 1.00 |
| ±30 | 缺席 @2.0 分 | 0.25 | **0.70** | 19 | 6 | 0.80 | 0.87 |
| ±60 | B0 | 0.40 | 1.00 | 56 | 0 | — | — |
| ±60 | 缺席 @0.5 分 | 0.35 | 1.00 | 68 | 0 | 1.00 | 1.00 |
| ±60 | 缺席 @1.0 分 | 0.25 | 1.00 | 61 | 0 | 1.00 | 1.00 |
| ±60 | 缺席 @2.0 分 | 0.10 | **0.40** | 9 | 12 | 0.54 | 0.73 |
每例平均缺席年 17.95;能分边的缺席题 2.05 / 4.30 / 6.15 道(有题的例子 12 / 17 / 17)。三档 debug 判定全部 no_benefit。
读法:
1. **按卡片强度扣,真值被大量挤出**(±60 覆盖 1.00 → 0.40)。原因不是方法错,而是 v4 的事件表是传记摘录:那些"空白年"里公众人物多半确有该领域的事,只是没进数据集;扣分等于用不完整的时间线否定真值。真人口述同样不完整(用户不会把每年的事都说全),所以这条在 v5 上大概率也是负的——**若 v5 上不翻转,R-B 关闭**。
2. 0.5 / 1.0 分档不挤真值(覆盖 1.00)但头名一律下降、宽度不降,等于只加噪声。漏说 1–2 件在低强度下不伤覆盖,在卡片强度下把覆盖压到 0.54–0.94。
## R-C · 精度追问可行性
**方法。** 把每例所有日精度经历降成"只记得年"(v4 没有月精度)作为口述形态 `C0_spoken`,先验按线上重算。对每件训练中的日精度经历,用 `_evaluate_contexts(month=真实月份)` 判断"知道月份后能否把代表候选分到两边";能分的按信息增益排序取前 1 / 2 件:`C{k}_prior`(那件恢复到月精度重算先验)、`C{k}_probe`(当作答"是"的卡)、`C{k}_both`。
| 半径 | 每例可追问件数均值 | ≥1 件的例子 | ≥2 件的例子 |
| --- | ---: | ---: | ---: |
| ±10 | 0.20 | 2 / 20 | 1 / 20 |
| ±30 | 0.45 | 7 / 20 | 1 / 20 |
| ±60 | 0.70 | 10 / 20 | 3 / 20 |
| 半径 | 方案 | n | 头名命中(raw) | 真值在区间 | 宽度中位 | 同 n 例的 C0(raw 头名 / 宽度) |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| ±10 | C1_prior / C1_probe | 2 | 1.00 / 0.50 | 1.00 | 16 | 见 JSON per-arm(样本太少) |
| ±30 | C1_prior | 7 | 0.71 | 1.00 | 31 | debug 判定 benefit(percent 口径头名 1.00) |
| ±30 | C1_probe | 7 | 0.57 | 1.00 | 33 | no_benefit |
| ±60 | C1_prior / C1_probe | 10 | 0.40 / 0.40 | 1.00 | 58 / 63 | no_benefit |
**读法。** 可追问的经历很少(与 09-29 打字经历研究一致:绝大多数经历落在所有候选都一样的大运段),但在 ±30 上"恢复到月精度重算先验"(走线上已有的打字经历通道,不加卡片)对那 7 例有正向信号;"当作答是的卡"没有。样本太少,只能说值得在 v5 上按同口径重测;产品层实现(只对落在候选分歧点的那一件追问月份,算在定向追问 2 条额度内)要等 v5 判定。
## 给产品的话
- 三条流水线都跑通、与线上对账 0 差,**但没有任何一条在 v4 上能下结论**,20 例上 42 个特征有四分之三分不清正负。这一轮交付的是"能在 v5 上一键复跑的测量工具",不是新打分。
- v4 上唯一方向明确的信号是负的:把口述时间线的空白当"没发生"会伤真值(R-B)。
- v5 交付后的复跑命令:`PYTHONHASHSEED=0 python3 scripts/research/scoring_research.py --holdout references/real_case_calibration/minute_rectification_holdout_v5.json --dataset-label v5 --out-suffix v5_<日期>`。
## 附:复跑命令
```bash
PYTHONHASHSEED=0 python3 scripts/research/scoring_research.py --cache-dir /tmp/scoring_ctx # 约 14 分钟,写三份 JSON
PYTHONHASHSEED=0 python3 scripts/research/scoring_research.py --limit 3 --radii 10 --fast --no-write # 开发用
python3 -m pytest tests/test_scoring_research.py -q
```
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff