Skip to content

Latest commit

 

History

History
417 lines (364 loc) · 27.9 KB

File metadata and controls

417 lines (364 loc) · 27.9 KB

FIXLOG — 兜底策略病例登记

每本"翻车"书一条病例,格式固定:

书目 / 引擎 | 现象 → 根因链 → 修补点(文件:函数)→ 回归测试 → 状态

阅读指南:结构判断的修补只落在三层—— 适配器层(stage1_*:把引擎脾气翻译成契约,只动文本形态)、 契约信任层(stage2_common:凡 LLM 输出皆首票,真值来自几何与投票)、 系统层(路径/打包/分片,与内容无关)。 每条守卫的失败方向必须是"不动作",不是"乱动作"。


病例 001|必须保卫社会 / PaddleOCR — 幻影章节

  • 现象:EPUB 被切成 20+ 章,章名错乱重复('one 7 JANUARY 1976' 出现在错误页)。
  • 根因链:目录页码被排成独立 aside_text 数字块(与条目分离)→ LLM 提取 toc_entries 配对不上,瞎编等差页码(真实 1/23/43… 编成 1/17/33…)→ _rescue_by_page 偏移投票全乱 → 按错误偏移在正文合成 8 个幻影标题块。
  • 修补(stage2_common.py):
    • _repair_toc_pages(新):条目块与数字块按 bbox y 坐标同行重配真实页码 (dy≤25 千分位贪心,支持罗马数字→置空,因其与正文偏移 regime 不同);
    • _rescue_by_page:偏移众数 <3 票直接放弃救援(票分散 = 页码不可信); 已满足判定加"完整键精确命中即满足"(强证据不看位置);
    • _build_anchors:剥尾页码保留完整形态('1976' 不是页码是年份); 恰好两级且高层级 ≤2 个无编号条目 → 收敛到多数层级(防 LLM 拔高前言);
    • _calibrate_levels:目录页标题降格(toc_pages 由 repair 识别: ≥3 条目命中 + ≥3 数字块);同页同文标题去重(含锚点富化后的重复);
    • _normalize_title:加 casefold('FOREWORD' ↔ 'Foreword: …')。
  • 回归:tests/test_stage2_toc.py — repair_* / rescue_* / anchor_* / calibrate_* 共 10 例;本书重跑树 = one–eleven 平铺、零幻影。
  • 状态:已修复并验证。

病例 002|必须保卫社会 / 系统层 — 文件名尾空格

  • 现象--skip-mineru 重跑时 FileNotFoundError: 工作目录找不到。
  • 根因:PDF 文件名 '...(David Macey) .pdf' 词干以空格结尾,Windows 建目录 时静默剥离,后续按原名 iterdir 失败。
  • 修补:pipeline.py / app.py 统一 stem.rstrip(" .")
  • 回归:本书重跑通过。状态:已修复。

病例 003|民法总论 / PaddleOCR — 轻量兜底截断 + 转义污染 + 目录多形态

  • 现象:首跑只剩 15 个平章,编层级丢失;二跑 53 章过度切分(目录条目 + 列表项混进树)。
  • 根因链(四个独立问题):
    1. 目录 ~200 条,_light_metadata_pass 单次响应超 max_tokens 截断 → toc_entries 全丢 → 0 锚点;
    2. 固定 200 页分片 → 3 片排队 48 分钟;
    3. PaddleOCR 输出 markdown 转义('1. 要件'、214 处 ']')→ 编号形状检测 失效 → 列表项被投成 L1;
    4. 简目(独立条目)+ 详目(点线页码 blob 块)并存,目录页识别单一形态 覆盖不了;且初版按行识别误杀章首页(章+本章节标题,形似小目录)。
  • 修补
    • stage2_common:轻量兜底失败拆两个紧凑重试(_LIGHT_META_PROMPT + _LIGHT_TOC_PROMPT 数组格式只采样书首);
    • stage1_paddleocr:_chunk_pages 按平均页体积自适应(≤45MB/≤1000 页, 本书单任务 709s,提速 4 倍);_MD_ESCAPE_RE 数学区外解转义;
    • stage2_common:_detect_toc_pages_by_entries(新):条目行命中 ≥3 + (数字行 ≥3 或占比 ≥50%)+ 印刷页码跨度 >30(章首页跨度≈0 不误杀)+ 邻页扩展(长目录残余页)。
  • 回归:tests 新增 parse_toc_array / unescape / detect_toc_pages_* 6 例; 终验 21 单元(6 编 15 章)与 MinerU 基准完全一致
  • 状态:已修复并验证。残留 cosmetic:'Default Title' 空章(popo 占位)、 第六编 重复一章(页眉被 _PART_HEADER 捞回,与真章扉页相邻同名)。

