负责人 2026-10-16「诊断编码归开关管,实现统一」:映射表加 Diagnosis 一项(17 项), 管三张表的诊断列;不再从报告表列名取编码含义;decode_codes 为 false 时答案库诊断名留空。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
30 KiB
任务书 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
格式:每个编码列一项,列名映射到「数字 → 含义」:
Sex: {1: ..., 2: ...}
Symptom_Pain: {0: ..., 1: ...}
需要映射的编码共 17 项,名单是 delivery 里的常量:Sex;Diagnosis(同时管临床表的 Diagnosis 列和两张报告表的「病理类型」列,三列编码相同);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。
- 映射表必须恰好覆盖这 17 项,多一项或少一项都整次构建停下(T5)。
Sex的含义必须是「男」「女」两个汉字,才能和报告表的性别比对(C10);Diagnosis的含义必须恰好是IPMN、SPN、MCN、SCN四个,评价拿它和专家的结论比。 - 这 17 项的名单定义在 delivery(它是交付包形状的一部分);T5 由 build 读配置时检查,因为映射表是配置。
- 映射表还没拿到,这一步不提交
config/code_maps.yaml,不编造含义。解码路径只用测试里的样例映射表验证。 - 诊断编码和其他编码列一样归
decode_codes管(负责人 2026-10-16:「诊断编码归开关管,实现统一」)。报告表列名里写着「1 IPMN,2 SPN,3 MCN,4 SCN」,T3 只把它当表头原文核对,程序不从列名里取含义。诊断不进资料库,所以这个开关对诊断只影响答案库。
二、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 时,映射表恰好覆盖 17 项编码,Sex 映射到「男」「女」,Diagnosis 映射到四个亚型缩写(由 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(decode_codes 为 true 时还要在映射表的 Diagnosis 里);推注开始时间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 时,临床表各编码列里非空的值都要在映射表里。
几点说明:
-
跳过清单和复核清单写在
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 表诊断编码、诊断名(`decode_codes` 为 true 时按映射表解码,为 false 时留空)
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 第三部分)。
- 读配置;检查 T5(编码列名单取自 delivery)。
- 调 delivery 读交付包,拿到通过的病例和跳过记录。
- 生成化名:用
random.Random(alias_seed)为每个通过的病例生成化名:4 个字符,每个字符从a–z0–9里抽;和已有化名重复、或含ipmn、spn、mcn、scn子串的就重抽(否则会被泄露扫描误报)。先把病例号排好序再依次抽,同一个种子、同一批病例得到同样的化名。 - 按
结构.md第四部分的可见性表写资料库:临床一路只写clinical.json;超声一路只写超声描述和关键图;MR 一路只写 MR 描述。性别、年龄只在临床一路。 - 写答案库和快照。
- 两个库先写进
<根目录>/<构建号>.partial/,全部写完、清单算完后再改名为<构建号>/:先改答案库,最后改资料库。资料库的<构建号>/出现,才算这次构建完成;会诊只读资料库,所以答案库改名成功、资料库没改成的情况,会诊看不到这次构建。目标目录已存在就报错,不覆盖。中途出错留下的.partial不删,由人看。 - 终端只打印:构建号、两个库的路径、病例总数、进库病例数、跳过病例数、认不出编号的行数、按检查编号分的跳过数、复核清单条数、跳过清单的说明句。不打印病例号和任何资料内容。
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,diagnoses.csv的诊断名留空;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 第三部分依赖表、第五部分):
- delivery、library、answers、runs 不引用其他
cadx模块; - build 只引用 delivery、library、answers、runs(ceus 这一步还没有,不写);
- 在
cadx里只有 delivery 引用openpyxl; - 在
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 通过后,按顺序:
- 派一个全新上下文的子 Agent 审阅。只给它:本任务书、
AGENTS.md、结构.md第三到第五部分、git --no-pager diff的完整输出。不给它看你的思路。让它找:专家能看到答案、真实病例号、另一路资料的具体路径;检查没过的病例却进了库;跳过清单记错检查项;构建产物被覆盖或改动的具体场景。 - 账本级,再派一个全新上下文的子 Agent 独立审一轮,材料相同,不给它看第一轮的意见。
- Codex 可用时,再用
/codex:rescue --fresh --wait做一轮对抗审查。
每条意见先问「日常使用中会不会真撞上」「不改会怎样」:会悄悄让答案漏进资料库或让病例进错库的,修,修了附测试;只会报错停下或一眼能看出来的,不加代码,在报告里列出来。子 Agent 不写 md 文件,结论直接回复;它们的 prompt 里写明本机 grep、find 用 /usr/bin/ 下的版本。
报告
做完后本地提交(不推送),在回复里交一份报告,监督者会对着实际文件逐项核对。已验证的事实、推断、未验证的部分分开写。
- 提交号;新增和修改的文件列表,每个模块的行数。
make check的完整输出。- 真实 40 例构建:终端输出原文、构建号、两个库的路径、按检查编号分的跳过计数、复核清单条数、
scripts/scan_library.py的输出。不写病例号和资料内容。 - 两轮审阅(和 Codex,如有)的意见,以及每条怎么处理:修了的附测试名,没修的写「不改会怎样」。
- 你和本任务书、
结构.md、AGENTS.md不一致的地方,以及为什么。 - 规则不清楚的地方:哪里让你犹豫、哪里两份文件说法不一致、哪里是靠猜的。没有就写「没有」。