Files
CADx-4/research-wiki/guides/医生核对包.md
T
iomgaa 9d82210f31 feat(review): 跳过病例表只进快照;规模表 review 行注明已接受
负责人 2026-10-17:跳过病例.csv 不放进医生目录,只留在 snapshot/,病例总表和问题清单已用大白话说了同样的内容;review 行数超预期一倍,原因对照表本来就该这么长,接受。

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

150 lines
16 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` 和改成医生读得懂的 `../任务书/04-核对包改成医生读得懂.md`(两者不一致处以 04 为准);它在代码里的位置见 `../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。拟合要过一道质量门才算成功;病灶曲线没成功时关键帧 B0、D1–D4 取不到,某条曲线没成功时它那一组灌注指标为空。
- **TIC 参数**:从过了质量门的拟合曲线算出的 11 个灌注指标(PE、TTP 等),紫圈(胰腺实质)和病灶去暗区各一组,就是资料库 `tic.json` 里给 AI 专家的那份。
- **紫圈**:临床方在视频上画的紫色圈,圈一块正常胰腺作对照,程序也为它画一条 TIC。
- **关键帧、槽位**:从视频里挑出的 7 个时刻的画面,每个时刻叫一个槽位。B0 是刚开始变亮之前,D1–D4 是从拟合曲线上取的开始变亮、变亮最快、最亮、变暗最快,F1、F2 是推注造影剂后第 20、60 秒。细节见包里的 `说明.pdf`。
- **回放视频**:原视频逐帧叠加程序画的病灶轮廓等线条,医生用它看程序圈得对不对。
- **问题清单**:包里的 `问题清单.csv`,列出程序在每个病例上观察到的现象:某例没进资料库、造影视频处理失败、曲线没能拟合、关键帧没取到、紫圈不可用。每条都抄自某个已有文件里的值,只写现象,不写原因推测和建议(负责人 2026-10-17,任务书 03「不做什么」):包只请医生核对事实,推测会把医生的注意力引向我们的猜测。
- **写成医生能懂的话**:程序记录的原因(检查编号、原因码、英文字段名)经 `src/cadx/review/_problems.py` 里的对照表换成一句大白话,同一个原因引起的几条合成一行(任务书 04)。对照表里没有的原因直接报错停下,不把程序原文给医生;程序原文另存在快照的 `问题清单_原文.csv` 里。
- **三种编号**:格式都是 `YYYYMMDD-HHMMSS-<git 短哈希>`,工作区有未提交改动时末尾加 `-dirty`(`装配两个库.md` 第三节「构建号」)。三者各指一样东西,本文用不同的名字区分:
- **构建号**:一次构建,即 `library/<构建号>/`、`answers/<构建号>/`;
- **CEUS 运行号**:一次 CEUS 定量运行,即 `runs/ceus/<CEUS运行号>/`;
- **出包号**:出一次核对包,即 `runs/review_pack/<出包号>/`,取出包那一刻的时间。
## 一、什么时候跑
医生每交一版新数据,或者处理规则改了,按顺序做完:
1. **干净工作区**:先确认 `git status` 没有任何改动(包括未跟踪文件),有就先提交或请改动的人处理。工作区不干净时三个运行号末尾都会带 `-dirty`,记下的提交号复现不出这次结果;
2. **ceus_quant**:CEUS 定量 `scripts/ceus_quant.py`,得到新的 `runs/ceus/<CEUS运行号>/`;
3. **build**:把 `config/build.yaml` 的 `ceus_run` 指向这个目录,跑 `scripts/build.py` 构建两个库,得到新的构建号;
4. **review_pack**:再跑本入口出核对包。
核对包只读已经构建好的两个库和那次 CEUS 运行,不碰交付包,也不改两个库。所以交付数据或处理规则变了,要先重新构建,再出包。一次构建可以出多次包,每次一个新的出包号。
没装 CEUS 结果的构建(`build.yaml` 的 `ceus_run` 为 null)也能出包。病例的 `关键帧/` 里只有医生截图,没有程序取的关键帧、TIC 曲线图、TIC 参数和回放视频,病例总表的 CEUS 各列留空,`说明.pdf` 里写明这一版没有造影视频的处理结果。
## 二、怎么跑
### 改配置
配置是 `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 的版本行;
- `问题清单_原文.csv`:问题清单的程序原文(检查编号、原因码、程序记的原因),列同包里的问题清单,给我们自己追溯;
- `跳过病例.csv`:答案库跳过清单 `skipped.csv` 的原样拷贝。它是程序原文,病例总表和问题清单已用大白话写了同样的内容,所以不进医生的目录(负责人 2026-10-17);
- `finished.json`:结束时间、病例数、问题清单行数。
- `医生核对包_<构建号>/`:
```
说明.pdf 给医生的说明,源文件是 src/cadx/review/readme_template.md
问题清单.csv 病例号、视频号、环节、问题(一句话)
病例总表.csv 全部病例一例一行
标准答案/ 化名对照.csv、诊断.csv、报告复核句.csv(答案库原样拷贝)
病例/<病例号>/ 只有进了资料库的病例才有
临床信息.csv 临床一路的字段
超声报告.pdf、MR报告.pdf 两路报告的描述段原文
关键帧/
医生截图.png 资料库 ultrasound/keyframes/key.png
<槽位>_第<秒>秒.png 程序定位到的关键帧,秒数取 tic.json 的 keyframe_times_s
TIC曲线.png CEUS 运行的 tic.png
TIC参数.csv 11 个灌注指标 × 紫圈、病灶去暗区两组,数值取自资料库 tic.json
回放视频.mp4 CEUS 运行的 overlay.mp4
```
CSV 都是带 BOM 的 UTF-8、中文列名、缺失为空。`TIC参数.csv` 的时间类指标保留 1 位小数、其余 2 位;某一组整组为空时最后加一行「没算出的原因」,原因从 CEUS 运行的 `summary.json` 读,经对照表写成医生的话(资料库里已经没有原因,AI 专家拿到的只是 null)。图片和视频逐字节拷贝。每列的意思和怎么看写在 `说明.pdf` 里,这里不重抄,免得两处说法不一致。
运行目录先写成 `<出包号>.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 的字段是 …,核对包只认得 …` / `灌注指标分组是 …` / `灌注指标是 …` | build 改了 `tic.json`(资料库里每例给 AI 专家的 CEUS 参数)的字段,核对包还没跟上 | 改 `src/cadx/review/_pack.py` 的 `TIC_GROUPS`、`TIC_METRICS`,同时补说明模板 |
| `… 不在对照表里:…` | 程序记录了一种新的原因(新的检查编号、新的失败原因、新的原因码、新的列),对照表里还没有给医生的写法 | 在 `src/cadx/review/_problems.py` 的对照表里补一句医生能懂的话,并在 `tests/test_review_reasons.py` 补一条手写预期 |
| `给医生的话里出现了程序用语「…」` | 某句给医生的话里带出了检查编号、字段名、下划线、括号或运行号(多半是交付资料里的取值本身带下划线等字符) | 看是哪一句:对照表写法的问题就改写法;是交付取值带出来的,问负责人怎么写 |
| `病灶曲线拟合失败 … 与因此未取到的关键帧 … 对不上` | CEUS 运行的 `summary.json` 里拟合结果和关键帧状态互相矛盾 | CEUS 运行本身有问题,查那次运行,不要改核对包绕过 |