已知边界(不是 bug,暂不接)

  • LLM 标题投票有 ±1 轮波动(同一本书两跑标题数差几个)——锚点+形状栈 吸收大部分,残余为散文小标题被投成 L1(内容无损)。
  • LLM 提取的目录层级仍可能错(两级目录收敛只兜"拔高前言"一种形态)。
  • 页码与条目同行但 y 错位 >25 千分位的目录版式,重配会漏。

当前回归资产

  • 单元测试:tests/test_stage2_toc.py(23 例,每例对应真实病例场景, 运行 python tests/test_stage2_toc.py
  • 结构体检表:qc_book.py <work_dir>...(内容完整=红:非空块缺失/ 目录条目未锚上/幻影合成块;结构瑕疵=黄:重复标题/空章/降级 metadata; 自动配套 content_list 与 popo_blocks 的引擎版本)
  • 基准书目(结构真值):
    • 民法总论(561p,七层深目录):21 单元 = 6 编 15 章,874+ 页脚注 (MinerU / PaddleOCR 双引擎须一致)
    • 必须保卫社会(328p,英文,页码分离目录):one–eleven 平铺、0 幻影
    • teoh2010flame(21p 论文,跨页大表):5 章、11 HTML 表、上下标残留 0

病例 004|刘擎西方现代思想讲义 / PaddleOCR — 后页节标题三连丢

  • 现象:'答学友问'、'参考文献'、'人名索引' 三个后页节标题未锚上。
  • 根因链(三种独立的匹配缺口):
    1. '答学友问' 正文块是 '答学友问1'(差 1 字),模糊匹配门槛 len≥6 把它挡在门外;
    2. '人名索引 $^{①}$' 的上标脚注标记进了归一化键;
    3. '参考文献' 的正文标题只以 header 形态出现(PaddleOCR 把章节首页的 标题行当页眉),popo/convert.py 丢弃全部非编名 header。
  • 修补_match_anchor 模糊门槛 len≥4;_normalize_title$^{...}$ 与法式装饰前缀 '— X. —';popo/convert.py 页眉每种文本首次出现保留为 text(交锚点晋升裁决,编名仍直接捞回为标题)。
  • 顺带:同页缩写标题去重('21 | 阿伦特Ⅱ' vs 完整章名,去标点+前导 编号后前缀判定,同级弃缩写)——解决章占位页的"空章"问题。
  • 回归:anchor 52/55 → 55/55,黄牌及以下。
  • 状态:已修复并验证。

病例 005|Naissance de la clinique / PaddleOCR — 装饰前缀幻影

  • 现象:页码救援在 P194 合成 '— X. — La crise des flèvres' 幻影标题。
  • 根因:目录条目带法式装饰前缀 '— X. —',且 OCR 把 'flèvres' 识成 'fièvres'(l/i 一字之差)——真实块 'la crise des fièvres' 距离 >2 锚不上。
  • 修补_normalize_title 剥装饰前缀后距离=1,模糊命中,幻影消失。
  • 回归:anchor 13/13、synth=0。状态:已修复并验证。

病例 006|Condensed Matter Physics / PaddleOCR — 详目与截断条目

  • 现象:详目页(blob 形态,单页只覆盖一章小节)未被目录页识别; 4 个小节标题未锚上。
  • 根因
    1. 目录页识别的印刷页跨度门槛 30 太严——详目单页跨度仅 ~22; 章首页跨度≈0 才是误判源 → 门槛降至 5(章首页依旧安全);
    2. 轻量兜底紧凑重试的响应里末尾条目被 LLM 截断('9.2.1 Kau'), 前缀规则只覆盖"块是锚点前缀",不覆盖反向。
  • 修补:跨度门槛 30→5;_match_anchor 加"锚点是块的前缀" (限锚点 ≥6 字符,防 '1.1' 误配 '1.1.2')。
  • 回归:未锚 4 条全部锚上(168/172 → 172/172 待终验)。
  • 已知残留(黄牌,内容无损):运行头=章名的书会有跨页重复标题与 空章(缩写去重只管同页);'CHAPITRE VIII' 多页重复章名同类。
  • 状态:已修复,终验中。

病例 007|伊豆の踊子(竖排日语)/ PaddleOCR — 能力边界

  • 现象:竖排全书。体检仅黄牌:metadata 完整(川端康成/偕成社/ja), 34/34 标题锚定,5 个 'image' 空章(插页被投成标题)。
  • 结论:PaddleOCR-VL 对竖排日语的支持超出预期,正文可转; 'image' 空章与假名连浊误差属引擎层边界,结构层不硬修。
  • 状态:记录在案,不接。

病例 008|刘擎 + 全语料 / 结构层级深化(几何证据 + 下沉约束)

  • 背景:用户指出两类"能用但不好用"——CMP 图注被误判为标题造成层级紊乱; 刘擎篇内无编号小标题('思想内在于现实')吸附到讲次同级。
  • 机制修复(证据优先级:锚点 > 几何 > LLM 首票):
    1. 图注/表注位置过滤_calibrate_levels):与图块 x 重叠、垂直紧贴 (图注在图下/表注在表上 ≤3% 页高)、字号不明显大于正文的标题候选 降回正文;锚得上的绝不动。
    2. 无编号无锚标题下沉_sink_unanchored_plain):无编号标题若真是 编/章级目录里一定有它,锚不上就必须严格深于所属锚定章。
  • 连锁暴露的四个纠缠问题
    1. 斜杠页码('xxx / 060')未剥 → 目录页识别失效、目录整份混进树 (24 个空章)→ _TRAIL_PAGE_RE 统一剥点线/空格/斜杠/破折号;
    2. 系列守卫:'答学友问1..12' 全部模糊命中父锚点 '答学友问' 逃过下沉 → 块=锚点+数字/字母后缀时禁止模糊命中;
    3. 下沉必须在页码救援之后(系列块依赖救援先锚定首项);
    4. 模糊上限不对称:LLM 笔误让条目比正文长('PRÉSPACE' vs 'PRÉFACE') → 上限按两者较长者定(<8 容 1,否则容 2)。
  • QC 同步:page_fuzzy 位置晋升视为满足(系列块的合法锚定方式)。
  • 回归:刘擎空章 24→1、55/55 锚定、答学友问系列正确嵌套; 单元测试 32/32。
  • 状态:已修复并验证。25 45 23

