docs: 任务书 01 装配两个库;待定问题 06 超声描述中段的造影提示
- 任务书 01:配置、delivery 的 T1–T5 与 C01–C13 检查、library/answers 布局、 快照、build、测试、规模与审阅;已过一轮「硕士生阅读」并按意见修订 - C11 只认段首的结论标题;中段「造影提示:」按负责人 2026-10-16「先不进行任何处理」 原样保留,记入待定问题 06 待问医生 - 结构.md 第五部分补「只有 library 引用 PIL」;WORK_LOG 更新下一步 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -263,7 +263,7 @@ X 是超声报告工作簿的「推注开始时间s」,含义是视频第 0
|
||||
|
||||
- `cadx` 不引用 `torch`、`sam2`。
|
||||
|
||||
第三部分的依赖表和下面几条外部库独占,随模块落地逐条写成契约(import-linter 只能检查已经存在的模块):只有 delivery 引用 `openpyxl`;只有 segment 引用 `torch`、`sam2`;只有 llm 引用 `polygateway`;只有 ledger 引用 `sqlite3`。
|
||||
第三部分的依赖表和下面几条外部库独占,随模块落地逐条写成契约(import-linter 只能检查已经存在的模块):只有 delivery 引用 `openpyxl`;只有 library 引用 `PIL`(写资料库的图片时去掉元数据);只有 segment 引用 `torch`、`sam2`;只有 llm 引用 `polygateway`;只有 ledger 引用 `sqlite3`。
|
||||
|
||||
契约管不到的,不要当成机器守住了:
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
# 任务书 01:装配两个库(不含 CEUS 结果)
|
||||
|
||||
读入 40 例交付包,逐例检查,把检查全部通过的病例装配成两个库:给 AI 专家看的资料库(`library/`,化名、三路分开)和只给评价用的标准答案库(`answers/`,金标准、化名对照、跳过清单)。同时落地参数系统(`config/build.yaml`、`config/code_maps.yaml`)。这一步不处理 CEUS 视频:超声一路只放报告「所见」和医生截的关键图;程序从视频里算出的关键帧和灌注指标(「CEUS 结果」),等「CEUS 定量」那一步(`结构.md` 第七部分第 3 步)完成后,由 build 重新构建一次再加进去。
|
||||
|
||||
对应 `research-wiki/explanation/结构.md` 第七部分第 2 步。要写的 delivery、library、answers、runs、build 都是 `src/cadx/<模块名>/` 下的子包,各自职责见 `结构.md` 第三部分。这是本仓库第一份任务书,前提是骨架已完成(提交 5eb38df、1f7a6a3)。
|
||||
|
||||
按 `AGENTS.md`「两档标准」,这一步决定专家能看见什么、金标准怎么读,是**账本级**代码:写完要派两个互相独立的全新上下文子 Agent 审阅(见「审阅」)。
|
||||
|
||||
来源:
|
||||
|
||||
- 负责人 2026-10-16 定:先做「装配两个库」;数据格式和口径以 40 例交付包为准,原话「因为数据改过很多版,所以你们需要对齐“40例交付包”那个」(design 0002 决定 5)。
|
||||
- 负责人 2026-10-16 定:配置「每段一份」;交付包里的数字编码列「晚点我会找他们要映射表」,解码做成一个可选的超参数,映射表是一份独立的配置文件(`结构.md` 第五部分「配置」)。
|
||||
- 负责人 2026-10-16 定:同一个人在不同表里本应一样的值(金标准、年龄等)要逐例比对;检查没过的病例「都是遇到整个病例跳过,让医生核对去吧(可以记录是哪一个检查没过……因为遇到第一个就整个病例跳过了)」(`AGENTS.md`「错误怎么处理」、`结构.md` 第三部分)。
|
||||
- 负责人 2026-10-16 定:用化名时,「保存金标准那个库中需要保存化名与真名的映射」。
|
||||
- 其余做法(检查项的具体内容和顺序、文件格式、构建号、半成品目录、化名形式)是监督者 2026-10-16 提出的,负责人确认任务书时一并认可。
|
||||
|
||||
## 先认识几个词
|
||||
|
||||
- **四类胰腺囊性肿瘤**:IPMN(导管内乳头状黏液性肿瘤)、SPN(实性假乳头状肿瘤)、MCN(黏液性囊性肿瘤)、SCN(浆液性囊性肿瘤)。项目要 AI 专家判断病灶属于哪一类,所以资料里出现这些名字就等于泄露答案。**金标准**是术后病理给出的类别,交付表里叫「病理类型」或 `Diagnosis`。
|
||||
- **CEUS、MR**:超声造影视频(往静脉打造影剂后录的超声);磁共振检查,本项目只有它的文字报告。
|
||||
- **临床表的化验**:`CA199`(CA19-9)和 `CEA` 是肿瘤标志物,`Amylase` 淀粉酶、`Lipase` 脂肪酶反映胰腺炎症,`TBil` 总胆红素反映胆道是否梗阻。每项配一个 `*_Level` 档位列(0/1,看起来是正常/异常,未经临床方确认)。`Symptom_*` 是症状,`Hx_*` 是既往史(`Hx_Family_PC` 是胰腺癌家族史)。
|
||||
- **推注开始时间s**:超声表里的一列,视频第 0 秒时造影剂已经推注了多少秒,「CEUS 定量」那一步挑关键帧要用。这一步只检查它是非负整数,不放进资料库。
|
||||
- **监督者**:和负责人讨论方向、写任务书并验收执行结果的会话。执行者是拿到这份任务书写代码的你。
|
||||
- **交付包**:临床方交来的一批原始资料,目前是 `data/交付包_20260908_40例/`,含三张汇总表(临床工作簿、超声报告文本、MR 报告文本)和 40 个病例目录(每个目录有一张医生截的超声关键图、一段 CEUS 视频、一份单例报告表)。只读,不进 git。
|
||||
- **病例号**:临床表里是 `P001` 这种写法,两张报告表的「患者ID」是不补零的 `1`、`12`,病例目录名是 `001`,视频文件名用 `U001`。它们指同一个病人,规范化后统一成三位补零的 `001`。
|
||||
- **编码列**:交付包里用数字表示类别的列,例如临床表的 `Sex` 是 1/2、症状列是 0/1。交付包没写这些数字代表什么,映射表要向临床方要。
|
||||
- **构建**:运行一次 `scripts/build.py`,产出一对同号的 `library/<构建号>/` 和 `answers/<构建号>/`。构建完不再修改,要改就重新构建。
|
||||
- **化名**:给专家的资料里用来代替病例号的随机名字。真实病例号会暴露交付顺序,而顺序可能和诊断有关(`AGENTS.md` 硬性约束 1)。
|
||||
- **结论段**:报告里医生写的诊断倾向(「印象」「提示」之类)。它接近答案,不能给专家。
|
||||
- **跳过清单**:检查没过、没进资料库的病例,以及各自没过的是哪一项。交负责人转给医生核对。
|
||||
- **账本级**:`AGENTS.md`「两档标准」里出错会悄悄污染结论的那一档:每条规则要有独立预期值的测试,运行产物只追加。
|
||||
|
||||
## 开工前先读
|
||||
|
||||
- `AGENTS.md`:全部,尤其是「硬性约束」1–3、「不设默认值」「罕见情况怎么办」「错误怎么处理」「测试」。
|
||||
- `research-wiki/explanation/结构.md`:第二部分(整体框架图)、第三部分(delivery、library、answers、runs、build 的职责,依赖表,「几条边界」)、第四部分(资料库布局与可见性表、答案库内容)、第五部分(目录、「配置」、文件格式、导入契约)、第六部分(规模)。
|
||||
- `research-wiki/design/0002-四段流水线与两个库.md`:决定 5、7。
|
||||
- `research-wiki/待定问题/03`、`04`。
|
||||
- `data/交付包_20260908_40例/交付说明_40例_20260908.md`。
|
||||
- 旧代码(只读,只借规则,按普通函数重写;行号以 `reference/CADx-4-CAD/` 为准):
|
||||
- `app/providers/workbook.py:48-99`:按列白名单读表,先扫表头记位置,每行只取白名单列,报错时不回显原始行。照这个思路读。
|
||||
- `app/providers/delivery.py:79-120`:病例号规范化。它不认不补零的写法,这里要认。
|
||||
- `app/providers/modalities.py:87-91,135-141,216-274`:结论段和亚型名的识别。旧规则只认段首的标题;这里沿用,见下文 C11。
|
||||
- `app/providers/clinical.py:118-130`:化验原值与档位成对检查。
|
||||
- 旧代码在 40 例上读不进去的原因(工作表名写死 `Sheet1`、编号列写死「研究编号」、病例目录里的 xlsx 报错)都是因为它按旧样本 `1.2-TEST` 写死;这里按 40 例交付包写,不做兼容。
|
||||
|
||||
**病人资料不进任何文字**:报告、提交说明、测试、日志里只写结构和计数,不写报告原文、病例号、诊断值。终端输出同样只给计数。
|
||||
|
||||
## 一、配置
|
||||
|
||||
### 依赖
|
||||
|
||||
在 `pyproject.toml` 加:`pydantic`、`pyyaml`、`openpyxl`、`pillow`。用 `uv add` 加,版本下限取当前版本。
|
||||
|
||||
### `config/build.yaml`
|
||||
|
||||
字段定义写在 build 里(pydantic,`strict=True`、`extra="forbid"`、`frozen=True`,每个字段必填、无默认值;`结构.md` 第五部分「配置」):
|
||||
|
||||
YAML 里的路径是字符串,路径字段是唯一允许从字符串转成 `Path` 的地方(字段级关掉 strict),其余字段一律不转换类型。
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `delivery_dir` | 路径 | 交付包目录 |
|
||||
| `library_root` | 路径 | 资料库的上级目录,构建产物放在它下面的 `<构建号>/` |
|
||||
| `answers_root` | 路径 | 答案库的上级目录 |
|
||||
| `alias_seed` | 整数 | 生成化名的随机种子 |
|
||||
| `decode_codes` | 布尔 | 编码列是否按映射表解码 |
|
||||
| `code_maps` | 路径或 null | 映射表位置。`decode_codes` 为 true 时必须是路径,为 false 时必须是 null,不符就报错 |
|
||||
|
||||
配置里的相对路径相对仓库根目录解析。提交一份真实可用的 `config/build.yaml`,`decode_codes: false`、`code_maps: null`。
|
||||
|
||||
### `config/code_maps.yaml`
|
||||
|
||||
格式:每个编码列一项,列名映射到「数字 → 含义」:
|
||||
|
||||
```yaml
|
||||
Sex: {1: ..., 2: ...}
|
||||
Symptom_Pain: {0: ..., 1: ...}
|
||||
```
|
||||
|
||||
需要映射的编码列共 16 列,名单是 delivery 里的常量:`Sex`;`Symptom_Pain`、`Symptom_Jaundice`、`Symptom_WeightLoss`、`Symptom_Fever`、`Symptom_Asymptomatic`;`Hx_Pancreatitis`、`Hx_Alcohol`、`Hx_Gallstones`、`Hx_Malignancy`、`Hx_Family_PC`;`CA199_Level`、`CEA_Level`、`Amylase_Level`、`Lipase_Level`、`TBil_Level`。
|
||||
|
||||
- 映射表必须恰好覆盖这 16 列,多一列或少一列都整次构建停下(T5)。`Sex` 的含义必须是「男」「女」两个汉字,才能和报告表的性别比对(C10)。
|
||||
- 这 16 列的名单定义在 delivery(它是交付包形状的一部分);T5 由 build 读配置时检查,因为映射表是配置。
|
||||
- 映射表还没拿到,**这一步不提交 `config/code_maps.yaml`**,不编造含义。解码路径只用测试里的样例映射表验证。
|
||||
- 诊断编码不在映射表里:两张报告表的列名「病理类型(1 IPMN,2 SPN,3 MCN,4 SCN)」已经写明了含义,delivery 把这个对应关系写成常量,同时核对列名原文和常量一致(T3),所以不受 `decode_codes` 控制。临床表的 `Diagnosis` 列没有说明,它和报告表逐例一致(C10)才进库,所以同样适用这组含义。
|
||||
|
||||
## 二、delivery:读交付包、逐例检查
|
||||
|
||||
delivery 只读交付包,交出每例读到的内容和检查结果,不决定谁能看见什么(`结构.md` 第三部分)。只有 delivery 引用 `openpyxl`。
|
||||
|
||||
### 交付包的形状(全部写成 delivery 里的常量)
|
||||
|
||||
| 文件 | 工作表 | 表头(第 1 行,顺序和名字都要一字不差) |
|
||||
| --- | --- | --- |
|
||||
| `Clinical_agent/临床工作簿_40例.xlsx` | `Sheet` | `Case_ID`、`Sex`、`Age`、`Diagnosis`、`Symptom_Pain`、`Symptom_Jaundice`、`Symptom_WeightLoss`、`Symptom_Fever`、`Symptom_Asymptomatic`、`Hx_Pancreatitis`、`Hx_Alcohol`、`Hx_Gallstones`、`Hx_Malignancy`、`Hx_Family_PC`、`CA199`、`CA199_Level`、`CEA`、`CEA_Level`、`Amylase`、`Amylase_Level`、`Lipase`、`Lipase_Level`、`TBil`、`TBil_Level`(共 24 列,监督者 2026-10-16 用只输出表头的脚本核过) |
|
||||
| `CEUS_agent/超声报告文本_40例.xlsx` | `超声报告` | `患者ID`、`CEUS视频ID`、`性别`、`年龄`、`病理类型(1 IPMN,2 SPN,3 MCN,4 SCN)`、`推注开始时间s`、`超声描述` |
|
||||
| `MR_agent/MR报告文本_40例.xlsx` | `MR报告` | `患者ID`、`性别`、`年龄`、`病理类型(1 IPMN,2 SPN,3 MCN,4 SCN)`、`MR描述` |
|
||||
| `CEUS_agent/<三位病例号>/` | — | 病例目录,内容见 C04 |
|
||||
| `CEUS_agent/<三位病例号>/U<号>_roi2_report.xlsx` | `Sheet1` | 与超声汇总表相同的 7 列 |
|
||||
|
||||
文件名里的 `roi2` 是交付方的版本标记(视频上带医生画的病灶框),这一步不用视频,只检查它在不在。
|
||||
|
||||
### 整张表的问题:整次构建停下
|
||||
|
||||
这些问题出现时一例也读不出来,没有「跳过这一例」可言。报错写清是哪个文件、哪一项不符。
|
||||
|
||||
| 编号 | 检查 |
|
||||
| --- | --- |
|
||||
| T1 | 三张汇总表都存在 |
|
||||
| T2 | 每张汇总表恰好有上表那一个工作表,名字一致 |
|
||||
| T3 | 表头与上表一字不差(含诊断列名里写的编码含义) |
|
||||
| T4 | `Clinical_agent/`、`MR_agent/` 下各只有自己那张汇总表;`CEUS_agent/` 下只有超声汇总表和三位数字命名的病例目录。多出来的文件说明交付方式变了,不能悄悄忽略 |
|
||||
| T5 | `decode_codes` 为 true 时,映射表恰好覆盖 16 个编码列,`Sex` 映射到「男」「女」(由 build 检查,见第一部分) |
|
||||
|
||||
交付包根目录的 `交付说明_*.md` 不读。
|
||||
|
||||
### 单个病例的问题:跳过这一例,记录第一项没过的检查
|
||||
|
||||
先找出编号认不出的行(C01):这种行不知道是哪个病人,在跳过清单里单独记一条,病例号一栏写「表名第 N 行」,不计入病例数。其余行规范化后,病例号取三张表和病例目录的并集,这就是「病例总数」。同一个病人如果在某张表里编号写坏了,他在 C01 记一条,在 C03(缺在那张表)再记一条,这是预期的。
|
||||
|
||||
类型按 openpyxl 读出的 Python 类型判断,不做转换:要求整数的格读出来是 `float`(如 `45.0`)也算不过;字符串不去首尾空格。
|
||||
|
||||
每一例按下表顺序检查,**遇到第一项没过就停止检查这一例**,跳过整例,在跳过清单里记下这一项(`AGENTS.md`「错误怎么处理」)。后面的检查没做过,不代表没问题;跳过清单的说明文字里要写明这一点。
|
||||
|
||||
| 编号 | 检查 | 记录的涉及值 |
|
||||
| --- | --- | --- |
|
||||
| C01 | 编号能规范化:临床表认 `P` 加三位数字;两张报告表认不补零或补零的纯数字(字符串或整数);病例目录认三位数字。规范化后在 1–999 之间 | 认不出的原始写法和所在表、行号 |
|
||||
| C02 | 同一张表里这个编号只出现一次 | 出现的表和行号 |
|
||||
| C03 | 三张表和病例目录里都有这一例 | 缺在哪里 |
|
||||
| C04 | 病例目录恰好有三个文件:`U<号>_key.png`;一个视频,名字是 `U<号>_roi2.mp4` 或 `U<号>.mp4`(交付说明里写明 033 例是后一种);`U<号>_roi2_report.xlsx`。这里的 `<号>` 与目录号相同 | 实际的文件名列表 |
|
||||
| C05 | 超声表的 `CEUS视频ID` 是 `U<号>`,与病例号一致 | 两者的值 |
|
||||
| C06 | 单例报告表:工作表 `Sheet1`,表头同超声汇总表,恰好 1 行数据,7 格与汇总表对应行逐格相等 | 不相等的列和两边的值 |
|
||||
| C07 | 临床表取值,见表下的分条 | 不合规的列和值 |
|
||||
| C08 | 超声表取值:`性别` 是「男」或「女」;`年龄` 形如「数字岁」;诊断是 1–4;`推注开始时间s` 是非负整数;`超声描述` 是非空字符串 | 不合规的列和值 |
|
||||
| C09 | MR 表取值:`性别`、`年龄`、诊断同 C08;`MR描述` 是非空字符串 | 不合规的列和值 |
|
||||
| C10 | 跨表一致:诊断三张表两两相等;年龄三张表相等(报告表去掉「岁」后比);性别超声表与 MR 表相等;`decode_codes` 为 true 时,临床表解码后的 `Sex` 也要与超声表相等 | 对不上的字段和各表的值 |
|
||||
| C11 | 报告文字(超声描述、MR 描述)里没有结论段标题:先做 NFKC(全角转半角)归一,再查「关键词 + 可选空白 + 冒号」,出现在全文开头或换行之后(段首)即不过。关键词:超声提示、超声诊断、影像学诊断、检查结论、诊断意见、印象、结论、诊断、意见、提示 | 哪份报告、命中的关键词、命中处所在的句子 |
|
||||
| C12 | 报告文字里没有亚型名:NFKC 归一并转大写后,不含 `IPMN`、`SPN`、`MCN`、`SCN`、导管内乳头状、实性假乳头状、黏液性囊、粘液性囊、浆液性囊 | 哪份报告、命中的词 |
|
||||
| C13 | 报告文字里没有人口学信息(影像两路不该看到性别、年龄,`结构.md` 第四部分):不含「数字岁」,不含「男」「女」两个字 | 哪份报告、命中的词 |
|
||||
|
||||
C07 的分条:
|
||||
|
||||
- `Sex` 是 1 或 2;`Age` 是正整数;`Diagnosis` 是 1–4;
|
||||
- 5 个症状、5 个既往史是 0 或 1,不能空;
|
||||
- 5 对化验(原值和档位):两格都空表示没测,可以;两格都有值时,原值是数字(整数或浮点),档位是 0 或 1;只有一格有值不过;
|
||||
- `decode_codes` 为 true 时,16 个编码列里非空的值都要在映射表里。
|
||||
|
||||
几点说明:
|
||||
|
||||
- 跳过清单和复核清单写在 `answers/` 里,只在本机、不进 git,可以写原值和原句,医生核对要用。报告、终端、日志、提交说明里只写计数和检查编号。
|
||||
- C13 禁单字「男」「女」:40 例实测两份报告文字都没有这两个字,描述病灶也用不到它们,出现了多半是写了患者性别。
|
||||
|
||||
- 第 C10 项是负责人点名要的:同一个人在不同表里本应一样的值对不上,大概率是录入错误,要拿去找医生改。`decode_codes` 为 false 时临床表的性别没法和报告表比,构建清单里写明「临床性别未比对」。
|
||||
- C11 沿用旧代码的规则,只认段首。40 例的报告正文都没有换行,这条在 40 例上不会触发。监督者 2026-10-16 用只输出位置的脚本核过:40 份超声描述里有 1 份在正文中段有「造影提示:」(冒号后还有 46 个字),MR 描述里没有。这句是不是医生的诊断倾向、该不该给专家,负责人要先问医生(`待定问题/06`);在那之前按负责人 2026-10-16 的决定**不做任何处理**,这一例照常进库,原文不改。它会因为含「提示」出现在复核清单里。这一条定下来之前,不能用这次构建做正式会诊。
|
||||
- C12 不查「浆液性」「黏液性」单独出现:这两个词在描述囊液性状时是正常用语。40 例实测上表的词都没出现。
|
||||
- 报告里出现「考虑、可能、倾向、提示」(不带冒号)不算检查不过,只列进复核清单(见第四部分),由负责人过目。40 例实测超声 1 份、MR 3 份。
|
||||
|
||||
delivery 对外交出:每个通过的病例的读入内容(三张表各一行的白名单列、病例目录里关键图的路径),和每个没过的病例的跳过记录(编号、检查编号、检查说明、文件、列、涉及值)。读入时只取白名单列:临床表除 `Case_ID`、`Diagnosis` 外的列进临床一路,诊断三列单独交出,供答案库用。
|
||||
|
||||
## 三、library:给专家的资料库
|
||||
|
||||
格式按 `结构.md` 第四部分。这一步的内容:
|
||||
|
||||
```
|
||||
library/<构建号>/
|
||||
manifest.json
|
||||
cases/<化名>/
|
||||
clinical/clinical.json
|
||||
ultrasound/report.txt
|
||||
ultrasound/key.png
|
||||
mr/report.txt
|
||||
```
|
||||
|
||||
- `clinical.json`:`{"codes_decoded": true 或 false, "fields": {列名: 值, ...}}`。`fields` 的键是临床表除 `Case_ID`、`Diagnosis` 以外的 22 列列名。编码列在 `codes_decoded` 为 false 时写原始数字,为 true 时写映射表里的含义;化验没测写 null。字段名给模型时要不要改写成自然语言,由会诊任务书定。
|
||||
- `report.txt`:超声描述、MR 描述原文,UTF-8,不加任何标题和编号。
|
||||
- `key.png`:解码像素后按原来的颜色模式重新编码成 PNG,不带任何文本块、EXIF 和 ICC 等附加数据。超声帧上烧录的信息栏不遮蔽(`AGENTS.md` 硬性约束 3)。
|
||||
- `manifest.json`:构建号、git 提交、配置文件的 SHA-256、`codes_decoded`、未比对项(如「临床性别未比对」)、所用 CEUS 结果的来源(这一步写 null)、病例化名列表(按化名排序)、每个文件的相对路径和 SHA-256。不写真实病例号、交付包里的文件名、诊断。
|
||||
- `manifest.json` 里「配置文件的 SHA-256」:`decode_codes` 为 true 时同时记映射表的 SHA-256。
|
||||
- **读取**:按路读出,一路一个对象(临床、超声、MR 各一个类型;超声对象里关键图给文件路径,怎么交给模型由会诊定),读时核对文件的 SHA-256 与清单一致。另提供列出一次构建的全部化名。拒绝读取名字以 `.partial` 结尾的目录。
|
||||
|
||||
只有 library 引用 `PIL`。
|
||||
|
||||
## 四、answers:标准答案库
|
||||
|
||||
```
|
||||
answers/<构建号>/
|
||||
manifest.json 构建号(与 library 同一个)、本目录其余全部文件(含 snapshot/ 下的)的 SHA-256
|
||||
snapshot/ 运行快照(第五部分)
|
||||
aliases.csv 化名、病例号(三位)、视频号(U 号,「CEUS 定量」的结果按视频号接回病例)
|
||||
diagnoses.csv 化名、病例号、临床表诊断编码、超声表诊断编码、MR 表诊断编码、诊断名(按诊断编码常量)
|
||||
skipped.csv 病例号或「表名第 N 行」、检查编号、检查说明、文件、列、涉及值(JSON 字符串)
|
||||
review.csv 化名、路(超声/MR)、命中的词、所在句子
|
||||
```
|
||||
|
||||
- `skipped.csv` 是交给医生核对的东西。文件开头不能加注释行(CSV),「每例只记第一项没过的检查,其余检查没做」这句说明写进 `manifest.json` 的 `skipped_note` 字段,构建结束时也在终端打印。
|
||||
- `review.csv` 只扫进库的病例:报告文字做 NFKC 归一后,按「。」和「;」(NFKC 会把全角分号转成半角)切成句子,含「考虑」「可能」「倾向」「提示」任一词的句子各记一行。
|
||||
- 读取:评价要用的读取函数(读化名对照、读诊断)在 answers 里提供,读时核对 SHA-256。其余读取等评价任务书再加。
|
||||
|
||||
## 五、runs:构建号与快照
|
||||
|
||||
runs 这一步只做快照部分,会诊运行目录等第 4 步再加。
|
||||
|
||||
- **构建号**:`YYYYMMDD-HHMMSS-<git 短哈希>`,工作区有未提交改动时末尾加 `-dirty`。时间用本地时间。
|
||||
- **快照**(写进 `answers/<构建号>/snapshot/`):`snapshot.json` 记构建号、完整 git 提交号、工作区是否干净、`git diff HEAD` 输出的 SHA-256、Python 版本、`uv.lock` 的 SHA-256、开始和结束时间;同目录复制 `build.yaml` 原文,`decode_codes` 为 true 时同时复制映射表原文,各记 SHA-256。
|
||||
- 工作区不干净照样允许构建,但必须如实记下(负责人 2026-10-16 认可)。
|
||||
|
||||
## 六、build 与 `scripts/build.py`
|
||||
|
||||
build 是唯一同时碰到原始资料和答案的地方,也是决定谁能看见什么的唯一地方(`结构.md` 第三部分)。
|
||||
|
||||
1. 读配置;检查 T5(编码列名单取自 delivery)。
|
||||
2. 调 delivery 读交付包,拿到通过的病例和跳过记录。
|
||||
3. 生成化名:用 `random.Random(alias_seed)` 为每个通过的病例生成化名:4 个字符,每个字符从 `a–z0–9` 里抽;和已有化名重复、或含 `ipmn`、`spn`、`mcn`、`scn` 子串的就重抽(否则会被泄露扫描误报)。先把病例号排好序再依次抽,同一个种子、同一批病例得到同样的化名。
|
||||
4. 按 `结构.md` 第四部分的可见性表写资料库:临床一路只写 `clinical.json`;超声一路只写超声描述和关键图;MR 一路只写 MR 描述。性别、年龄只在临床一路。
|
||||
5. 写答案库和快照。
|
||||
6. 两个库先写进 `<根目录>/<构建号>.partial/`,全部写完、清单算完后再改名为 `<构建号>/`:先改答案库,最后改资料库。资料库的 `<构建号>/` 出现,才算这次构建完成;会诊只读资料库,所以答案库改名成功、资料库没改成的情况,会诊看不到这次构建。目标目录已存在就报错,不覆盖。中途出错留下的 `.partial` 不删,由人看。
|
||||
7. 终端只打印:构建号、两个库的路径、病例总数、进库病例数、跳过病例数、认不出编号的行数、按检查编号分的跳过数、复核清单条数、跳过清单的说明句。不打印病例号和任何资料内容。
|
||||
|
||||
`scripts/build.py` 只接收 `--config <路径>` 一个参数(`结构.md` 第五部分「配置」)。
|
||||
|
||||
## 七、测试
|
||||
|
||||
预期值照本任务书的文字写,不从实现里推(`AGENTS.md`「两档标准」)。测试不读 `data/` 下的真实资料。
|
||||
|
||||
**测试用交付包**:写一个测试辅助函数,在 pytest 临时目录里用 openpyxl 生成一个形状与 40 例完全相同的假交付包(3–5 例,报告文字是编造的、不涉及真实病人;关键图用 Pillow 生成小图并带上一个 PNG 文本块)。各条测试在它的基础上改出要测的情况。
|
||||
|
||||
至少覆盖:
|
||||
|
||||
- **T1–T5**:各一条,整次构建停下,`library/`、`answers/` 下都没有留下非 `.partial` 的目录。
|
||||
- **C01–C13**:各一条,只让一例违反这一项,断言这一例被跳过、跳过记录的检查编号正确、其余病例正常进库。C10 分诊断、年龄、性别三种各一条;C07 包括化验只空一列;C11 包括全角冒号、换行后的段首两种,另加一条:关键词加冒号出现在正文中段时不算不过、这一例正常进库。
|
||||
- **只记第一项**:一例同时违反 C08 和 C10,跳过记录只有 C08。
|
||||
- **可见性**:资料库里性别、年龄只出现在临床一路;超声、MR 两路的文件里不出现临床表的任何列名。
|
||||
- **泄露扫描**:library 提供一个扫描函数:给定一次构建的目录、禁止出现的字符串列表、禁止的 JSON 键名列表,返回命中(文件、命中的词)。字符串按子串扫全部 `.txt`、`.json` 文件(含 `manifest.json`),不扫 PNG(PNG 的附加数据由「关键图」那条测试管);键名按整个键名、不分大小写比较。测试里用一份独立写的词表(不 import delivery 的常量)调用它,断言不出现:本次构建任一病例的病例号(`P001`、`U001` 两种写法)、交付包里的文件名、四种亚型的缩写和中文名、诊断列名、`Diagnosis`、`Case_ID`;键名不出现 `diagnosis`、`label`、`gold`、`pathology`、`name`、`row`。再写一条测试证明扫描函数本身管用:往资料库塞一个泄露,它能查出来。
|
||||
- **化名**:同一个种子构建两次,化名相同;换种子,化名不同;化名都是 4 位小写字母加数字,互不相同。
|
||||
- **解码开关**:false 时 `clinical.json` 是原始数字、`codes_decoded` 为 false;true 时按样例映射表解码;`decode_codes` 与 `code_maps` 不配套(true 配 null,false 配路径)时配置读取报错。
|
||||
- **关键图**:资料库里的 `key.png` 颜色模式和像素与原图相同,PNG 文本块没有了。
|
||||
- **清单与读取**:改坏资料库里的一个文件,library 读取时报错;`.partial` 目录读取时报错。
|
||||
- **不覆盖**:同一个构建号目录已存在时报错。
|
||||
|
||||
**真实 40 例验收**(执行者跑,不进 `make check`):
|
||||
|
||||
- 用提交的 `config/build.yaml` 跑一次 `scripts/build.py`,报告终端输出。
|
||||
- 写一个 `scripts/scan_library.py --library <资料库目录> --answers <答案库目录>`:从答案库读出这次构建的真实病例号和视频号,加上脚本里自己写的亚型名和列名词表,调 library 的扫描函数扫资料库,只输出命中的条数和类别,不输出命中的内容。这个脚本同时读两个库,只能放在 `scripts/`。
|
||||
- 报告跳过数按检查编号分的计数。监督者事先知道的情况:C10 应该有 6 例左右(`待定问题/04`),C11 应该是 0 例,复核清单里有一条来自那份含「造影提示:」的超声描述(见第二部分说明)。实际数字和这里不一样就如实报告,不改规则去凑。
|
||||
|
||||
## 八、import-linter 契约
|
||||
|
||||
在 `pyproject.toml` 加(`结构.md` 第三部分依赖表、第五部分):
|
||||
|
||||
1. delivery、library、answers、runs 不引用其他 `cadx` 模块;
|
||||
2. build 只引用 delivery、library、answers、runs(ceus 这一步还没有,不写);
|
||||
3. 在 `cadx` 里只有 delivery 引用 `openpyxl`;
|
||||
4. 在 `cadx` 里只有 library 引用 `PIL`。
|
||||
|
||||
顺手把 `pyproject.toml` 里「规划中的契约见结构.md 第四节」改成「第五部分」。
|
||||
|
||||
## 规模
|
||||
|
||||
预期(不含测试,`结构.md` 第六部分计数口径):
|
||||
|
||||
| 模块 | 预期行数 |
|
||||
| --- | --- |
|
||||
| delivery | 400–700 |
|
||||
| library | 150–300 |
|
||||
| answers | 100–200 |
|
||||
| runs(只含快照部分) | 50–150 |
|
||||
| build(含配置定义) | 300–500 |
|
||||
| `scripts/build.py` | ≤ 100 |
|
||||
| `scripts/scan_library.py` | ≤ 150 |
|
||||
|
||||
超过区间上限的两倍就停下检查:是需求本身复杂,还是专门处理罕见情况的分支在变多。
|
||||
|
||||
完成后更新:
|
||||
|
||||
- `research-wiki/explanation/结构.md`:第六部分填实际行数;第八部分补这五个模块对外的函数和类型,每项一句话;第五部分「配置」表里 `build.yaml` 一行如有出入,按实际写。
|
||||
- `research-wiki/guides/装配两个库.md`:怎么改配置、怎么跑、产物在哪、跳过清单怎么看(常青文档,过一轮「硕士生阅读」)。
|
||||
- `WORK_LOG.md`。
|
||||
|
||||
不新建其他 md 文件。
|
||||
|
||||
## 审阅
|
||||
|
||||
写完、`make check` 通过后,按顺序:
|
||||
|
||||
1. 派一个全新上下文的子 Agent 审阅。只给它:本任务书、`AGENTS.md`、`结构.md` 第三到第五部分、`git --no-pager diff` 的完整输出。不给它看你的思路。让它找:专家能看到答案、真实病例号、另一路资料的具体路径;检查没过的病例却进了库;跳过清单记错检查项;构建产物被覆盖或改动的具体场景。
|
||||
2. 账本级,再派一个全新上下文的子 Agent 独立审一轮,材料相同,不给它看第一轮的意见。
|
||||
3. Codex 可用时,再用 `/codex:rescue --fresh --wait` 做一轮对抗审查。
|
||||
|
||||
每条意见先问「日常使用中会不会真撞上」「不改会怎样」:会悄悄让答案漏进资料库或让病例进错库的,修,修了附测试;只会报错停下或一眼能看出来的,不加代码,在报告里列出来。子 Agent 不写 md 文件,结论直接回复;它们的 prompt 里写明本机 `grep`、`find` 用 `/usr/bin/` 下的版本。
|
||||
|
||||
## 报告
|
||||
|
||||
做完后本地提交(不推送),在回复里交一份报告,监督者会对着实际文件逐项核对。已验证的事实、推断、未验证的部分分开写。
|
||||
|
||||
1. 提交号;新增和修改的文件列表,每个模块的行数。
|
||||
2. `make check` 的完整输出。
|
||||
3. 真实 40 例构建:终端输出原文、构建号、两个库的路径、按检查编号分的跳过计数、复核清单条数、`scripts/scan_library.py` 的输出。不写病例号和资料内容。
|
||||
4. 两轮审阅(和 Codex,如有)的意见,以及每条怎么处理:修了的附测试名,没修的写「不改会怎样」。
|
||||
5. 你和本任务书、`结构.md`、`AGENTS.md` 不一致的地方,以及为什么。
|
||||
6. 规则不清楚的地方:哪里让你犹豫、哪里两份文件说法不一致、哪里是靠猜的。没有就写「没有」。
|
||||
@@ -0,0 +1,27 @@
|
||||
# 06 超声描述正文中段的「造影提示:」
|
||||
|
||||
提出:2026-10-16 监督者写任务书 01 时发现,负责人决定先问医生。
|
||||
|
||||
## 问题
|
||||
|
||||
40 份超声描述里有 1 份,在正文中段(全文 312 个字,第 263 个字起)写着「造影提示:」,冒号后还有 46 个字。监督者只用输出位置和关键词的脚本核过,没看原文。它可能是医生对这次造影检查的诊断倾向(相当于报告的结论段),也可能只是对造影表现的描述。
|
||||
|
||||
其余 39 份超声描述和 40 份 MR 描述里,任何位置都没有「结论、印象、提示、诊断、意见」之类的词加冒号。
|
||||
|
||||
## 为什么要紧
|
||||
|
||||
如果它是诊断倾向,超声那一路的 AI 专家就看到了医生的结论,超声一路和会诊的准确率都会被抬高,`AGENTS.md` 硬性约束 1 不成立。在这个问题定下来之前,资料库里这一例的超声描述保持原文(负责人 2026-10-16:「出现“造影提示:”先不进行任何处理……现在按默认不处理走」),所以**这期间的构建不能用来做正式会诊**。
|
||||
|
||||
## 选项
|
||||
|
||||
- 是诊断倾向:从「造影提示:」切到文末,只保留前面的描述;或者把这一例整例跳过。
|
||||
- 不是诊断倾向:保持原文。
|
||||
- 不管哪种结论,都要再定 C11 的规则:要不要把「关键词加冒号出现在正文中段」也算作结论段(`任务书/01` C11 目前只认段首)。
|
||||
|
||||
## 倾向
|
||||
|
||||
暂无,等医生说明这句话的性质。
|
||||
|
||||
## 等谁定
|
||||
|
||||
临床方回答,负责人转达。证据:`answers/<构建号>/review.csv` 里有这一句(构建后可查),任务书 01 第二部分 C11 的说明。
|
||||
Reference in New Issue
Block a user