Files
mdpolish/research-wiki/design/0006-clindb-first-batch-cleaning-components.md
T

532 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0006:ClinDB 第一批清洗组件与组合顺序
## 状态
已批准并冻结(2026-08-22)。
本设计已经用户明确批准,授权按第 13 节实施。后续语义变化必须新增 design,不回写本文。
本文沿用 `0003` 的内存核心与组件契约、`0004` 已批准的 arXiv 组件,以及 `0005` 的本地实验产物契约。
它不替代这三份设计,也不修改核心数据模型、执行器、流水线或产物格式。
## 1. 为什么现在需要这一设计
当前 5 份 ClinDB 论文已经能够通过本地实验层运行,但正式组件只有
`paper.arxiv_submission_stamp`。现有 reference 列出了第一批 8 类自动清洗目标,却还没有逐项确定:
- 每个问题由哪个组件负责;
- 哪些前置条件同时满足时才允许修改;
- 一处修改究竟替换哪些字符;
- 组件之间是否存在先后依赖;
- 当前 5 份输入的真实命中数是多少;
- 哪些看似合理的通用化会把正文一起改掉。
只按问题名称直接写正则会产生实际误改。例如,JAMA 文档中 Abstract 之前的 59 个行首数字是作者单位编号,不能当作
手稿行号删除;Springer 正文里还有编号方法列表,不能由参考文献空行规则全局处理;HTML 表格中的双重实体也不能直接
替换为原始 `<`,否则可能改变 HTML 解析边界。
因此本轮先冻结第一批组件的严格语义和组合顺序,再实施代码。
## 2. 只读调研结论与待修订事实
本轮重新按物理行和原始字符检查了 `data/md/` 中的 5 份本地论文副本。下表是本设计拟采用的验收事实;在本文批准前,
它们只是待评审结论,不表示组件已经实现。
| 范围 | 只读结果 | 对现有 reference 的影响 |
| --- | --- | --- |
| Word 审阅批注 | JAMA 有 2 条,批注文字都与 `Commented [...]` 位于同一物理行 | 第一版不猜测多行批注正文,只删除严格命中的单行批注及其后一个空行 |
| 手稿行号 | JAMA 共出现 134 个候选数字前缀;Abstract 之前 59 个是作者单位编号,Abstract 之后 75 个才是手稿行号,其中 6 个位于标题内 | `128 + 6` 的旧计数会误伤作者单位,应改为总计 75 |
| arXiv 提交戳 | sim、springer 各 1 条 | 沿用 `0004`,不改变组件语义 |
| 重复跑动页眉 | dmp 同一标题出现 2 次,其中 1 次切断正文,另 1 次落在参考文献中 | 页眉必须先于参考文献分隔执行 |
| 跨页断词 | 当前可确认 6 处,包括 3 处去连字符、2 处保留词内连字符和 1 处带错误句点的精确修复 | 不能只用“行尾连字符 + 小写字母”,也没有证据支持引入英文词表 |
| HTML 双重实体 | dmp 23 处、ejhf 8 处,共 31 处,全部在严格 HTML 表格单元格文本中 | “解除一层”应得到 `&lt;``&gt;``&amp;`,而不是直接生成 HTML 源码中的 `<``>``&` |
| 单行 HTML 表格 | 共 9 张;每张都至少有一个非 `1``colspan`,没有可验证的简单无合并表格样本 | 第一批只保留 HTML 并按行展开,不批准未经真实样本验证的 GFM 转换 |
| 参考文献空行 | dmp 需要补 13 处,springer 需要补 15 处,共 28 处 | 旧的条目范围计数不准确;规则必须限定在 References 章节内 |
dmp 的一处输入实际是前一行以 `threshold.` 结束、下一非空行以 `olds` 开始。它并不是普通的
`thresh-` + `olds`,而是上游结果同时多了句点并重复了 `old`。将它恢复为 `thresholds` 属于一条基于当前上下文批准的
精确映射,不能推广成通用断词算法。
上述差异在设计批准后才同步修订
[`CLINDB_REVIEWBENCH_CLEANING_SCOPE.md`](../reference/CLINDB_REVIEWBENCH_CLEANING_SCOPE.md)。不能一边保留旧计数,
一边让测试按新计数通过。
## 3. 目标与非目标
### 3.1 目标
- 为第一批 8 类问题提供 7 个新组件,并复用现有 arXiv 组件;
- 每个组件只提出唯一、可立即应用的精确修改;
- 将论文场景的组合留在实验脚本,核心和组件不依赖 DOI、文件名或 `data/md/`
- 明确组件顺序和少量真实依赖,避免靠多轮执行修正顺序错误;
- 保持标准库运行时、确定性、原子修改、完整审计和幂等性;
- 用合成测试覆盖正向、反向、异常边界和整条组合的稳定性;
- 设计批准并实现后,使用 `0005` 的现有产物结构保存 5 份论文的清洗结果、JSON 审计和 diff。
### 3.2 非目标
- 不恢复截断、缺表、缺句、乱码、OCR 表头或其他缺失内容;
- 不判断修订残留中哪个词正确,不改写写作、拼写或医学语义;
- 不增加 `detect_only`、建议、Inspector、人工审核或问题报告;
- 不引入 Markdown parser、HTML parser 第三方依赖、AST 或全文重新渲染;
- 不建立 Profile 文件格式、组件自动发现、公共 CLI 或稳定公共批处理接口;
- 不把无合并 HTML 表格转成 GFM;当前 5 份输入没有这种真实样本,表头推断也没有唯一答案;
- 不做一般段落重排、全局空行格式化或全局 HTML 实体解码;
- 不读取、复制、改写或打包图片;清洗产物中的图片相对路径仍可能因目录变化而无法直接显示;
- 不修改或覆盖 `data/md/` 输入,不对仓库外 GovDoc 数据保存实验产物;
- 不宣称输出已与 PDF 原文完全一致,也不宣称 ClinDB 中所有内容问题已经解决。
本文中的“ClinDB 第一批全清洗”只表示:本设计批准的 8 个自动组件全部运行并稳定,5 份输入中已确认的对应噪声全部
按契约处理。它不表示文档没有本设计明确排除的问题。
## 4. 采用的组件划分
第一批流水线固定包含以下组件:
| 顺序 | 组件标识 | 版本 | 职责 |
| ---: | --- | --- | --- |
| 1 | `paper.word_review_comment` | `1.0.0` | 删除严格单行 Word 审阅批注 |
| 2 | `paper.manuscript_line_number` | `1.0.0` | 删除通过整段证据确认的手稿行号前缀 |
| 3 | `paper.arxiv_submission_stamp` | `1.0.0` | 删除严格整行匹配的 arXiv 提交戳 |
| 4 | `paper.repeated_running_header` | `1.0.0` | 删除重复跑动页眉,并在证据充分处接回被切断段落 |
| 5 | `paper.page_break_word_join` | `1.0.0` | 按显式批准映射修复跨页断词 |
| 6 | `markdown.html_table_double_escape` | `1.0.0` | 只在严格表格单元格文本中解除一层实体转义 |
| 7 | `markdown.html_table_layout` | `1.0.0` | 将严格单行 HTML 表格展开为一行一个 `<tr>` |
| 8 | `paper.reference_spacing` | `1.0.0` | 只在 References 章节内统一相邻编号条目的空行 |
不采用一个 `clindb.clean_all` 大组件。拆分后每项修改都有独立身份、版本、适用边界和命中计数;改变表格规则不会迫使
行号规则一起升级,其他项目也可以只组合自己需要的组件。
组件不互相导入、不直接调用另一个组件,也不知道自己位于 ClinDB 流水线。顺序由实验入口显式组装并由
`manifest.json` 记录。
## 5. 共同扫描基础
多个组件需要精确物理行范围,两个表格组件需要完全相同的保守 HTML 范围。批准后允许新增两个不公开的内部辅助模块:
```text
src/mdpolish/
├── _text_ranges.py
└── _html_table.py
```
### 5.1 `_text_ranges.py`
它只负责返回原字符串中的物理行内容范围、行尾范围和空行关系,支持 `\n``\r\n`、单独 `\r` 和无末尾换行。
它不解释 Markdown 结构,也不规范化换行。
现有 arXiv 组件中的私有物理行扫描可以机械迁移到该模块。迁移不得改变
`paper.arxiv_submission_stamp` 的标识、版本、参数、识别范围或测试结果。
### 5.2 `_html_table.py`
它是针对当前转换器输出子集的严格词法扫描器,不是通用 HTML parser。只有同时满足以下条件的完整片段才返回结构范围:
- 具有完整、正确嵌套的小写 `<table>``<tr>``<td>` / `<th>` 起止标签;
- `<tr>``<table>` 的直接子元素,单元格是 `<tr>` 的直接子元素;
- 标签属性可以存在并原样保留,但引号和标签边界必须完整;
- 结构标签之间只能有空白,单元格文本中不能出现其他原始 HTML 标签;
- 不含嵌套表格、注释、声明、`script``style``pre` 或无法闭合的标签。
不满足条件的相似片段直接忽略,组件不尝试修复它,也不降级为全局正则。扫描器返回原字符串下标,不构造 DOM,
不解码实体,不重新输出 HTML。
第一批组件继续不识别 Markdown 围栏、引用块或其他块级上下文。严格目标文本即使出现在这些结构中仍可能命中;
当前 5 份证据没有这种反例,本设计不借共享扫描器重新加入围栏保护。
## 6. 各组件的精确契约
### 6.1 `paper.word_review_comment`
目标物理行必须完整满足:
```regex
^Commented \[[A-Za-z0-9]+\]: .*\S$
```
并且其后至少紧跟一个空白内容为空的物理行。每个命中产生一个删除编辑,范围包含:
1. 批注行的全部文字和自身行尾;
2. 紧随其后的第一个空行及其行尾。
组件不删除更多空行,不把后续普通行猜成批注正文,也不处理 `Commented` 出现在句中、行首带空格、缺少批注 ID、
缺少冒号或没有正文的相似文本。末尾没有后续空行时保持原样。
固定修改理由为“删除严格单行 Word 审阅批注及其后一个空行”。当前 JAMA 两条批注各产生一条 `Change`
### 6.2 `paper.manuscript_line_number`
该组件不全局删除行首数字。它先按以下步骤确认一个完整手稿行号序列:
1. 文档中必须恰好有一个严格物理行 `## Abstract`
2. 只扫描该行之后的内容,Abstract 之前的所有数字前缀一律排除;
3. 候选前缀只有两种:普通行的 `^[1-9][0-9]{0,2} `,或 ATX 标题中的
`^#{1,6} [1-9][0-9]{0,2} `;前缀后必须还有非空正文;
4. 按出现顺序取得的所有候选数字必须严格递增,允许跳号,不允许相等或回退;
5. 整段至少有 20 个候选,并至少有 2 个标题候选。
任一条件不满足时,整个组件返回零候选,不做部分删除,也不从某个看似合理的位置继续猜测。当前阈值用于证明这是
整篇带行号的手稿,而不是恰好出现几个数字开头的段落;未来出现短手稿时必须用新证据重新评审,不能暗中降低阈值。
确认序列后,每个前缀各产生一条精确修改:普通行只删除数字和其后的一个 ASCII 空格;标题只删除标题标记之后的
数字和一个空格,保留原有 `#` 级别和标题空格。正文中的数字、列表 `1.` / `1)`、四位年份、Abstract 之前的作者单位
编号均不修改。
固定修改理由为“删除已确认手稿序列中的行号前缀”。当前 JAMA 应产生 75 条 `Change`,其中普通行 69 条、标题 6 条。
### 6.3 `paper.arxiv_submission_stamp`
完全复用 `0004` 已批准的 `ArxivSubmissionStampComponent`,不修改代码语义和版本。第一批组合只把它放入新的流水线,
当前 sim 和 springer 各产生 1 条 `Change`
### 6.4 `paper.repeated_running_header`
候选页眉首先必须是无首尾空格的完整 ATX 标题行:
```regex
^#{1,6} \S(?:.*\S)?$
```
相同的完整标题行必须原样出现至少 2 次。一个重复组只有在所有出现位置都能安全删除,并且至少有一个位置满足“正文被
切断”证据时,才整体成立。
“正文被切断”要求该标题前后各恰好有一个空行,前一非空物理行去掉行尾空白后不以中英文句号、问号、叹号、冒号或
分号结束,后一非空物理行以 ASCII 小写字母开头。该位置从前一正文行末到后一正文行首的整个间隔替换为一个 ASCII
空格,从而删除空行和页眉并接回同一句。
同组的其他位置只在标题后至少有一个空行时删除标题行及其后第一个空行,保留标题之前已有的分隔。若某个出现位置不满足
上述任一安全形态,则整组不修改。组件不使用标题关键词,不删除只出现一次的标题,也不把一般重复章节标题视为页眉。
固定修改理由为“删除经重复和断句证据确认的跑动页眉”。当前 dmp 的同一标题产生 2 条 `Change`
### 6.5 `paper.page_break_word_join`
该组件不使用词典、拼写检查、概率模型或“行尾有连字符就合并”的启发式规则。调用方必须显式传入有序映射,每项包含:
```text
left_fragment
right_fragment
replacement
```
组件初始化时把映射规范化为确定顺序,并拒绝空字段、重复的左右片段组合或会产生无效修改的映射。实际参数完整写入
`ComponentInfo.parameters`,因此相同组件版本使用了哪些项目映射可以从运行清单复核。
一条映射只有在以下条件全部满足时才命中:
- `left_fragment` 是前一非空物理行的精确末尾;
- 右片段位于紧邻的下一物理行,或中间恰好隔一个空行;存在空行时,相关行尾使用同一种形式;
- `right_fragment` 是下一非空物理行的精确开头;
- 左片段前和右片段后都满足 ASCII 字母词边界,不能只是更长单词的一部分;
- 同一位置没有被另一条映射命中。
编辑范围从左片段起点到右片段终点,包含两个片段和中间一个或两个行尾,整体替换为显式 `replacement`。前后其余正文
一字不改。两个及以上空行通常表示段落边界,即使片段文字相同也不处理。
ClinDB 第一批参数固定为:
| `left_fragment` | `right_fragment` | `replacement` | 说明 |
| --- | --- | --- | --- |
| `medi-` | `cal` | `medical` | 去除分页连字符 |
| `possi-` | `bly` | `possibly` | 去除分页连字符 |
| `cre-` | `ated` | `created` | 去除分页连字符 |
| `SOFA-` | `based` | `SOFA-based` | 保留复合词连字符,只删除分页分隔 |
| `life-` | `threatening` | `life-threatening` | 保留复合词连字符,只删除分页分隔 |
| `threshold.` | `olds` | `thresholds` | 当前 dmp 的精确证据修复,不推广 |
每处固定理由为“按已批准映射修复跨页断词:`<left_fragment>` + `<right_fragment>``<replacement>`”;当前应产生
6 条 `Change`。增加或改变映射会改变实验参数和预期结果,必须先更新项目设计或已批准的项目配置,不能从外部词典自动扩张。
### 6.6 `markdown.html_table_double_escape`
该组件只处理 `_html_table.py` 接受的完整表格,并只扫描 `<td>` / `<th>` 的文本范围。第一版映射固定为:
| 修改前源码 | 修改后源码 | 浏览器最终显示意图 |
| --- | --- | --- |
| `&amp;lt;` | `&lt;` | `<` |
| `&amp;gt;` | `&gt;` | `>` |
| `&amp;amp;` | `&amp;` | `&` |
这是对 HTML 源码解除一层实体转义,不是把最终显示字符直接写进 HTML。每个精确实体 token 产生一个编辑;已经是单层的
`&lt;``&gt;``&amp;` 不再命中,因此再次运行稳定。
标签名、属性值、表格外文本、普通 Markdown、URL 和不完整实体均不修改。组件不调用 `html.unescape()`,因为它会扩大到
没有逐项批准的命名实体和数字实体,也无法保留 HTML 标签边界。
固定修改理由为“在严格 HTML 表格单元格文本中解除一层实体转义”。当前应产生 31 条 `Change`
### 6.7 `markdown.html_table_layout`
该组件只处理 `_html_table.py` 接受、并且整个 `<table>...</table>` 片段不含任何 `\r``\n` 的单行表格。
它不计算行列数,不解释 `rowspan` / `colspan`,不推断表头,也不改变任何单元格内容、属性或标签书写。
输出形态固定为:
```html
<table>
<tr>...</tr>
<tr>...</tr>
</table>
```
实际 `<table>` 起始标签、每个完整 `<tr>...</tr>``</table>` 都从输入原样复用;组件只规范化这些外层片段之间的
布局空白。文档只有一种行尾时沿用该行尾;文档没有任何行尾时使用 `\n`;文档混用多种行尾时保持表格原样。
每张表产生一个覆盖完整表格片段的替换编辑。已经是多行的表格不命中,因而第二次运行不再改动。当前 9 张表全部含
`colspan`,均保留 HTML,只展开为一行一个 `<tr>`,应产生 9 条 `Change`
固定修改理由为“展开严格单行 HTML 表格的行布局”。未来是否把简单表格转为 GFM,必须等真实无合并样本出现后单独决定
表头、转义、换行和 HTML/GFM 等价性。
### 6.8 `paper.reference_spacing`
该组件先定位严格 ATX 标题,其标题文字按 ASCII 大小写折叠后必须恰好是 `references`。章节范围从该标题之后开始,
到下一个级别相同或更高的 ATX 标题之前结束;没有后续标题时到文档末尾。
章节中的编号条目起始行必须完整满足 `^([1-9][0-9]*)\. \S`。只有同时满足以下条件才处理整个章节:
- 至少有 2 个编号条目;
- 第一个编号为 1
- 后续编号严格逐个加 1,没有缺号、重复或回退;
- 两个相邻条目之间的最后一条正文行与下一条目起始行之间只有同一种行尾和零个或多个空行。
组件把每个相邻条目边界规范为恰好一个空行,即两个相同的行尾。已经恰好一个空行的边界不产生修改;没有空行时插入
一个行尾,多余空行时收敛为一个。章节外编号列表、非 References 标题、编号不连续的章节和混合行尾边界保持原样。
固定修改理由为“统一 References 章节中相邻编号条目之间的一个空行”。该组件必须在跑动页眉之后执行,否则 dmp 位于
第 18、19 条之间的页眉会破坏章节连续性。当前 dmp 应产生 13 条、springer 应产生 15 条,共 28 条 `Change`
## 7. 顺序为什么固定
第一批只执行一轮,不能依赖“第二轮自然修好”。固定顺序的直接理由是:
```text
Word 批注 ─┐
手稿行号 ──┼── 先去除论文编辑层噪声
arXiv 戳 ──┘
重复页眉 ─────► 参考文献分隔
跨页断词 ─────► 独立正文精确修复
HTML 双重实体 ─► HTML 表格布局
```
- 跑动页眉先删除,dmp 的参考文献编号才能形成完整连续序列;
- 实体先改、布局后改,使每个实体 `Change` 保留较小的局部范围,随后表格整体布局基于最新快照;
- Word 批注先删除,行号组件看到的是不含审阅插入物的手稿,但行号判定本身仍不得依赖批注一定存在;
- 其他顺序目前没有内容依赖,仍固定下来以保证审计、哈希和 diff 可复现。
脚本和组合测试必须断言第 4 节的完整顺序,不能按文件名排序组件,也不能针对不同文档临时增删组件。
## 8. 代码与依赖边界
批准后允许新增:
```text
src/mdpolish/
├── _html_table.py
├── _text_ranges.py
└── components/
├── html_table_double_escape.py
├── html_table_layout.py
├── manuscript_line_number.py
├── page_break_word_join.py
├── reference_spacing.py
├── repeated_running_header.py
└── word_review_comment.py
scripts/
└── run_clindb_first_batch_experiment.py
tests/
├── test_clindb_first_batch_pipeline.py
├── test_html_table_double_escape.py
├── test_html_table_layout.py
├── test_manuscript_line_number.py
├── test_page_break_word_join.py
├── test_reference_spacing.py
├── test_repeated_running_header.py
└── test_word_review_comment.py
```
允许同步修改:
- `src/mdpolish/components/__init__.py`,导出 7 个新组件;
- `src/mdpolish/components/arxiv_submission_stamp.py`,只把物理行扫描机械迁移到 `_text_ranges.py`
- 现有 arXiv 测试,验证迁移没有改变行为;
- README、对应 explanation 和经实际验证的 guide,使当前阶段、目录和检查命令与实现一致;
- ClinDB reference,修订第 2 节列出的计数、实体语义、表格边界和 `0005` 已实现的输出事实。
依赖方向固定为:
```text
components ──► models / component
├───────► _text_ranges
└───────► _html_table
experiment script ──► components + Pipeline + experiment layer
```
内部辅助模块不导入具体组件、流水线、文件适配层或 reporter。7 个新组件不从顶层 `mdpolish.__init__` 导出,不承诺
稳定公共 API。现有核心签名、JSON `schema_version` 和产物目录结构均不改变。
`run_clindb_arxiv_experiment.py` 保留为首个组件实验的历史入口,不改写成新语义。新的 first-batch 脚本显式建立第 4 节
的 8 组件流水线,仍只接受 `--run-id`,并复用 `0005` 已批准的 5 份文档清单和
`artifacts/<run_date>/runs/<run_id>/` 保存逻辑。它不是公共 CLI,也不是 Profile 实现。
第一版继续只使用 Python 标准库,不修改最低 Python 版本,不新增运行依赖。
## 9. 合成测试要求
每个组件至少覆盖空输入、无命中、单命中、多命中、三种行尾、无末尾行尾、参数或结构反例、修改理由、确定顺序和
再次运行零修改。额外必须覆盖:
### 9.1 Word 批注
- 两条由空行隔开的单行批注都删除,并正确接回周围普通行;
- 行内 `Commented`、不合法 ID、缺正文、没有后续空行和假想多行正文保持原样;
- 不多删第二个及后续空行。
### 9.2 手稿行号
- Abstract 前的连续作者单位编号全部保留;
- 满足阈值和标题证据的递增序列同时清理普通行与标题;
- 候选少于 20、标题候选少于 2、重复、回退、多个 Abstract 或没有 Abstract 时整体不修改;
- `1.``1)`、四位年份、正文中间的 `35 pediatric experts``10 sites``4 continents` 保留。
### 9.3 重复页眉
- 一个桥接位置替换为单个空格,另一个独立位置删除后只保留一个原有段落分隔;
- 只出现一次、重复但没有断句证据、下一行大写或某次出现无法安全删除时保持原样;
- 页眉文字不硬编码,候选顺序按原文位置稳定。
### 9.4 跨页断词
- 6 条批准映射分别验证,包含删除连字符、保留连字符和 `threshold.` / `olds` 精确修复;
- 未配置词、不是一整个词边界、两个及以上空行、混合行尾、大小写不同和相似长词保持原样;
- 相邻物理行和中间恰好一个空行两种已批准边界都能正确合并;
- 映射输入顺序不同仍得到相同参数和结果,重复或冲突映射明确报契约错误。
### 9.5 HTML 实体与布局
- 实体只解除一层,第二次不再改变;属性、表外文本、单层实体和未批准实体保留;
- 9 张表所代表的 `colspan` 形态只改布局,不改属性、单元格文本和标签顺序;
- 已经多行的表格不改;混合行尾文档不改布局;
- 未闭合、嵌套、含额外标签或结构不合法的表格整体忽略;
- 两个 HTML 组件组合后再次运行稳定。
### 9.6 参考文献与完整组合
- References 章节内缺失或过多空行都收敛为一个;章节外编号方法列表保留;
- 大小写不同的准确 References 标题可识别,近似标题、缺号和混合行尾保持原样;
- 合成 dmp 形态证明页眉先删除后,18、19 条之间和后续连续条目能正确处理;
- 完整 8 组件流水线第一次成功,最终复查没有残留候选;对成功输出再次运行为 `success` 且零 `Change`
- 每条 `Change` 的组件、版本、位置、理由、`before``after` 和前后哈希可以按 `0003` / `0005` 重放。
测试 fixture 只能使用小型虚构文本,不能复制真实论文段落或完整表格到 Git。
## 10. 本地 5 份论文验收
所有合成测试通过后,才允许对 `data/md/` 的 5 份本地副本执行新的保存型实验。预期计数如下:
| 组件 | 预期 `Change` 数 |
| --- | ---: |
| `paper.word_review_comment` | 2 |
| `paper.manuscript_line_number` | 75 |
| `paper.arxiv_submission_stamp` | 2 |
| `paper.repeated_running_header` | 2 |
| `paper.page_break_word_join` | 6 |
| `markdown.html_table_double_escape` | 31 |
| `markdown.html_table_layout` | 9 |
| `paper.reference_spacing` | 28 |
| **合计** | **155** |
验收必须确认:
1. `manifest.json` 中 5 份文档全部为 `success`,组件身份、版本、参数和顺序与本文一致;
2. 合计恰好 155 条 `Change`,各组件计数与上表一致;
3. 每个 `cleaned.md` 的哈希与 `result.json` 一致,最终复查没有残留候选;
4. 对 5 份成功输出再运行同一流水线,全部 `success` 且合计零修改;
5. JAMA 的作者单位编号保留,只清理 Abstract 后确认的 75 个手稿前缀;
6. Springer 的合法 arXiv 参考文献和正文编号方法列表保持原样;
7. 9 张表的标签、属性、单元格内容和顺序不变,只发生已批准的实体和行布局修改;
8. dmp 的重复页眉、跨页修复和参考文献空行在最终 diff 中可分别追溯;
9. 逐份人工查看 `changes.diff`,确认没有上表之外的正文改写;
10. 运行前后 5 份 `data/md/*.md` 输入字节哈希完全不变;
11. 产物只出现在 Git 忽略的 `artifacts/<run_date>/runs/<run_id>/``git status` 不列出产物;
12. 清洗产物不复制图片。图片是否可显示不属于本轮成功条件,README 或 guide 必须明确这一点。
只要实际计数、结构或正文 diff 与预期不一致,就停止收尾并回到本设计核对。不得通过放宽规则、删除断言或把额外修改
改名为“格式整理”来让实验通过。
基础检查继续以根目录 README 为唯一命令权威。实现后至少实际运行 Ruff、mypy、完整 pytest、Agent 镜像 diff、
Git diff 检查和上述本地实验;未执行的检查不得报告为通过。
## 11. 方案比较与否决项
### 11.1 一个组件处理全部 ClinDB 问题
文件少,但任何规则变化都会改变同一组件语义,审计无法按问题归因,也不能被其他项目选择性复用。不采用。
### 11.2 用全局正则清理数字、实体和空行
会分别误伤作者单位、普通 Markdown 实体和正文编号列表。已有 5 份输入就存在真实反例。不采用。
### 11.3 第一批引入 Markdown/HTML parser 和 AST 重写
成熟 parser 能扩大结构识别范围,但会引入方言、渲染和非目标内容重写问题。当前 9 张表只需要保留原字节的严格词法
范围,标准库小扫描器足够验证第一批。不采用。
### 11.4 用英文词典自动决定跨页合词
词典无法决定 `SOFA-based` 是否保留连字符,也无法解释 `threshold.` + `olds` 的上游错误;领域词、缩写和专名还会造成
漏判。第一批采用可审计的显式映射,不采用词典。
### 11.5 把无合并表格自动转为 GFM
当前没有真实样本,且没有 `<th>` 时必须猜测表头;转义、换行和合并语义也未验证。第一批保留 HTML,不采用。
### 11.6 顺便打包图片或建立项目 Profile
图片需要输入资产定位、路径改写、复制冲突和产物目录契约;Profile 需要身份、配置格式和兼容规则。两者与文本组件并非
同一问题,不能借本设计默认授权。不采用。
## 12. 风险与后续变化成本
- **严格规则会漏掉未来变体:** 这是保真优先的有意选择。新格式先补证据、测试和版本,不在原规则中静默放宽。
- **行号阈值针对长手稿:** 短文档可能不清理。组件与组合分离后,可以新增另一个有独立证据的组件或升级本组件,
不需要修改核心。
- **跑动页眉仍是启发式分类:** 重复加断句证据降低误删,但不能证明适用于所有论文。它只进入 ClinDB 第一批组合,
不成为全局默认规则。
- **显式断词映射需要维护:** 新文档会出现新词,但映射作为参数记录,不必修改执行器或其他组件;代价是每次扩张前要评审。
- **严格 HTML 子集会忽略复杂表格:** 失败关闭能够避免重写未知结构。以后若需要完整 HTML parser,只替换内部扫描实现或
升级两个表格组件,不改变 `DocumentSnapshot``TextEdit` 和产物契约。
- **整体表格布局编辑的 diff 较大:** 每张表只有一条可回放 `Change`,但人读 diff 时会看到整行展开。实体组件先执行,
`result.json` 仍能分别追踪局部实体修改和后续布局修改。
- **没有 Profile 对象:** 当前组合只在一个实验脚本中,适合第一批验证。未来新增第二个稳定项目组合时再设计 Profile,
组件本身无需迁移。
- **图片仍不可随产物查看:** 这是 `0005` 的既有边界。若人工评审必须在清洗目录直接显示图片,应新增图片资产产物设计,
不改变本轮文本组件。
这套拆分保持“业务识别组件 → 精确编辑核心 → 文件实验层”的单向依赖。后续调整某条识别规则通常只影响一个组件及其测试;
调整项目组合只影响实验入口或未来 Profile;调整产物日期和目录只影响 `0005` 的 artifact store。核心数据契约无需跟着变化。
## 13. 批准后的实施边界
如果用户明确批准本文,只授权:
1. 新增第 8 节列出的 7 个组件、2 个内部辅助模块、合成测试和 first-batch 实验脚本;
2. 对现有 arXiv 组件做不改变语义和版本的物理行辅助函数迁移;
3. 按第 4、6、7 节实现固定组件身份、严格识别、精确修改和组合顺序;
4. 按第 9 节运行合成检查;
5. 合成检查通过后,按 `0005``data/md/` 的 5 份副本保存一次本地 first-batch 实验;
6. 根据真实实现和实际验证更新 README、ClinDB reference、对应 explanation 和 guide。
批准仍不授权:
- 修改、覆盖、移动或删除任何输入和真实数据;
- 保存 GovDoc 或其他仓库外材料的清洗产物;
-`artifacts/``data/` 或真实文本 fixture 加入 Git
- 实现内容恢复、Inspector、建议、Profile 格式、公共 CLI、通用 parser、图片复制或路径改写;
- 提交、推送、创建 PR、发布或修改其他仓库。