病例 009|全语料 / 目录页识别升级 + 运行头收敛 + 孤儿编号

  • 目录页识别第三判据(用户提议的"页码特征"落地):点线引导行 ('xxx …… 60'、'xxx ..... 153')几乎只出现在目录/索引页,≥3 行且 页码跨度 >5 即判目录页——与引擎和条目文本都无关,是最本质的判据。 罗马数字页码在 _repair_toc_pages 已处理(置空不参与偏移)。
  • 运行头收敛:同一标题文本在更早页面已出现、本块位于页首(y2≤8%) → 页眉重复降回正文(锚点视为已被首次出现消费)。CMP 跨页重复标题 的主要来源。
  • 孤儿编号先救后罚_fix_orphan_series):某编号家族全书无 '1' 且系列 ≥3 起跳 → 几乎必是列表项误判('4.' 孤挂层级顶);先在附近 ±3 页找 '1' 晋升救回,找不到才把整个系列降回正文。2 起跳保守不罚。
  • 运行头规则的一个教训:初版"锚定块不动"使规则完全失效(运行头 与章名同文,也能锚上)——锚点必须视为被首次出现消费。
  • 回归:单元测试 35/35;全语料终验见当轮汇报。
  • 状态:已修复并验证。

病例 009 终验结果(2026-08-03)

  • 必须保卫社会:🟢 全绿;刘擎 55/55(空章 1);民法总论 165/165; 伊豆 26/26;Naissance 13/13。
  • CMP 重复标题 31→0(运行头收敛生效),但体检新红 4 条未锚——排查结论 非本轮回归:'10 Crystal dynamics' 等 2 条在 content_list 中根本不存在 (PaddleOCR 漏识别章头,正文内容完好;稠密章首页按设计不合成标题); '13 Electrons in the periodic potential' 同;'A.6 Elements of statistical mechanics' 是印刷目录与正文标题的真实措辞差异(目录多 'Elements of', 编辑距离 9 超出模糊上限)。三条均属引擎层边界,结构层不接。 本轮 toc_entries 191 条(LLM 抽取波动,上轮 172 条不含这 4 条)。

