# ClinDB 第一批组件如何在不猜正文的前提下完成清洗
## 1. 它解决的实际问题
5 份 ClinDB 论文 Markdown 同时包含编辑痕迹、转换噪声和排版噪声。它们看起来都像“删掉几行或整理一下格式”,
实际误删边界不同:行首数字可能是手稿行号,也可能是作者单位;`arXiv:` 可能是边栏戳,也可能是合法参考文献;
编号列表可能属于 References,也可能是正文方法步骤。
当前实现没有建立一个能随意改全文的“大清洗器”,而是把第一批确定问题拆成 8 个组件。每个组件只识别一种证据,
返回快照绑定的精确 `TextEdit`,由公共流水线统一验证、应用和记录。
清洗语义来自已批准的
[`0006-clindb-first-batch-cleaning-components.md`](../design/0006-clindb-first-batch-cleaning-components.md),
输入范围和稳定计数见
[`CLINDB_REVIEWBENCH_CLEANING_SCOPE.md`](../reference/CLINDB_REVIEWBENCH_CLEANING_SCOPE.md)。
## 2. 当前组件和顺序
```text
Word 批注 ─┐
手稿行号 ──┼──► arXiv 戳 ──► 重复页眉 ──► 映射断词
│ │
│ └────────────► 参考文献空行
│
└────► HTML 双重实体 ──► HTML 表格布局
```
实验脚本固定按以下顺序组装:
| 顺序 | 组件 | 当前作用 |
| ---: | --- | --- |
| 1 | `paper.word_review_comment` | 删除完整单行 Word 批注及其后一个空行 |
| 2 | `paper.manuscript_line_number` | 删除由长递增序列确认的手稿行号 |
| 3 | `paper.arxiv_submission_stamp` | 删除严格整行提交戳 |
| 4 | `paper.repeated_running_header` | 删除重复页眉,并接回有明确续句证据的段落 |
| 5 | `paper.page_break_word_join` | 只应用本次运行参数中记录的词片段映射 |
| 6 | `markdown.html_table_double_escape` | 只在严格表格单元格文本中解除一层实体转义 |
| 7 | `markdown.html_table_layout` | 保留 HTML 内容,把单行表格展开成一行一个 `
` |
| 8 | `paper.reference_spacing` | 只在完整连续的 References 章节中统一条目空行 |
顺序不是为了让结果“看起来整齐”。dmp 的一个重复页眉正好位于第 18、19 条参考文献之间;如果不先删除页眉,
参考文献组件就不能确认这是完整连续序列。HTML 实体先修改小范围 token,表格布局再基于新快照替换整张表,
两类修改仍能在审计中分别追踪。
## 3. 为什么行号不能使用全局正则
JAMA 文档中一共有 134 个看似 `数字 + 空格` 的行首前缀。前 59 个位于 Abstract 之前,是作者单位编号;真正的手稿
行号只有 Abstract 之后的 75 个。
当前组件要求:
- 文档中恰好有一个严格 `## Abstract`;
- 只看它之后的候选;
- 候选数字全部严格递增;
- 至少有 20 个候选,并至少有 2 个数字标题。
任一条件失败就整篇不改。这样会漏掉较短的带行号手稿,但不会为了提高命中率删除作者单位或零散数字段落。
## 4. 为什么断词使用显式映射
“行尾连字符加下一行小写字母”无法决定连字符应删还是保留:`possi-` + `bly` 应成为 `possibly`,而
`SOFA-` + `based` 应保留为 `SOFA-based`。dmp 还存在 `threshold.` + `olds`,它同时包含多余句点和重复片段,
普通词典也无法解释。
因此组件的实际参数记录三项:左片段、右片段和结果词。只有相邻物理行或中间恰好一个空行、词边界完整且映射唯一时
才修改。增加新词不是自动学习行为,需要先批准新的项目映射;组件算法版本不变时,运行清单仍能通过参数区分实际语义。
## 5. 两个表格组件怎样共享范围
`_html_table.py` 只识别当前转换器输出的严格子集:`
` 直接位于 `` 下,`| ` / ` | ` 直接位于
` | ` 下,标签完整闭合,单元格中没有嵌套标签。它返回原字符串下标,不生成 DOM,也不重新渲染全文。
实体组件只处理单元格文本中的三个精确 token:
```text
< → <
> → >
& → &
```
结果仍是合法 HTML 源码中的单层实体。标签、属性、表格外文本和其他实体不受影响。
布局组件只处理整个片段没有换行的严格表格。它原样复用 `` 起始标签、每个完整 `...
` 和结束标签,
只增加外层换行与两个空格缩进。当前 9 张真实表格都有 `colspan`,所以全部保留 HTML;实现没有猜测表头,也没有转 GFM。
遇到未闭合、嵌套、额外结构标签或混合换行时,扫描失败关闭,保持原文。
## 6. 共享代码为什么仍然很小
- `_text_ranges.py` 只提供 Python 字符下标下的物理行、行尾和空行关系;
- `_html_table.py` 只提供严格 HTML 表格、行和单元格范围;
- 业务组件依赖这两个私有模块,但辅助模块不依赖组件、流水线或文件层;
- 文件实验层只接收已经组装的 `Pipeline`,不知道任何识别规则。
因此以后放宽某个业务规则通常只改一个组件及其测试;替换 HTML 识别方式不会改变 `DocumentSnapshot`、
`ProposedChange`、`Change` 或 JSON 产物;未来引入 Profile 时,也只接管当前脚本里的组件组装。
## 7. 当前验证结果和边界
2026-08-22 在 Python 3.13.11 环境完成:
- Ruff 通过;
- mypy 检查 37 个文件无问题;
- pytest 204 项通过;
- 5 份本地论文全部 `success`,合计 155 条 `Change`;
- 对 5 份成功输出再次运行,全部 `success` 且零修改;
- 输入运行前后哈希不变;
- JAMA Abstract 前内容不变,Springer 两条合法 arXiv 参考文献保留;
- 图片引用文字不变,但实验产物没有复制图片资产。
保存型实验位于本机 Git 忽略的
`artifacts/2026-08-22/runs/clindb-first-batch-v1/`。运行和复核方法见
[`run-local-clindb-first-batch-experiment.md`](../guides/run-local-clindb-first-batch-experiment.md)。
这次成功只证明批准的 8 类规则在当前 5 份输入上闭环。截断、缺表、乱码、OCR 语义错误、图片资产、修订词选择和一般
段落重排仍不在自动清洗范围内。