PDF 全文翻译 → 可编辑 Word(Report-Full-TNA-docx)
> 姊妹 skill:Report-Full-TNA-html(同一前端,输出单文件 HTML)。
> 两者不是两条互斥管线,而是「共用前端 + 两个渲染器」:
>
> > PDF ──► 提取原文 ──► 脱敏判别 ──► 网格表重建 ──► 分段翻译 ──► out/zh/part_XX.md
> │
> ┌───────────────────────────┴───────────────┐
> ▼ ▼
> Report-Full-TNA-html/scripts/ 本 skill/scripts/
> build.py md2docx.py
> ▼ ▼
> 单文件 HTML 中文 .docx
>
何时使用 / 何时不用
用本 skill:用户要 .docx;要可编辑(改措辞、加批注);要走审批流/递交;要交给不用 HTML 的同事。
改用 Report-Full-TNA-html:要单文件、要保留原文件原图且位置与图注不变、要侧边目录 + 可打印/存 PDF、图很多(几十到上百张)。
两者都要(常见于「先内部审阅、后归档」):
译文 md 只有一份,跑两次渲染即可,不要翻译两遍。
都不适用:
- 扫描件 PDF(无文字层)→ 先 OCR,见「边界与失败模式」
- 只要摘要/要点,不要全文 → 直接读 PDF 回答,不必走管线
- 加密不可提取的 PDF → 先向用户要解密版
环境
# 1) 解释器:显式指定你的 venv(建议写进 ~/.bashrc 或 ~/.zshrc,之后开箱即用)
export PY="<你的 venv 路径>/Scripts/python" # Windows
# export PY="<你的 venv 路径>/bin/python" # macOS / Linux
PY="${PY:-$(command -v python3 || command -v python)}"
# 2) 脚本目录:自动定位,不写死绝对路径
# —— 技能会装到不同客户端、不同盘符、不同用户目录,写死的路径在别人机器上必然失效
find_skill_dir() {
for base in "$HOME/.workbuddy/skills" "$HOME/.claude/skills" "./.workbuddy/skills"; do
d=$(ls -d "$base/$1"* 2>/dev/null | head -1); [ -n "$d" ] && echo "$d" && return 0
done; return 1
}
HTML_SKILL=$(find_skill_dir "Report-Full-TNA-html") \
|| { echo "未找到 Report-Full-TNA-html,请先安装该技能(本 skill 依赖它的前端脚本)"; exit 1; }
SHARED="$HTML_SKILL/scripts" # 共用前端(Step 1–4)
DOCX="$(find_skill_dir "Report-Full-TNA-docx")/scripts" # 本 skill 渲染器
依赖:pymupdf、pdfplumber、pypdf、python-docx
(缺装时:$PY -m pip install pymupdf pdfplumber pypdf python-docx)
(不要用系统裸 Python;Git Bash 下命令需带 PYTHONUTF8=1 PYTHONIOENCODING=utf-8,
传给 pymupdf 的路径要用 Windows 形式 C:/...,/c/... 不认。)
> ⚠ 翻译本身不调用任何在线翻译 API(沙箱无外网,且机翻质量不达标)。
> 翻译由主 Agent 或后台子代理完成,产出 Markdown 分块。
端到端步骤
Step 0 — 前置依赖自检(首次使用必做,别跳过)
本 skill 只提供渲染器 md2docx.py;Step 1–4 的四个前端脚本由 Report-Full-TNA-html 提供。两个技能必须同时安装,否则管线在第一步就断,且报错不会告诉你缺的是另一个技能。
> 为什么不把脚本复制一份进来? 共享代码若存在两份,改一处另一处不更新,必然产生版本漂移(坐标算法、脱敏判别这种东西漂一点就得出错译文)。共享代码只应有一份真源,缺依赖应该在入口显式报出,而不是靠复制来掩盖。
[ -f "$SHARED/extract_text.py" ] || { echo "缺少 Report-Full-TNA-html,请先安装它"; exit 1; }
for f in extract_text check_redaction grid_table verify; do
[ -f "$SHARED/$f.py" ] || echo "缺少 $f.py —— html 版过旧,请更新到最新版"
done
[ -f "$DOCX/md2docx.py" ] || echo "缺少渲染器 md2docx.py"
"$PY" -c "import docx, fitz, pdfplumber" 2>/dev/null || echo "缺 Python 依赖,见「环境」一节"
若缺 html 版:唯一正确解法是安装 Report-Full-TNA-html——不要自己手写替代品,那会在坐标重建与脱敏判别上引入偏差,且这类偏差不会报错,只会安静地产出错译文。
Step 1 — 提取原文(事实源)
PYTHONUTF8=1 $PY "$SHARED/extract_text.py" "C:/path/src.pdf" out/full_raw.txt
产出 ===== PAGE N ===== 分隔的纯文本。这是后续一切的事实源:翻译以它为准,校验以它为准。
保留一份未做任何替换的备份(如 out/full_raw_full.txt)——提取阶段任何"顺手处理"都要在备份之后。
Step 2 — 脱敏判别(若 PDF 有黑框 / 文件名带 _redacted)
PYTHONUTF8=1 $PY "$SHARED/check_redaction.py" "C:/path/src.pdf" --samples 15
| 黑框数 | 被覆盖 span | ■ 字形 | 结论 | 处理 |
|---|---|---|---|---|
| 多 | 多 | 0 | 视觉脱敏 → 可恢复 | 用完整文本,被覆盖内容照常翻译写入 |
| — | 0 | 多 | 真脱敏 → 不可恢复 | 保留 ■ 占位并注明 |
| 多 | 多 | 多 | 混合 | 逐处判断 |
⚠ 最贵的坑:别把"视觉脱敏"误判成内容已删、用替换成 ■ 的提取器——那会让整轮译文作废重译。
Step 3 — 网格大表坐标重建(跨页/多层表头/合并格)
PYTHONUTF8=1 $PY "$SHARED/grid_table.py" "C:/path/src.pdf" --pages 68-70 --hrows 2 --out out/soa.md
--pages 必须含表头所在页;续页用 --hrows 0;--mincol(默认 8pt)自动合并双线边框产生的极窄伪列。
列归属必须坐标验证,不能靠语义猜;输出行数骤减 = 踩了表头去重坑或页区间漏了表头页。
Step 4 — 分段翻译(唯一真源:Markdown 分块)
按页切成 out/zh/part_01.md … part_12.md,每块约 6–15 页原文:
- 页锚
<!--PAGE:N-->(必须逐页齐全,缺失通常是 Agent 合并了页块,先核对内容再补锚,别重译) - 表格写成 GFM Markdown 表
- 图槽
@@FIG:p:n@@(docx 渲染时转为占位段落) - 术语规范写进
out/translation_guide.md,并行翻译前先写它——多子代理各翻各的必然漂移
派发节奏:后台子代理每批 4–5 个,别一次全派(易限流/502)。被中断后先盘 out/zh/ 落盘状态再补派。
Step 5 — 渲染 docx
PYTHONUTF8=1 $PY "$DOCX/md2docx.py" out/zh \
--out "中文译本.docx" --title "XXX(中文译本)" \
--shift -2 --toc
| 参数 | 作用 |
|---|---|
--shift N |
标题层级整体偏移。HTML 流水线常把层级压得很深,出 docx 建议 -1~-2 |
--toc |
文首插 TOC 域(Word 里 Ctrl+A 后 F9 生成目录) |
--pagebreak |
页锚处插分页符(默认只插浅灰「— 原文第 N 页 —」标记,便于与原文对照) |
--nopagemark |
完全忽略页锚 |
--font-cn/--font-en/--size |
默认宋体 / Times New Roman / 10.5pt |
--imgwidth |
图片最大宽度(cm,默认 15) |
--no-header-repeat |
表格首行不跨页重复 |
实测基准(127 页 APG-2575 方案,12 个 md 分块 / 15.3 万字):
→ 段落 1506、标题 197、表格 47(475 行,含 15 列 SoA 与 9 列统计界值表)、页锚 128、0.16 MB、约 8 秒。
Word 渲染的三个坑(都是踩过的,勿改)
- 绝不使用 Word 的
List Bullet/List Number样式。那会在文本已有的 "•" / "N." 之上再加一个标记 → 双重项目符号;且List Number会从 1 重新编号,丢失入选标准原编号("4. ECOG≤2" 变 "1. 4. ECOG≤2")。
→ 列表一律渲染为普通段落 + 悬挂缩进,保留文本原有标记;孤立的项目符号段直接跳过。 - 中文字体必须同时设
w:eastAsia。只设run.font.name会让中文回退成默认字体。
→run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体'),标题样式同理。 - 宽表(>8 列)会撑破纸面。
→ 自动降到 8pt;仍过宽则改横向页面或在 Word 里调。
附:表格网格过度切分("800mg" 被切成 "800m"+"g")已由前端 grid_table.py 的坐标重建统一解决,
不再需要 pdfplumber 词聚类那套补丁。
术语与格式约定
术语总表以 Report-Full-TNA-html/SKILL.md「临床方案 Profile B」为唯一真源,两边共用,不要各写一份。要点:
- 药物名首现中英对照(lisaftoclax(APG-2575)、阿扎胞苷(Azacitidine, AZA)),后续从简
- 研究代码 / 方案编号 / 访视周期代码(APG-2575、GLORA-4、C1D1、C2+D15、EOT)保留英文
- 缩略语首现「中文全称(English, 缩写)」;统计方法保留原名(Lan-DeMets、Cochran-Mantel-Haenszel…)
- 数字、剂量、单位、实验室值、受试者编号、日期、时间窗原样不换算
- 章节号、入选/排除标准号、表号图号不重排;参考文献保留英文原文
- 页码 "Page N of 105" → "第N页,共105页";"Table X"→"表X";"Section X"→"第X节"
表格是否译中文——开工前问清,不要默认(这是两份 skill 曾冲突的地方):
| 场景 | 处理 |
|---|---|
| 内部审阅 / 阅读型交付(默认) | 表格译为中文,与正文一致 |
| 药政递交 / 需与原文逐字对照 | 表格保留英文,派发前写进 translation_guide.md |
图:docx 无法承载 @@FIG 图槽,渲染为占位段落「〔图 p-n:见配套图片文件〕」。
确需插图时,在 md 里写 ,图片与 docx 一并交付。
边界与失败模式
| 情况 | 判据 | 处理 |
|---|---|---|
| 扫描件(无文字层) | full_raw.txt 极短或几乎为空 |
停止,告知用户需 OCR;别硬翻空白 |
| 加密 PDF | pymupdf.open 报权限错误 |
向用户要解密版 |
| 超大文档(>150 页) | 段落数将达数千 | 仍可出 docx,但编辑体验差;建议同时给 HTML 版,或直接问用户 |
| 图很多(>30 张) | — | 建议走 HTML 版(docx 只能占位或外挂图片) |
| 子代理中断 | 部分 part_XX.md 未落盘 |
先盘落盘状态再补派,不要盲重发 |
交付检查清单
- [ ]
verify.py(前端)页锚数 = 原文页数 + 附页;无缺页无重复 - [ ] docx 段落数、表格数与 md 源吻合
- [ ] 无
• •/。•/ "1. 4." 双重标记;未使用任何 List 样式 - [ ] 入选标准原编号(4. 5. 6. …)保留正确
- [ ] 表格列无断裂数字;宽表(>8 列)已缩字号
- [ ] 中文字体生效(
w:eastAsia已设),标题层级用--shift提过 - [ ] 页锚以「— 原文第 N 页 —」标记或分页符呈现,可与原文对照
- [ ] 术语与 html 版一致(同一份
translation_guide.md) - [ ] 结尾「保留未译与存疑术语清单」已附(Profile B 类文档)
- [ ]
present_files已调用