已知边界(更新)

  • 章头被 OCR 整块漏识别且首页稠密:内容完好但标题不进目录 (稠密页不合成是有意取舍——插错位置比缺标题更糟)。
  • 印刷目录与正文标题措辞差异大('Elements of statistical mechanics' vs 'Statistical mechanics'):超出模糊匹配上限,需更松的语义匹配,暂不接。
  • 完整几何判别(垂直留白/对齐偏离/字号聚类定层级)尚未实现; 已落地的是图注位置过滤与页首运行头收敛两个几何点。

病例 010|全语料 / 字号阶梯(pdf-craft 方案的几何判别)

  • 背景:完整几何判别的落地。调研 pdf-craft(AGPL)后确认其核心武器 是 split_by_cv 字号聚类——它不读 PDF 文本层(全书光栅化),"字号" 就是 bbox 高度,与我们 bbox-only 输入同构。居中/留白/对齐它一律没用, 证明那些不是必需品。按算法思想重写,未搬代码
  • 实现(stage2_common.py):
    • _split_by_cv:变异系数阈值 + 最大间隔二分,把标题块高度(bbox 高,0..1,标题通常单行一块无需多行聚合)聚成字高档位(rank 0 最大);
    • _height_ladder_map:用锚定标题的真实层级标定每个档位 → rank→level 映射(无锚定的档位向更浅的借一级);
    • 接入 _sink_unanchored_plain:无编号无锚标题在下沉硬约束 (不得浅于所属锚定章+1)之上,按字高档位只许加深、不许上浮—— 字大的小标题停在本章下一级,字小的继续沉。max_cv=0.1(宁细勿粗, 误分只会在保守方向)。
  • 回归:单元测试 37/37;全语料终验见当轮汇报。
  • 状态:已实现并验证。

几何判别最终清单(三轮沉淀)

特征 用途 状态
bbox 位置邻接(图下/表上) 图注/表注过滤 ✅ 病例 008
页首位置 + 前文同名 运行头收敛 ✅ 病例 009
bbox 高度聚类(split_by_cv) 无锚标题层级差异化 ✅ 病例 010
垂直留白/对齐偏离 评估后放弃(pdf-craft 也不用) ❌ 不接

病例 011|全局一致性定级(LLM 收口)+ 运行头阈值修正

  • 全局一致性定级_global_level_pass,stage2_common,开关 GLOBAL_LEVEL_PASS):确定性规则把候选标题列表洗干净后,把全书 标题表(ID/页/形状/锚定/文本)交给 LLM 一次定级,替代分块局部投票 的漂移。锚定锁死、无编号标题受下沉底线钳制、层级钳 1..8、失败 自动保持现有层级(安全降级)。
  • A/B 验证:刘擎 0 调整(LLM 完全同意确定性路径——最强佐证)、 CMP 30 调整(锚定 168/168 不变)、民法总论 105 调整(空章 6→13 后 回落,见下)。
  • 运行头阈值修正:民法总论出现全章节重复,根因是页眉 y2=0.084 恰好超过 0.08 阈值(本轮 LLM 投票波动使更多页眉被投成标题,暴露了 阈值盲区)→ 放宽到 0.10,15 章零重复恢复基准。教训:几何阈值 必须留足 OCR 抖动余量,且要在多本书上标定
  • 回归:单元测试 39/39。
  • 状态:已实现并验证。

