实现 ClinDB 第一批清洗组件
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
# 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 表格单元格文本中 | “解除一层”应得到 `<`、`>`、`&`,而不是直接生成 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>` 的文本范围。第一版映射固定为:
|
||||
|
||||
| 修改前源码 | 修改后源码 | 浏览器最终显示意图 |
|
||||
| --- | --- | --- |
|
||||
| `&lt;` | `<` | `<` |
|
||||
| `&gt;` | `>` | `>` |
|
||||
| `&amp;` | `&` | `&` |
|
||||
|
||||
这是对 HTML 源码解除一层实体转义,不是把最终显示字符直接写进 HTML。每个精确实体 token 产生一个编辑;已经是单层的
|
||||
`<`、`>`、`&` 不再命中,因此再次运行稳定。
|
||||
|
||||
标签名、属性值、表格外文本、普通 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、发布或修改其他仓库。
|
||||
@@ -75,9 +75,9 @@ arXiv:<新版数字编号和版本> [<ASCII 分类>] <日> <英文月份缩写>
|
||||
|
||||
## 6. 剩余边界
|
||||
|
||||
这个组件只证明第一条严格删除规则能够在公共核心上闭环,不表示论文已经清洗完成。HTML 实体、Word 批注、手稿
|
||||
行号、断词、表格和参考文献间距仍未实现。当前只有固定输入和固定组件的本地实验输出;通用文件接口、profile、
|
||||
公共 CLI 和通用批处理仍不存在。
|
||||
这个组件最初证明了第一条严格删除规则能够在公共核心上闭环。此后 ClinDB 第一批另外 7 个组件已经按 `0006` 实现,
|
||||
当前完整组合见 [`clindb-first-batch-components.md`](clindb-first-batch-components.md)。这仍不表示论文内容问题全部解决;
|
||||
通用文件接口、profile、公共 CLI、图片资产和通用批处理仍不存在。
|
||||
|
||||
如果出现新的提交戳格式,默认行为是保留。必须先补充真实证据、反向样例和 design,再决定是否放宽模式,不能为了
|
||||
提高命中数量直接修改正则表达式。
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# 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 内容,把单行表格展开成一行一个 `<tr>` |
|
||||
| 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` 只识别当前转换器输出的严格子集:`<tr>` 直接位于 `<table>` 下,`<td>` / `<th>` 直接位于
|
||||
`<tr>` 下,标签完整闭合,单元格中没有嵌套标签。它返回原字符串下标,不生成 DOM,也不重新渲染全文。
|
||||
|
||||
实体组件只处理单元格文本中的三个精确 token:
|
||||
|
||||
```text
|
||||
&lt; → <
|
||||
&gt; → >
|
||||
&amp; → &
|
||||
```
|
||||
|
||||
结果仍是合法 HTML 源码中的单层实体。标签、属性、表格外文本和其他实体不受影响。
|
||||
|
||||
布局组件只处理整个片段没有换行的严格表格。它原样复用 `<table>` 起始标签、每个完整 `<tr>...</tr>` 和结束标签,
|
||||
只增加外层换行与两个空格缩进。当前 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 语义错误、图片资产、修订词选择和一般
|
||||
段落重排仍不在自动清洗范围内。
|
||||
@@ -29,7 +29,8 @@ experiment.py
|
||||
- `artifact_store.py` 不理解清洗规则,只把已经生成的字节写入私有临时目录,校验后一次性发布。
|
||||
|
||||
因此,新增组件不会改变文件层;调整目录布局不会影响清洗和报告;修改 JSON 或 diff 时也不需要碰流水线。
|
||||
ClinDB 的 5 份论文和 arXiv 组件组合只存在于仓库内实验脚本,通用模块没有硬编码论文名。
|
||||
ClinDB 的 5 份论文、历史 arXiv 单组件组合和当前 first-batch 组合只存在于两个仓库内实验脚本,通用模块没有硬编码
|
||||
论文名或业务组件。
|
||||
|
||||
## 3. 输入怎样保持原样
|
||||
|
||||
@@ -154,3 +155,21 @@ artifact store 先在同一日期的 `runs/` 下建立本次专用临时目录
|
||||
|
||||
实际运行方法见
|
||||
[`run-local-clindb-arxiv-experiment.md`](../guides/run-local-clindb-arxiv-experiment.md)。
|
||||
|
||||
同日又使用 `design/0006` 的 8 组件流水线完成 ClinDB 第一批保存型实验:
|
||||
|
||||
| 项目 | 结果 |
|
||||
| --- | --- |
|
||||
| 运行 ID | `clindb-first-batch-v1` |
|
||||
| 输出位置 | `artifacts/2026-08-22/runs/clindb-first-batch-v1/` |
|
||||
| 文档状态 | 5/5 `success` |
|
||||
| 实际修改 | dmp 47、ejhf 9、jama 79、sim 3、springer 17,共 155 条 |
|
||||
| 第二次运行 | 5/5 `success`,合计 0 条修改 |
|
||||
| 输出校验 | 5/5 `cleaned.md` 与 `current_sha256` 一致 |
|
||||
| 输入只读 | 5/5 运行前后字节和哈希不变 |
|
||||
| 内容反例 | JAMA Abstract 前内容不变;Springer 两条合法 arXiv 引用保留;图片引用文字不变 |
|
||||
| 权限 | 运行目录 `0700`,产物文件 `0600` |
|
||||
|
||||
当前完整运行方法见
|
||||
[`run-local-clindb-first-batch-experiment.md`](../guides/run-local-clindb-first-batch-experiment.md)。实验层的文件、
|
||||
JSON、diff 和权限契约没有因组件增多而改变。
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
```bash
|
||||
.venv/bin/python --version
|
||||
.venv/bin/ruff check .
|
||||
.venv/bin/mypy src tests scripts/run_clindb_arxiv_experiment.py
|
||||
.venv/bin/mypy src tests scripts/run_clindb_arxiv_experiment.py scripts/run_clindb_first_batch_experiment.py
|
||||
.venv/bin/pytest
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# 运行本地 ClinDB 第一批完整清洗实验
|
||||
|
||||
## 1. 适用范围
|
||||
|
||||
本指南只运行仓库内已经批准的 first-batch 实验脚本:
|
||||
|
||||
- 输入:`data/md/` 中的 `dmp.md`、`ejhf.md`、`jama.md`、`sim.md` 和 `springer.md`;
|
||||
- 流水线:`design/0006` 固定的 8 个组件和顺序;
|
||||
- 输出:`artifacts/<YYYY-MM-DD>/runs/<run_id>/`;
|
||||
- 输入只读,不覆盖原文件;
|
||||
- 不读取或复制图片,不处理 `/home/lihaoze/gov_test_data`。
|
||||
|
||||
本指南于 2026-08-22 在 Python 3.13.11 环境实际验证。
|
||||
|
||||
## 2. 前置检查
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
.venv/bin/python --version
|
||||
.venv/bin/ruff check .
|
||||
.venv/bin/mypy src tests scripts/run_clindb_arxiv_experiment.py scripts/run_clindb_first_batch_experiment.py
|
||||
.venv/bin/pytest
|
||||
diff -u <(tail -n +2 AGENTS.md) <(tail -n +2 CLAUDE.md)
|
||||
```
|
||||
|
||||
当前已验证结果是 Ruff 通过、mypy 37 个文件无问题、pytest 204 项通过。再确认 5 份输入存在:
|
||||
|
||||
```bash
|
||||
find data/md -maxdepth 1 -type f -name '*.md' -printf '%f\n' | sort
|
||||
```
|
||||
|
||||
必须看到 `dmp.md`、`ejhf.md`、`jama.md`、`sim.md` 和 `springer.md`。不要把真实论文复制进测试 fixture。
|
||||
|
||||
## 3. 运行实验
|
||||
|
||||
人工选择一个当天未使用的安全运行 ID:
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/run_clindb_first_batch_experiment.py \
|
||||
--run-id clindb-first-batch-review
|
||||
```
|
||||
|
||||
成功时终端只显示运行身份和汇总,不打印原文:
|
||||
|
||||
```text
|
||||
run_id=clindb-first-batch-review
|
||||
status=success
|
||||
documents=5
|
||||
changes=155
|
||||
artifacts=/.../mdpolish/artifacts/<YYYY-MM-DD>/runs/clindb-first-batch-review
|
||||
```
|
||||
|
||||
同一天同名目录已存在时脚本会拒绝覆盖。需要重跑时使用新 ID,不要删除旧目录来绕过检查。
|
||||
|
||||
## 4. 先看哪些结果
|
||||
|
||||
先打开运行目录的 `manifest.json`,确认:
|
||||
|
||||
- `run.status` 是 `success`;
|
||||
- `summary.document_count` 和 `summary.success_count` 都是 5;
|
||||
- `summary.failed_count`、`summary.unstable_count` 都是 0;
|
||||
- `summary.change_count` 是 155;
|
||||
- `pipeline.components` 的顺序与 `design/0006` 一致。
|
||||
|
||||
然后查看每份文档目录:
|
||||
|
||||
```text
|
||||
documents/<document_id>/
|
||||
├── result.json
|
||||
├── cleaned.md
|
||||
└── changes.diff
|
||||
```
|
||||
|
||||
- `changes.diff` 用于人工查看输入到最终输出的总变化;
|
||||
- `result.json` 用于按组件、理由、位置和哈希追踪每条修改;
|
||||
- `cleaned.md` 是成功输出全文。
|
||||
|
||||
当前 5 份输入的预期计数是:
|
||||
|
||||
| 文档 | `Change` 数 |
|
||||
| --- | ---: |
|
||||
| dmp | 47 |
|
||||
| ejhf | 9 |
|
||||
| jama | 79 |
|
||||
| sim | 3 |
|
||||
| springer | 17 |
|
||||
| **合计** | **155** |
|
||||
|
||||
按组件应为:Word 批注 2、手稿行号 75、arXiv 戳 2、重复页眉 2、映射断词 6、HTML 实体 31、
|
||||
HTML 表格布局 9、参考文献空行 28。
|
||||
|
||||
## 5. 人工复核重点
|
||||
|
||||
除了逐份查看 diff,至少确认:
|
||||
|
||||
- JAMA 的 Abstract 前作者和单位编号仍在,只删除 Abstract 后的 75 个手稿行号;
|
||||
- Springer 两条 `arXiv preprint arXiv:` 合法参考文献仍在;
|
||||
- Springer 正文中的编号方法列表没有被参考文献规则整理;
|
||||
- dmp 的重复页眉删除后,正文句子接回,参考文献第 18、19 条之间仍有一个空行;
|
||||
- 9 张表仍是 HTML,属性和单元格内容未被布局组件改写;
|
||||
- 双重实体变成单层 `<`、`>` 或 `&`,没有直接生成标签边界;
|
||||
- 图片引用文字保持不变。
|
||||
|
||||
清洗目录没有复制图片资产,所以直接打开 `cleaned.md` 时图片仍可能无法显示。这不表示图片引用被清洗组件删除;
|
||||
资产打包和路径改写需要单独设计。
|
||||
|
||||
## 6. 验证幂等和输入不变
|
||||
|
||||
流水线会在每份文档结束时做最终稳定性复查。需要额外复核整个保存结果时,可以把 `cleaned.md` 作为内存输入再次运行
|
||||
同一 `build_pipeline()`;5 份都应为 `success` 且合计零 `Change`。
|
||||
|
||||
实验层已经在发布前后复读输入并比较字节哈希。需要人工记录运行前后的摘要时,可在运行前后分别执行:
|
||||
|
||||
```bash
|
||||
sha256sum data/md/*.md
|
||||
```
|
||||
|
||||
两次输出必须逐项一致。每个 `cleaned.md` 的 SHA-256 还必须等于对应 `result.json.current_sha256`。
|
||||
|
||||
## 7. 常见失败
|
||||
|
||||
### 状态不是 `success`
|
||||
|
||||
查看对应 `result.json` 的 `errors` 或 `residual_proposals`。`failed` / `unstable` 文档不会有正式 `cleaned.md`,
|
||||
不能把其他文档的部分成功当成整批成功。
|
||||
|
||||
### 修改数不是 155
|
||||
|
||||
先按组件和文档分组定位差异。输入变化、组件参数变化或识别边界变化都必须回到 design/reference 核对;不要放宽断言、
|
||||
补跑第二轮或手工改产物。
|
||||
|
||||
### 图片不显示
|
||||
|
||||
当前运行只保存 Markdown、审计和 diff,不复制图片。不要为了显示图片而修改输入路径或把真实资产强制加入 Git。
|
||||
|
||||
### 私有目录无法被 Snap 工具读取
|
||||
|
||||
运行目录权限是 `0700`,文件是 `0600`。使用普通编辑器或当前虚拟环境中的 Python 读取,不要放宽权限。
|
||||
|
||||
## 8. 数据边界
|
||||
|
||||
产物包含完整论文和原文片段,只能保存在本机 Git 忽略的 `artifacts/`。不得执行 `git add -f`,不得复制到 Wiki、
|
||||
其他仓库、云存储或外部系统。
|
||||
|
||||
`manifest.json` 中的 `retention_until` 是默认 30 天到期时间。当前不自动删除;到期后如需清理,必须先确认具体运行目录。
|
||||
@@ -11,8 +11,8 @@ ClinDB-ReviewBench 是师姐的论文清洗项目。`data/` 下当前 5 份 DOI
|
||||
(JAMA、EJHF、Statistics in Medicine/arXiv、Springer/arXiv、Disaster Med Public Health Preparedness)
|
||||
是它的首批输入,未来会继续扩充同源转换产物。
|
||||
|
||||
本仓库(mdpolish)为该项目的数据提供清洗能力;ClinDB-ReviewBench 通过 profile
|
||||
表达论文场景的规则组合,不把论文专属规则写进通用核心。
|
||||
本仓库(mdpolish)为该项目的数据提供清洗能力。当前由仓库内 first-batch 实验脚本显式组合论文规则,
|
||||
尚未建立 Profile 对象或配置格式;论文专属规则仍不写进通用核心。
|
||||
|
||||
## 2. 第一版清洗目标
|
||||
|
||||
@@ -21,14 +21,14 @@ ClinDB-ReviewBench 是师姐的论文清洗项目。`data/` 下当前 5 份 DOI
|
||||
|
||||
| # | 问题(审计编号) | 规则要点 | 触发范围(本轮实测) |
|
||||
|---|---|---|---|
|
||||
| 1 | HTML 实体双重转义(D2) | `&gt;`→`>`、`&lt;`→`<`、`&amp;`→`&`,还原一层;幂等 | dmp L27/L33 共 23 处、ejhf L143 共 8 处,全部在 `<table>` 行内 |
|
||||
| 1 | HTML 实体双重转义(D2) | 只在严格 HTML 表格单元格文本中把 `&gt;`→`>`、`&lt;`→`<`、`&amp;`→`&`,解除源码的一层转义;幂等 | dmp 23 处、ejhf 8 处,共 31 处 |
|
||||
| 2 | arXiv 边栏戳(H1) | 只有整行满足 `design/0004-arxiv-submission-stamp-component.md` 第 4 节的严格格式才删除;编号条目内的 "arXiv preprint arXiv:…" 不受影响 | sim L1、springer L18;springer L143/L152 是合法参考文献,必须不误删 |
|
||||
| 3 | Word 审阅批注(B2) | 以 `Commented [xx]:` 开头的行及其批注正文整块删除 | jama L155–157 共 2 处 |
|
||||
| 4 | 手稿行号(B1) | 仅剥离**单调递增序列**的行首 `^\d{1,3} ` 与标题内 `^#{1,6} \d{1,3} `;序列中断即停止提出后续修改,防止误伤正文数字(如 "35 pediatric experts") | jama 128 行正文 + 6 个标题(L127/149/151/165/193/195) |
|
||||
| 3 | Word 审阅批注(B2) | 删除严格单行 `Commented [xx]: ...` 及其后第一个空行;不猜测多行批注正文 | jama 2 处 |
|
||||
| 4 | 手稿行号(B1) | 只处理唯一 `## Abstract` 后至少 20 个、严格递增且含至少 2 个标题证据的完整序列;剥离普通行和标题中的数字前缀 | jama 75 处,其中普通行 69、标题 6;Abstract 前 59 个作者单位编号必须保留 |
|
||||
| 5 | 跑动页眉(C4) | 同一文本行原样重复 ≥2 次(且非正文引用对象)判为页眉,删除并把被切断的上下文段落接回 | dmp L73/L191("MSOFA Score for Critical Care Triage"),L71→L75 句子被切断 |
|
||||
| 6 | 单行 HTML 表格展开(D1) | 无 rowspan/colspan 的表转多行 GFM;含合并属性的表保留 HTML 但按 `<tr>` 换行缩进;内容一字不改 | 全部 9 个表:dmp 7、ejhf 1、springer 1 |
|
||||
| 7 | 跨页断词(E2) | 行尾连字符 + 下一行首小写字母 → 合并;仅在拼出的词能通过英文词表校验时执行,否则保留原样且不提出修改 | dmp L125/127(thresh-olds)、sim L87/89(possi-bly)、L217/219(cre-ated) |
|
||||
| 8 | 参考文献分隔统一(G3) | `^\d+\. ` 条目之间统一一个空行 | dmp refs 19–33、springer refs 9–19(连续堆叠段) |
|
||||
| 6 | 单行 HTML 表格展开(D1) | 严格完整的单行表格保留 HTML、属性和单元格内容,只按 `<tr>` 换行缩进;当前不转 GFM | 全部 9 个表:dmp 7、ejhf 1、springer 1;每张都有非 `1` 的 `colspan` |
|
||||
| 7 | 跨页断词(E2) | 只按项目批准的左右片段和结果词映射合并相邻行或只隔一个空行的片段;不使用英文词表猜测 | dmp 2 处、jama 2 处、sim 2 处,共 6 处 |
|
||||
| 8 | 参考文献分隔统一(G3) | 只在准确 References 章节内,对从 1 开始连续递增的编号条目统一一个空行 | dmp 13 处、springer 15 处,共 28 处 |
|
||||
|
||||
本表只包含当前自动清洗核心能够承载的修改。原审计中的图片断链校验修复不了 Markdown,已移到第 3 节等待
|
||||
未来独立 Inspector 设计,不计入这 8 类自动清洗目标。
|
||||
@@ -54,20 +54,24 @@ ClinDB-ReviewBench 是师姐的论文清洗项目。`data/` 下当前 5 份 DOI
|
||||
|
||||
## 4. 输入输出边界
|
||||
|
||||
- 项目输入位于 `data/<转换结果目录>/markdowns/*.md`,原文件只读;
|
||||
- 当前核心只接收内存 Markdown 字符串并返回内存结果;文件输出位置和保存流程尚未设计,不回写、不覆盖原文件;
|
||||
- 当前批次不读取图片资产;未来 Inspector 和路径改写分别设计;
|
||||
- 每处实际修改必须在内存结果中记录组件、理由、原文、改后内容和批次哈希,保证可追踪。
|
||||
- 当前本地输入是 `data/md/` 中按论文缩写命名的 5 份 Markdown 副本,原文件只读;
|
||||
- 核心仍只接收内存 Markdown 字符串;本地实验层按 `design/0005` 将成功输出、JSON 审计和 diff 保存到
|
||||
`artifacts/<YYYY-MM-DD>/runs/<run_id>/`,不回写、不覆盖输入;
|
||||
- 当前批次不读取或复制图片资产;未来 Inspector、资产打包和路径改写分别设计;
|
||||
- 每处实际修改记录组件、理由、原文、改后内容和批次哈希,完整落盘字段以代码和 `design/0005` 为准。
|
||||
|
||||
## 5. 验收口径(第一版)
|
||||
|
||||
- 上述 8 类问题在本轮 5 份文件上的触发处全部按规则处理,处理数与审计报告的实测数字一致;
|
||||
- 上述 8 类问题在本轮 5 份文件上的触发处全部按规则处理,共产生 155 条 `Change`;
|
||||
- 5 份文件中未被任何规则命中的正文零变更——除表中列出的触发处外不得有任何其他 diff;
|
||||
- 幂等性:同一输入清洗两次,第二次产出与第一次完全一致;
|
||||
- 规则 2(arXiv 戳)在 springer 上的验收必须包含反向用例:L143/L152 参考文献原文保留;
|
||||
- 规则 4(行号)验收必须包含反向用例:正文中的 "35 pediatric experts"、"10 sites"、"4 continents"
|
||||
等数字开头/含数字短语不受影响。
|
||||
|
||||
2026-08-22 的保存型验收中,5 份文档全部为 `success`,各组件计数为:批注 2、行号 75、arXiv 2、页眉 2、
|
||||
断词 6、HTML 实体 31、表格布局 9、参考文献空行 28。第二次运行 5 份合计 0 条修改,输入运行前后不变。
|
||||
|
||||
## 6. 与审计报告的编号对应
|
||||
|
||||
本文的 8 类自动清洗规则对应 `scratch/data-5papers-cleaning-audit-2026-08-21.md` 的决策清单行:
|
||||
|
||||
Reference in New Issue
Block a user