# Devlog · H3:读序异常分 + `--layout` 自动路由收口

> 2026-06-11 · 里程碑 H3(见 [hardening-iteration.md](../plans/hardening-iteration.md))· 状态:✅ 二选一明确落地

## 背景

G2 已把版面模型 `--layout` 的 +0.18 收益做出来,但"这页该信几何还是信模型"
的**自动路由判据**三个几何假设全部数据否决(行填充率 / XY-cut 读序歧义分 /
左缘对齐度,见 [g2-correction devlog](2026-06-10-g2-correction.md))。H3 是
计划里的"最后一试":第 4 个候选——**确定性输出的读序异常分**(块重叠率 +
读序 y 回跳),同时补 quality 模块(M7)留空的 reading-order 异常项。

## 判据可分性验证(先探针,后落码)

吸取 G2 教训(判据 2 写进 Rust 又回退、留死代码),先用 Python 探针在
WIN/LOSE 标注样例上算分布,可分再落 Rust。指标:读序相邻块的 y 回跳率
(下一块顶边高于当前块 0.5 行高以上)+ x 重叠率。

| 文档 | 标注 | y 回跳率(mean/max) | x 重叠率 |
|---|---|---|---|
| amt_handbook_sample | **WIN**(该开) | 0.000 / 0.000 | 0.000 |
| normal_4pages | **WIN** | 0.021 / 0.083 | 0.239 |
| 2203.01017v2 | **LOSE**(不该开) | 0.124 / 0.250 | 0.184 |
| 2305.03393v1 | **LOSE** | 0.000 / 0.000 | 0.099 |

**不可分,且方向是反的**:
- y 回跳率上 amt(WIN)=0 与 2305(LOSE)=0 完全无法区分;LOSE 的 2203 反而**最高**;
- x 重叠率上 amt(WIN)=0 < 2203(LOSE)=0.184,重叠区大。

根因:读序异常分衡量的是"确定性 XY-cut 读序自身的几何挣扎度",但
①amt 这类设计感版面**几何读序其实很顺**(该开 layout 是因为标题/区块的
**语义分组**,几何看不出来);②2305 这类 clean 学术双栏 XY-cut **切得很干净**
(回跳 0),与 amt 无法区分;③2203 回跳高纯粹是子词级碎块噪声。这与前三个判据
失败同源:几何信号无法承载"语义版面 vs 干净版面"的区分。

## 决策(计划的"不可分"分支)

**关闭确定性自动路由路线。** 四个判据(行填充率 / XY-cut 歧义 / 左缘对齐 /
读序异常分)全败,有足够证据停止在确定性几何信号里找路由判据。

- `--layout` **永久文档级手动 opt-in**——"设计感版面开、学术论文关"本身是
  合理且可解释的产品面,用户比任何几何判据都清楚自己的文档类型;
- 页型判官归**外接服务**(按需):多特征小分类器或 VLM 判页型(G8b 顺带),
  属可插拔 AI 边界,不进确定性核心。

## 落地(诊断信号,非 auto 开关)

`reading_order_anomaly` 作为 `PageProfile` 字段落地([quality.rs](../../crates/docparse-core/src/quality.rs)):
用与输出路径同一个 `reading_order` 重排可见文本块后算 y 回跳率,纯几何、便宜、
in [0,1]。它**不接任何自动决策**(字段 doc 写明),价值是:① 补上 M7 留空的
reading-order 异常项,进 `--profile` 可观测;② 给未来外接页型判官当特征。
单测钉死最关键的不变量——**clean 页(单/多栏)恒为 0,绝不误报**(误报正是
判据被否的核心原因);>0 由探针真实数据佐证,不写脆弱构造单测。

## 验收

- 二选一明确落地:确定性路由关闭 + `--layout` 永久手动,已文档化;
- `reading_order_anomaly` 进 profile,Rust 实测 amt 0.008 / 2305 0.001 / 2203
  0.004——同样不可分,印证探针(Rust 值更低因 reading_order 重排比 chunks 原序更顺);
- 记分牌默认路径零回归(纯增量字段,composite 不变);136 单测绿(+1 anomaly
  不变量),clippy 0。

## 经验

- **判据先探针后落码**:Python 探针 10 分钟出可分性结论,避免又一轮"写进
  Rust→回退→留死代码"(G2 判据 2 的教训)。
- **几何信号天花板**:四判据全败的共因是"语义版面意图"(该不该重排)无法
  从确定性几何读序的挣扎度推断——clean 双栏和顺排设计页在几何上同样"无回跳"。
  这类判断本就属模型/语义域,确定性核心如实交出信号、把决策留给可插拔边界,
  比硬凑一个不可分的阈值诚实。