病例 012|发版回归(高等数学/Born a Crime/城市与国家财富)— 匹配层收尾

  • 现象:发版回归 3 本全管线,4 条目录条目未锚上。
  • 根因与修补(全部是标题键归一化缺口,_normalize_title):
    1. 公式节标题:目录写 $f(x)=..$型,正文是 equation 块 $$ 一、f(x)=.. 型 $$ → 剥 $ 定界符对齐;
    2. LaTeX 排版命令:正文 \left[ \right]、目录裸括号 → 剥 \left/\right (残余 1↔i 一字 OCR 误差由模糊匹配兜住);
    3. 弯直引号:正文大写直撇号、目录弯撇号(Born a Crime ch14 长标题) → 弯引号统一为直引;
    4. 超短尾部:章名碎块 'RUN'(3 字母)对 'Chapter 1: Run' → 尾部匹配 len≥4 放宽到 ≥3。
  • 边界(不接):Born a Crime 'Dedication' 正文无标题块(引擎漏识, 内容完好);高等数学/Born a Crime 各剩 1 条未锚属此类引擎边界。
  • 回归:单元测试 40/40;高等数学 140/140、Born a Crime 21/22 ('Chapter 1: Run' 锚上)、城市与国家财富 16/16。
  • 状态:已修复并验证。

病例 016|高等数学 / stage3 — 公式不居中复燃:双 display 属性 + 正则跨段吞并

  • 现象:8/15 修复后用户跑回归仍报"公式不居中、标题 heading level 不对 (TOC 是对的)"。审计新 EPUB:2726 处 block 看似正常,但 257 处独占段落的 显示公式仍 inline 贴左;且章标题(h2.chapter-title)与节标题(h2)同级, 节标题渲染成居中大字与章视觉平级。
  • 根因链(四条独立缺陷,层层掩盖)
    1. 双 display 属性_latex_to_mathml):latex2mathml 的 convert() 自带 display="inline"(标签尾部),我们再插一份 alttext+display<math alttext display="inline" xmlns display="inline">。ebooklib 序列化去重后 inline 胜出——promote 替换第一个属性后仍被第二个抵消, 升格全部落空($$ 定界公式的 block 同病);
    2. 正则跨段吞并_P_LONE_MATH_RE):math 部分 .*?</math> 懒惰匹配 允许跨 </p>——前段 math 的 post 超长匹配失败时回溯扩展到后段的 </math>,形成跨段大匹配;repl 按前段 latex 判定不合格即整体原样 返回,后段(如 '(1) f(x)=\left{...' 例题)永远失去单独匹配机会。 此 bug 单段调用永不暴露(单测全绿、探针全升、整文却漏),且反向造成 误升(大匹配判定通过时连带升格前段的行内引用公式);
    3. pre 过严_PUNCT_NUM_RE 剥空才放行——"解/证/双曲正弦/例1设"等 短叙述前缀的整段显示公式 74 处贴左(附录习题答案重灾区);
    4. 标题层级映射:popo 路径 h{min(level,6)} 直用结构层级,节与章 撞 h2(章标题固定 h2)。
  • 修补(stage3_epub.py):
    • _latex_to_mathml:替换 latex2mathml 自带 display 的,不再另插;
    • _P_LONE_MATH_RE:math 部分改 tempered token (?:(?!</p>).)*? 禁跨段, replace(..., 1) 改全量替换(防御双属性残留);
    • pre 放宽:[^<$]{0,12} + 剥标点数字编号后 ≤4 字放行(post 保持严格, 行文行内引用一律被 post 挡住);_PUNCT_NUM_RE 补圈号 ①-⑳ ㈠-㈩;
    • 标题层级:章内标题按相对章深度偏移(节 → h3、小节 → h4,两条渲染 路径同规);CSS h3 改居中(贴合原书节标题居中排版)。
  • 回归:新增 tests/test_stage3_promote.py 12 例(含双 display、跨段吞并 场景——后者复现整文/单段分歧);结构单测 40/40;高数重跑 block 2726→ 3223(误升消退后净升 497)、独段 inline 257→156(残余=短答案行不该升)、 '(1) f(x)' 段升 block、章 h2/节 h3 层级正确、QC formula_artifacts 0。
  • 边界(不接):multi_math 13 处——同一公式被引擎拆成两块(block 主体 已居中,"+C" 尾巴 inline 残留),合并 MathML 过于脆弱,记引擎层边界。
  • 状态:已修复并验证。

