Files
CADx-4/research-wiki/guides/医生核对包.md
T
iomgaa c7373efde2 feat(review): 医生核对包入口;CEUS 运行目录的读取收进 runs
- 新模块 review(组织层,只依赖 library、answers、runs):把一次构建和它装入的
  CEUS 运行整理成医生可读的文件夹(CSV 带 BOM、说明与报告转 PDF、图片视频逐字节拷贝),
  入口 scripts/review_pack.py + config/review_pack.yaml。任务书 03。
- 读 CEUS 运行目录(failed.csv、结果目录、关键帧文件)从 build 挪进 runs,build 与
  review 都经它读;failed.csv 读取在边界上收严(步号须为整数、视频号不重复、列数固定)。
- library、answers 补只读函数;import-linter 增 review 契约。
- 文档:guides/医生核对包.md(过一轮硕士生阅读),结构.md 第三至八部分同步。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 06:04:19 -04:00

124 lines
13 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.
# 医生核对包
医生核对包是一个文件夹,交给临床方的医生,请他们核对我们对交付资料的处理结果。医生不用写代码就能打开:表格是 CSV,文字是 PDF,图和视频原样拷贝。本文写给出包的人:什么时候跑、怎么跑、产物在哪、怎么交出去、报错了怎么办。为什么做、包里每个文件的格式,见 `../任务书/03-医生核对包.md`;它在代码里的位置见 `../explanation/结构.md` 第三、四部分。
## 先认识几个词
- **病例号**:交付包里每个病例的真实编号,核对包里写成 `P` 加三位数字(如 `P001`),和临床工作簿里的写法相同。
- **病灶**:病人胰腺上的那个囊性肿瘤,CEUS 定量要在视频里把它圈出来。
- **构建**:跑一次 `scripts/build.py`(`装配两个库.md`),产出同号的资料库 `library/<构建号>/` 和标准答案库 `answers/<构建号>/`。
- **资料库**:给 AI 专家看的资料,按临床、超声、MR 三路分开。
- **答案库**:放病理诊断(金标准,即术后或穿刺的病理结论)、化名与病例号的对照、跳过清单(没进资料库的病例和原因)、复核清单(报告里可能带诊断倾向的句子)。
- **资料库清单 `manifest.json`**:资料库根目录下的一个文件,记这次构建的构建号、每个文件的哈希,以及 `ceus_source`:这次构建装入的那次 CEUS 运行的运行号(没装为 null)。它只记运行号,不记运行目录的路径。
- **化名**:资料库里代替病例号的 4 位随机名字,只用在资料库里。交付表按诊断分组录入,病例号的先后本身带着答案。专家看到病例号,就可能从编号推出诊断(`AGENTS.md` 硬性约束 1)。
- **核对包为什么可以用病例号**:核对包是给医生看的,不给 AI 专家;里面本来就有诊断(答案库的诊断表原样在包里),不进专家能读到的任何资料。所以病例目录直接用病例号,化名只在表里出现,方便以后对回资料库。答案库原本「只给评价用」,核对包是另一类用途:把答案库连同资料库和 CEUS 结果一起交给临床方核对,范围仍限于我们和临床方之间(`AGENTS.md` 硬性约束 3)。
- **CEUS 定量运行**:跑一次 `scripts/ceus_quant.py`(`CEUS定量.md`),产物在 `runs/ceus/<CEUS运行号>/`。
- **`failed.csv`**:这次运行里记各病例在第几步失败、失败原因的表。
- **视频号**:每例 CEUS 视频的编号,`U` 加三位病例号(如 `U001`),CEUS 运行的结果目录按它命名。
- **TIC、拟合**:TIC(时间-强度曲线)是病灶区域平均亮度随时间的变化。拟合是用一条光滑的模型曲线(Lognormal,对数正态形状)去贴 TIC。拟合要过一道质量门才算成功;没成功时,关键帧 D1–D4 和灌注指标都取不到。
- **紫圈**:临床方在视频上画的紫色圈,圈一块正常胰腺作对照,程序也为它画一条 TIC。
- **关键帧、槽位**:从视频里挑出的 7 个时刻的画面,每个时刻叫一个槽位。B0 是刚开始变亮之前,D1–D4 是从拟合曲线上取的开始变亮、变亮最快、最亮、变暗最快,F1、F2 是推注造影剂后第 20、60 秒。细节见包里的 `说明.pdf`。
- **回放视频**:原视频逐帧叠加程序画的病灶轮廓等线条,医生用它看程序圈得对不对。
- **问题清单**:包里的 `问题清单.csv`,列出程序在每个病例上观察到的现象:某例没进资料库、CEUS 在第几步失败、拟合没完成、关键帧没取到、紫圈曲线不可用。每条都抄自某个已有文件里的值,只写现象,不写原因推测和建议。这是负责人 2026-10-17 定的(任务书 03「不做什么」):包只请医生核对事实,推测会把医生的注意力引向我们的猜测。
- **三种编号**:格式都是 `YYYYMMDD-HHMMSS-<git 短哈希>`,工作区有未提交改动时末尾加 `-dirty`(`装配两个库.md` 第三节「构建号」)。三者各指一样东西,本文用不同的名字区分:
- **构建号**:一次构建,即 `library/<构建号>/`、`answers/<构建号>/`;
- **CEUS 运行号**:一次 CEUS 定量运行,即 `runs/ceus/<CEUS运行号>/`;
- **出包号**:出一次核对包,即 `runs/review_pack/<出包号>/`,取出包那一刻的时间。
## 一、什么时候跑
医生每交一版新数据,按顺序做完:
1. CEUS 定量 `scripts/ceus_quant.py`,得到新的 `runs/ceus/<CEUS运行号>/`;
2. 把 `config/build.yaml` 的 `ceus_run` 指向这个目录,跑 `scripts/build.py` 构建两个库,得到新的构建号;
3. 再跑本入口出核对包。
核对包只读已经构建好的两个库和那次 CEUS 运行,不碰交付包,也不改两个库。所以交付数据或处理规则变了,要先重新构建,再出包。一次构建可以出多次包,每次一个新的出包号。
没装 CEUS 结果的构建(`build.yaml` 的 `ceus_run` 为 null)也能出包。包里没有关键帧、TIC 曲线、TIC 结果和回放视频,病例总表的 CEUS 各列留空,`说明.pdf` 里写明这次没有 CEUS 结果。
## 二、怎么跑
### 改配置
配置是 `config/review_pack.yaml`,进 git:出包用了哪两个库、产物写到哪,看提交历史就能查到,运行可以复现;每次出包时,快照里还会存一份配置原文。字段定义和校验在 `src/cadx/review/_config.py` 的 `ReviewPackConfig`:每个字段必填,没有默认值;多写字段、拼错字段名、同一层重复的键都直接报错;相对路径**相对仓库根目录**解析,不能写空串。当前取值看配置文件本身,这里不抄。
| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `library_dir` | 路径 | 某次构建的资料库目录(`library/<构建号>/`) |
| `answers_dir` | 路径 | 同一次构建的答案库目录(`answers/<构建号>/`)。两个目录名必须相同,不同就报错 |
| `output_root` | 路径 | 核对包运行目录的上级目录,每次出包在它下面新建 `<出包号>/` |
### 用哪次 CEUS 运行
用哪次 CEUS 运行不在这里配,从这次构建自己的记录里取:
- **CEUS 运行号**:取资料库 `manifest.json` 的 `ceus_source`。
- **运行目录的路径**:取答案库快照里的 `build.yaml`(`answers/<构建号>/snapshot/build.yaml`,构建时那份配置的原文副本)的 `ceus_run`。路径不从 `manifest.json` 拿,因为它只记了运行号、没记路径;任务书 03 又要求不在 `review_pack.yaml` 里另配。
- 两者必须一致:`ceus_run` 的目录名要等于 `ceus_source`,不一致就报错。这样包里的 TIC 曲线和回放视频,一定来自资料库实际装入的那次运行。
- `ceus_source` 为 null 时(构建时 `ceus_run` 为 null),程序不读快照里的 `build.yaml`,直接出不含 CEUS 结果的包。
### 跑
```bash
uv run python scripts/review_pack.py --config config/review_pack.yaml
```
脚本只接收 `--config` 一个参数(`../explanation/结构.md` 第五部分「配置」)。它调 `pandoc` 和 `soffice`(LibreOffice 的命令行程序),把说明和每例的两份报告转成 PDF。两个工具必须已装在 PATH 上(本机已装;中文字体用已装的 Noto CJK)。40 例的包要拷贝每例的回放视频、转几十份 PDF,耗时以实际运行为准;超过约一分钟时按 `AGENTS.md`「本机环境的坑」放进 tmux 跑。
终端只打印计数,不打印病例号,可以贴进汇报:
- 运行目录、核对包目录;
- 病例数(进库、跳过各几例);
- 问题清单行数(按「环节」分);
- PDF 份数;
- 拷贝的视频总大小(没装 CEUS 结果时显示 `0.0 MiB`);
- 耗时。
## 三、产物在哪
```
runs/review_pack/<出包号>/ (output_root 下)
snapshot/ 快照:不给医生
医生核对包_<构建号>/ 交给医生的就是这个目录
```
- `snapshot/`:记「这个包是怎么出来的」,给我们自己复现用。
- `snapshot.json`:git 状态、Python 版本、`uv.lock` 哈希、开始时间、配置哈希;
- `review_pack.yaml`:配置原文副本;
- `review.json`:构建号、CEUS 运行号、pandoc 与 soffice 的版本行;
- `finished.json`:结束时间、病例数、问题清单行数。
- `医生核对包_<构建号>/`:目录布局、每张 CSV 的列和每个文件的来源,见任务书 03「核对包的内容」。也可以直接打开包里的 `说明.pdf`:它是写给医生的完整说明,包括目录结构、每一列的意思、处理流程、关键帧和 TIC 曲线怎么看、编码含义。这里不重抄,免得两处说法不一致。`说明.pdf` 的源文件是 `src/cadx/review/readme_template.md`。
运行目录先写成 `<出包号>.partial/`,全部写完再改名成 `<出包号>/`。看到 `.partial`,说明那次出包中途出错。它不会被自动删除,留着查原因,查完由人决定删不删。已有的运行目录不删、不覆盖(`AGENTS.md`「两档标准」)。
## 四、打包交给医生
1. 只打包 `医生核对包_<构建号>/` 这一个目录,**不带 `snapshot/`**:快照里是仓库的 git 状态和配置,对医生没用。例如:
```bash
cd runs/review_pack/<出包号>
zip -r 医生核对包_<构建号>.zip 医生核对包_<构建号>
```
打包后最好在另一台电脑上解开看一眼,中文文件名是否正常显示(医生多半用 Windows)。
2. 核对包里有患者资料:病例号、报告原文、诊断、超声画面和视频(`AGENTS.md` 硬性约束 3)。只在临床方和我们之间传递,由负责人用认可的渠道(如 U 盘)转交。不贴进文档、提交说明、Linear,不发给外部模型服务。
3. 交出去的包以后不改。医生改好交付资料后,按第一节重新跑一遍,得到新的构建号和新的核对包。
## 五、常见报错
| 报错 | 原因 | 怎么办 |
| --- | --- | --- |
| `资料库与答案库不是同一次构建:A ≠ B` | `review_pack.yaml` 里两个目录名(构建号)不同 | 把两项改成同一个构建号 |
| `资料库清单里的构建号 … 与目录名 … 不同` | 资料库目录被改过名 | 用原目录名;构建目录不要改名 |
| `… 是没写完的构建(.partial),不能读` | 配置指向了没跑完的构建(名字以 `.partial` 结尾) | 改指向一次跑完的构建 |
| `… 的 SHA-256 与清单不一致` | 资料库或答案库里的文件在构建之后被改过 | 不要手改构建目录;重新构建一次 |
| `运行目录已存在,不覆盖:…` | `output_root` 下已有同名的 `<出包号>/` 或 `<出包号>.partial/`(出包号带到秒,通常是同一秒内跑了两次) | 隔一秒重跑;不要删已有的运行目录来腾位置 |
| `找不到 pandoc` / `找不到 soffice` | 转 PDF 的工具没装或不在 PATH 上 | 装上 pandoc 和 LibreOffice,再跑 |
| `pandoc 的参考样式里没有 …` | 中文 PDF 里有的字会显示成日文写法,程序为此改了 pandoc 自带的样式文件,把中文字体设成 Noto CJK 简体。这条报错说明 pandoc 版本变了,样式文件里要改的那段已经不在,程序设不了中文字体 | 先查 `pandoc --version` 是否被升级过。能退回原版本就退回;不能就打开 `src/cadx/review/_pdf.py` 顶部的注释和 `_STYLE_FONTS`,按新版样式文件改写要替换的内容 |
| `soffice 没有转出 …` | LibreOffice 转 PDF 失败 | 先单独用 `soffice --headless --convert-to pdf` 转一个 docx 试试,看 LibreOffice 本身是否正常 |
| `… 的 ceus_run 是 …,与资料库记的 CEUS 运行号 … 不符` | 答案库快照里 `build.yaml` 的 `ceus_run`(路径)与资料库 `manifest.json` 的 `ceus_source`(CEUS 运行号)对不上 | 两个库不是同一次构建写出的,检查配置是否指错 |
| `CEUS 运行目录不存在` / `是没跑完的 .partial` / `缺快照` | 构建所用的 CEUS 运行目录被移走或删了 | CEUS 运行目录只追加不删;找回原目录 |
| `CEUS 运行 failed.csv 里的视频号 … 对不上这次构建的任何病例` | 那次 CEUS 运行里失败的病例不在这次构建里,既没进库,也不在跳过清单上 | 检查 `build.yaml` 的 `ceus_run` 与 `delivery_dir`(交付包目录)是否配套,即那次 CEUS 运行是否在这份交付包上跑的 |
| `tic.json 里有 TIC结果.csv 没有中文名的字段` | build 往 `tic.json`(资料库里每例的 CEUS 结果摘要)里加了新字段,核对包还没给它起中文名 | 在 `src/cadx/review/_pack.py` 的 `TIC_ITEMS` 里补上中文名,同时补说明模板 |