病例 016 补记(同日二轮)——第五连环:CSS 破坏 UA 的 block math 布局

  • 现象:上述修复全部落地、EPUB 文件验证无误后,用户在 SageRead 实例里 看到的公式仍然全部贴左(截图实证)。
  • 排查(CDP 穿透 foliate 的 closed shadow root 实测 computed style): block 公式的 text-align: center 已生效,但视觉贴左——因为 DEFAULT_CSS 写了 math[display="block"] { display: block; },把 UA 样式表的 display: block math(MathML Core 的块级数学布局,内容自动 水平居中)覆盖成普通块盒,数学内容 shrink-to-fit 贴左;text-align 管 不到数学布局内部。A/B 实证:把 display 恢复 block math 后整页公式 即刻居中。
  • 修补
    • 转换侧(stage3_epub.py DEFAULT_CSS):删除 display: block 声明, 保留 text-align:center 作冗余保险——新产物不再破坏 UA 布局;
    • 阅读侧(SageRead utils/style.ts 注入样式):显式 math[display="block"] { display: block math; text-align: center; } ——存量旧产物无需重转,注入样式统一兜底(SageRead commit 同步)。
  • 教训:EPUB 文件级验证(解包看 HTML/CSS)与渲染级验证(阅读器实测) 必须都过——本案文件完全正确、渲染却是坏的;"315/315 居中"的验证当时 只抽查了 display 属性,没看最终像素。
  • 回归:实例内打开未重转的 v1.3.2 版高数(旧 CSS),注入样式兜底后 习题区行列式/分式/编号公式全部居中,节标题层级正确。
  • 状态:已修复并验证(exe/zip 重打,Release v1.3.2 资产已更新)。

病例 015|高等数学 / MinerU—stage3 — 显示公式被降级 inline 贴左

  • 现象:8/15 干净重跑后用户仍报"块级公式不居中"(对照外部 z-lib 图片版 居中)。DOM 实测 display=block 的公式全部精确居中——不居中的根本不是 它们:审计发现 840 处显示型公式(例题 y=frac…;、定义域映射、推导链) 被标 display="inline",独占段落却按行内排,吃 2em 首行缩进贴左、行内 字号。
  • 根因链:MinerU 把显示公式以单 $…$ 定界输出;_mathmlify 的行内分支 照单全收转 display="inline"。(8/4 paddleocr 脏解析事件修复后,同一本书 剩余的"不居中"观感即源于此——与阅读器无关。)
  • 修补(stage3_epub.py):promote_lone_display_math 段落级后处理—— 整段唯一数学区 + LaTeX 含显示结构命令(frac/left/sum/…)且长于 25 字符
    • 前后仅标点/编号 → 升 display="block";叙述前缀("双曲正弦"/"例 1 设"/ ",即…")的行内引用不动。四个内容发射点(正文/前置/后置/ch_html)接线。
  • 回归:高等数学重跑,block 2008→2726(升格 718 处),剩余 183 处 独段 inline 均带叙述前缀属正确行内;应用内实测第一章 315 个可见块级 公式 315 居中 0 偏心;升格判定单测 6 例全绿。
  • 状态:已修复并验证(exe 已重打同步双实例 binaries)。

病例 013|QFT 两本书 / 打包 — 全书公式退化为 LaTeX 源码

  • 现象:A Modern Introduction to QFT / QFT and the Standard Model 转出的 EPUB 全书公式都是 <code class="latex"> 裸 LaTeX 源码(阅读器 原样显示源码);同期高等数学(外部 EPUB,前缀式 MathML)渲染正常, 排除阅读器侧嫌疑。
  • 根因链:latex2mathml 的符号表 unimathsymbols.txt 是包内数据文件, symbols_parser 运行时 open 读取;books_converter_cli.spec 的 hiddenimports 只收模块不收数据文件 → frozen exe 里符号表缺失 → 每条公式转 MathML 都 FileNotFoundError,被 _latex_to_mathmlexcept Exception 吞成 None → _mathmlify 全量走 <code> 兜底。 无公式书不触发,此前未暴露。
  • 修补(books_converter_cli.spec):datas=collect_data_files('latex2mathml') (与 papers_converter_cli.spec 收 pypinyin 数据同款方案)。
  • 回归:archive_viewer 确认新 exe 含 unimathsymbols.txt;旧 exe 从 staging 重跑复现(chapter_004: 0 MathML/126 code),新 exe 同 staging 重跑(全书 16609 MathML/1 兜底);两本坏书已从 staging 重跑 stage3 并原位替换 book.epub,应用内 foliate 实测 1108 math/1044 渲染/0 兜底。
  • 状态:已修复并验证(exe 已同步 SageRead 双实例 binaries)。

病例 014|QFT 两本书 / stage3 — 表格·表注·图注·标题里的公式漏转

  • 现象:正文段落公式已渲染(病例 013 修复后),但表格单元格、表注/ 图注、章节标题里的公式仍是裸 $…$ 源码(如 h4 "26.3 $e^{+}e^{-} \rightarrow$ hadrons"、td "$\left(\frac{1}{2},0\right)$")。
  • 根因链_mathmlify 只挂在正文段落渲染路径(_render_popo_body 的 seg 处理);标题(<hN id>/chapter-title/part-title 四处发射点)、图注/ 表注(caption_map/small/strong)、表体(MinerU HTML 原样透传)、列表项 均未过公式转换。旧 _render_block_to_html 路径同病。
  • 修补(stage3_epub.py):
    • 上述全部发射点补 _mathmlify(alt 属性除外——属性值不能放 MathML);
    • equation 块兼容 latex/text 字段形态:纯 LaTeX 也尝试转 MathML, 失败才退 <code class="latex">
    • DEFAULT_CSS 表格改 width: fit-content; max-width: 100%; margin: 1em auto(窄表按自然宽度居中,宽表占满列宽——旧版 width:100% 把所有表拉满整行)。
  • 补丁(同日二轮):前置/后置物质走旧路径 _render_block_to_html——首轮批量 补丁脚本因末对断言失败在写盘前退出,该路径的图注/表注/表体补丁未落盘 (高等数学回归实测附录图注裸 $y=�rctan x$);Edit 补齐后高等数学 11212 MathML、Standard Model 47/16/25 无回归。GUI spec 同步补 latex2mathml hiddenimport+数据文件(GUI 版同病)。
  • 回归:两本 QFT 书从 staging 重跑(--skip-mineru --skip-deepseek), EPUB 内 td math 47/41、注 16/31、标题 25/5,裸 $ 清零;应用内 foliate 实测单元格/注/标题 MathML 渲染、表格与 block 公式均居中。
  • 状态:已修复并验证(exe 已重打同步双实例 binaries)。

病例 017|Calculus Made Easy(Gutenberg 公版书)/ stage2 — 无目录书 LLM 编造假目录,层级毒化

  • 现象:born-digital 公版书无印刷目录页,轻量元数据 pass 的 LLM 不返回空 toc_entries,而是拿 prompt 附带的【全书标题列表】编造一份 假目录(page 直接抄标题块的扫描页码)。假条目经锚点以最高优先级锁死 错误层级,形状栈/字号阶梯全被跳过,文档树结构散架。
  • 根因链:LLM 面对"提取目录"指令不会空手而归——采样页里没有目录页, 就拿现成的标题列表拼一份;且 page 抄扫描页(真目录应给印刷页,与扫描页 通常有非零偏移),形成确定性的伪造指纹。
  • 修补
    • stage2_common.py finish_structure 伪造目录双判定硬兜底: 判定一(主)两个目录页识别器均未命中 → 判定无印刷目录,丢弃 LLM toc_entries;判定二(双保险)_forged_toc_fingerprint——≥5 条可比对 且 ≥80% 条目页码与标题块扫描页完全相等 → 丢弃。丢弃后回退 形状栈+编号先验路径;
    • _LIGHT_PROMPT / _LIGHT_TOC_PROMPT 软约束:无目录页必须输出 [], 严禁编造(软约束不足以依赖,故需硬兜底);
    • setdefaultor 防御:LLM 把字段输出成 null 时 setdefault 挡不住 (None 不是缺失键);
    • pipeline.py _read_pdf_outline:fitz get_toc 读取 PDF 书签 (born-digital PDF 的免费结构真值,1 起物理页码与扫描页同 regime), 经 stage2_hybrid 透传 pdf_toc,非空时取代 LLM toc_entries 且不参与 伪造判定;扫描本无书签 → 返回 [],原流程不变;
    • qc_book.py 连带:无目录书不再当硬错误,降为黄牌提示。
  • 回归:tests/test_stage2_toc.py 新增伪造指纹用例,全套 41/41 + stage3 12/12 绿;伪造条目回放走丢弃路径实测通过;Calculus Made Easy (Gutenberg #33283)60 页重转目录两级正确、477 MathML 居中。
  • 状态:已修复并验证(源码路径;exe 重打与 SageRead 同步随下次发版进行)。