Files
CADx-4/research-wiki/explanation/结构.md
T
iomgaa fa962a2672 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>
2026-10-16 01:14:04 -04:00

315 lines
27 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.
# 代码结构
这份文件说明数据从医院原始资料到评价结果经过哪几段、代码分成哪些模块、每个模块藏住什么决定、模块之间怎么依赖、各模块预期多大,以及按什么顺序把它们写出来。各模块对外的函数和类型,写到哪个模块时再补进第八部分。
来源:
- 负责人 2026-10-15 定:旧项目不再做成工程产品,在本仓库按研究原型重写。原话:「我们后面发现其其实不需要(至少目前不需要)是一个工程产品」(design 0001)。
- 负责人 2026-10-15 定:协作环境和文档体系参考 ClinDB-ReviewBench 与 dissect3;检查强度取 ClinDB 那一档;一个 uv 环境,MedSAM2 与 torch 装在里面,靠导入边界隔离(design 0001 第 4、7 条)。
- 负责人 2026-10-16 定:数据流是「医院原始资料->中间文件(可以有可以没有)->「给专家的资料库」(给agent的东西都在里面)&「标准答案库」(我们自己用来评估的金标准的标准答案等)」;两个库和中间文件都用文件夹,原话:「两个库和缓存都用文件夹」;调模型用实验室的 PolyGateway;CEUS 代码「直接一个个迁移」;数据格式和口径以 40 例交付包为准(design 0002)。
- 负责人 2026-10-16 定:配置「每段一份」;编码列的解码是一个可选的开关,映射表是独立的配置文件;交付资料的检查,一个病例只要有一项没过就整例跳过并记录,原话:「都是遇到整个病例跳过,让医生核对去吧」。
- 其余内容(模块名单、边界、依赖、规模、推进顺序)是 Agent 2026-10-16 的提议;规模在对应模块动手前由负责人逐个确认。
---
## 一、切分原则
不按流水线的步骤切,也不按技术层(接口、数据库、服务)切,按「以后可能会改的设计决定」切,一个模块藏住一个决定。换掉某个决定时,只改这一个模块。例如「用哪个分割模型」是一个可能会改的决定:把它藏进 segment,CEUS 的其余算法只接收「每帧一张病灶掩码」,不知道背后是 MedSAM2 还是别的模型;以后换模型只改 segment。这个原则来自 Parnas 1972 年的信息隐藏论文,ClinDB-ReviewBench 用的也是这个分法。
旧项目按技术层切(`app/api`、`app/persistence`、`app/workers`、`app/providers`、`app/domain`)。按层切的问题是同一个决定散在几层里:
- 「专家能看见什么」散在四处:Excel 列白名单在 `app/providers/clinical.py`、`modalities.py`,输入包的形状在 `app/domain/input_packages.py`,按病例拼装在 `app/domain/input_readers.py`,入库分区在 `app/domain/ingestion*.py`。
- CEUS 处理有两条互相分叉的路径:产品编排 `app/domain/ceus_roi/service.py`,以及实际跑出 40 例结果的 `debugs/` 脚本。两者在裁黑边、识别分屏用的帧、关键帧在哪一步算这几处行为不同(design 0002 决定 4)。
判断一刀切得对不对,问这 7 个问题:
1. 这个模块藏住的是哪一个决定?说不出来,或者说出来的是「第几步」,就是切错了。
2. 换掉这个决定,要改几个模块?应该只改 1 个。
3. 对外露出的函数和类型,是否比内部实现简单得多?
4. 依赖有没有绕成圈?下层能不能离开上层单独测试?
5. 纯计算的模块里,有没有读写文件、网络或模型?
6. 每样外部的东西(交付包的 Excel、MedSAM2、模型服务、账本的 SQLite),是不是只有一个模块直接碰它?
7. 同一份知识(哪一路能看见哪些列、诊断编码、关键帧的时刻定义)是不是只在一处定义?
---
## 二、项目的完整范围
项目做胰腺囊性肿瘤的鉴别诊断:三个 AI 专家各看一路资料,模拟多学科会诊,判断病灶属于 IPMN、SPN、MCN、SCN 四类中的哪一类(全称见 `AGENTS.md`),结论对病理金标准评价;同时评价专家从资料里抽取字段的可靠性。模块要覆盖全部四段,不只是旧项目已经做的部分。
| 段 | 做什么 | 输入 → 输出 | 旧项目状态 |
| --- | --- | --- | --- |
| CEUS 定量 | 从超声造影视频里追踪病灶、画出亮度曲线、拟合、挑关键帧、算灌注指标 | `data/` 的视频 → `work/` | 算法约 7.3k 行,40 例跑通 34 例(旧项目远程结果清单);两条路径行为不一致,映射锚点按 1856 宽素材写死(design 0002 决定 4) |
| 装配两个库 | 读交付包,按白名单把每路允许的内容写进给专家的资料库,把答案写进标准答案库 | `data/` + `work/` → `library/`、`answers/` | 列白名单和编号规范化有,但按旧样本 `1.2-TEST` 写死了工作表名和列名,读不了 40 例交付包 |
| 会诊 | 三个专家各读自己那一路,按会诊流程给出判断 | `library/` → `runs/` | 没有,只有三版设计文档 |
| 评价 | 会诊结论对金标准;专家抽取的字段对医生标注 | `runs/` + `answers/` → `runs/<运行号>/eval/` | 没有;写过一套评估骨架,因缺陷和无人使用而删除 |
整体框架:每段一个入口脚本、一份配置,段与段之间只通过磁盘上的目录交接。双线以下的会诊碰不到标准答案库,评价才把会诊结果和答案放在一起。
```
data/交付包_20260908_40例/ 医院原始资料(只读)
│ 视频 │ 三张表、关键图
▼ │
① CEUS 定量 │ config/ceus.yaml
scripts/ceus_quant.py │
ceus + segment(显卡) │
│ │
▼ ▼
work/ 中间文件 ─────────► ② 装配两个库 config/build.yaml
(可删掉重算) scripts/build.py config/code_maps.yaml
delivery → build
┌─────────┴──────────┐
▼ ▼
library/<构建号>/ answers/<构建号>/
给专家的资料库 标准答案库
化名、三路分开 金标准、化名与真实病例号对照、跳过清单
│ │
══════════════════════╪════════════════════╪═══ 会诊读不到右侧
▼ │
③ 会诊 scripts/consult.py │ config/consult.yaml
consult → llm(PolyGateway) │
│ │
▼ │
runs/<运行号>/ │
各路意见、会诊结论、账本 │
│ │
└─────────┬──────────┘
▼
④ 评价 scripts/evaluate.py config/evaluate.yaml
▼
runs/<运行号>/eval/
```
`data/`、`work/`、`library/`、`answers/`、`runs/` 都不进 git(第五部分)。每段的配置怎么写见第五部分「配置」。
几个贯穿全文的词:
- **给专家的资料库**(`library/`):AI 专家能看到的一切。每次装配是一个新目录,叫一次**构建**。
- **标准答案库**(`answers/`):病理诊断、化名与真实病例号的对照、跳过清单(没进资料库的病例及原因),将来还有医生标注工作簿。只给评价用。
- **中间文件**(`work/`):CEUS 定量的工作产物,可以删掉重算。
- **运行**:一次会诊或评价,产物放在 `runs/` 下一个新目录里,只追加不删。
- **账本**:一次会诊运行里每次调用模型的记录(请求、回复、用量、结局),存为 SQLite,只追加。
- **关键帧**:从 CEUS 视频里挑出交给超声专家的 7 张图,定义见第四部分。
---
## 三、模块
| 层 | 模块 | 藏住的决定 | 旧项目里的对应内容 |
| --- | --- | --- | --- |
| 入口 | `scripts/`(不在包内) | 人怎么操作:每段一个脚本(`ceus_quant`、`build`、`consult`、`evaluate`),另有一个按顺序调用四段的总入口;读配置文件,把模块装配起来 | `debugs/t1_local_debug.py`、`debugs/tic_lognormal_replay.py` |
| 组织 | build | 两个库怎么装配:每一路放哪些内容(可见性的唯一决定处)、化名怎么取、病例顺序怎么打乱、清单记什么。全项目只有它同时碰得到原始资料和答案 | `app/domain/input_readers.py`、`ingestion*.py` 的装配部分 |
| 组织 | consult | 会诊怎么进行:几个专家、谁先说、后发言的看得到前面的什么、什么时候结束;「调一次模型」的统一入口(先存请求、记开始、调用、存回复、记结束)也在这里 | 没有 |
| 组织 | evaluate | 评价口径:指标、统计方法、字段级对照怎么计分 | 没有(`core/eval` 已删) |
| 格式 | library | 给专家的资料库的目录布局、文件格式和读取;按路分别读出,一路一个对象 | `app/domain/input_packages.py` 的数据形状 |
| 格式 | answers | 标准答案库的目录布局、文件格式和读取 | 旧库里的金标准表 |
| 格式 | verdicts | 会诊产物(各路意见、会诊结论)的文件格式和读取;consult 写、evaluate 读 | 没有 |
| 外部 | delivery | 交付包长什么样:目录和文件名、工作表与列名、病例编号的几种写法和规范化、诊断编码、报告「印象 / 结论」段怎么认、跨表一致性怎么核对。只读,不决定谁能看见什么 | `app/providers/clinical.py`、`modalities.py`、`workbook.py`、`delivery.py` |
| 外部 | segment | 用哪个分割模型、权重在哪、怎么在显卡上把提示传播成逐帧掩码 | `debugs/medsam2_local_track.py`、`roi_inference/` 的后端 |
| 外部 | llm | 经 PolyGateway(实验室的模型调用库,管外部错误的重试和超时)调模型;交出服务商返回的精确版本号、响应编号、用量、结束原因。只管调用,不写账本 | `app/providers/model_gateway_config.py`(只借思路);ClinDB 的 `llm/` |
| 外部 | ledger | 账本怎么存:SQLite 只追加、大块内容按哈希存成文件、同一目录同时只许一个进程写、找出开始了没结束的调用 | 没有;ClinDB 的 `ledger/` |
| 外部 | runs | 运行目录怎么命名、运行快照记什么(git 提交号、配置、所用构建、提示词哈希、模型版本、随机种子) | 没有 |
| 纯计算 | ceus | CEUS 定量的全部算法和参数:分屏、绿框、坐标映射、多边形、暗区、TIC 采样、Lognormal 拟合、关键帧、灌注指标、出图;视频解码也在这里 | `app/domain/ceus_roi/`(除 `service.py`)、`app/providers/video_decoder.py` |
暂不设共用词汇模块。一样东西要被两个互相不能引用的模块用到,才另立一处放它;目前跨模块的类型各有归属(资料库格式归 library、答案格式归 answers、会诊产物格式归 verdicts)。配置的字段定义和校验归使用它的模块:CEUS 参数归 ceus(旧 `research_config.py`),会诊的实验配置归 consult;每个字段必填,不补默认值(`AGENTS.md`「不设默认值」)。
依赖只能往下,同一行的模块之间不互相依赖:
```
入口 scripts/
组织 build consult evaluate
格式 library answers verdicts
外部 delivery segment llm ledger runs
纯计算 ceus
```
谁具体能依赖谁:
| 模块 | 可以依赖 |
| --- | --- |
| ceus、delivery、segment、llm、ledger、runs | 不依赖其他 `cadx` 模块 |
| library、answers、verdicts | 不依赖其他 `cadx` 模块 |
| build | delivery、ceus(读 CEUS 结果的类型)、library、answers、runs |
| consult | library、verdicts、llm、ledger、runs |
| evaluate | answers、verdicts、runs |
| scripts/ | 任何模块(不在导入检查范围内,见第五部分) |
### 几条边界
- **专家能看见什么,只在 build 一处决定。** build 按白名单把每路允许的内容写进 `library/`,此外任何模块都不挑选给专家的材料。consult 只拿到某一次构建的路径,经 library 按路读出;它不依赖 delivery、answers、build,读原始资料和答案的代码对它来说根本不存在。报告的「印象 / 结论」段(医生写的诊断倾向)由 delivery 认出、由 build 剔除,`library/` 里只有「所见」描述。
- **交付资料的检查不过,整例跳过。** delivery 逐例检查(编号、文件、取值、跨表是否一致,具体检查项在装配两个库的任务书里列),build 把没过的病例整例跳过:资料库和答案库的病理诊断里都没有它,答案库的跳过清单写明它没过哪一项、涉及哪个文件哪一列。一例遇到第一项不过就不再往下查,清单上每例只记一项。整张表读不了时整次构建停下。规则见 `AGENTS.md`「错误怎么处理」。
- **三路互相隔开。** 每个专家的提示词只用自己那一路的对象拼。consult 里拼某一路提示词的函数只接收那一路的对象,不接收整个病例。这是研究设计本身:模拟各科医生各看各的检查结果,再在会诊时交换意见。会诊时一路能看到另一路的什么,由 consult 的会诊流程决定,在会诊的任务书里定。
- **我们自己调模型只有一个入口:consult 的「调一次模型」。** 它按固定顺序做五步:把完整请求存进账本的大块存储、写「调用开始」、经 llm 调用、把完整回复存进大块存储、写「调用结束」。llm 只管调用,不写账本,所以任何一次模型调用都绕不开登记。中断后重启时,从账本找出开始了没结束的调用,记为「状态未知」,不重做已有结果的调用。做重复运行(同一病例、同一专家调多次,测一致性)时,每一次都恰好登记一次。
- **CEUS 算法不知道分割模型是谁,也不管中间文件放哪。** ceus 接收视频路径、参数和分割结果,交出曲线、拟合、关键帧和指标;中间文件写在哪、按什么复用,由 `scripts/ceus_quant.py` 决定(目前只有它一个使用者,出现第二个使用者再收进模块)。segment 只管「给提示、出逐帧掩码」,不认识分屏和绿框。
- **医生的判断不进 CEUS 定量。** 医生标注工作簿里的时刻(如造影剂到达时间 AT)属于标准答案库,ceus 和 build 都不读它;关键帧全部由程序自己算或按推注时间取(design 0002 决定 6)。交付视频里医生画的绿框是 CEUS 定量的输入,它能否作为输入、在最终病灶轮廓里占多大分量,记在 `../待定问题/01-医生画的绿框能否作为输入.md`。
- **评价只读文件,不重新调模型。** evaluate 读 `runs/` 里的会诊产物(经 verdicts)和 `answers/`,改指标或统计方法不需要重跑会诊。
---
## 四、两个库与 CEUS 中间文件的内容
### 给专家的资料库(`library/<构建号>/`)
```
library/<构建号>/
manifest.json 构建清单:每个文件的 SHA-256、构建时的 git 提交、配置哈希、所用 CEUS 结果的来源
cases/<化名>/
clinical/clinical.json 临床那一路
ultrasound/ 超声那一路:report.txt、key.png、keyframes/*.png、tic.json
mr/report.txt MR 那一路
```
每一路能看见什么:
| 内容 | 临床 | 超声 | MR |
| --- | --- | --- | --- |
| 性别、年龄 | 看得见 | 看不见 | 看不见 |
| 5 个症状、5 项既往史、5 项化验(原值与是否异常) | 看得见 | 看不见 | 看不见 |
| 超声报告「所见」段、医生截的关键图 | 看不见 | 看得见 | 看不见 |
| CEUS 关键帧、TIC 灌注指标 | 看不见 | 看得见(给哪些指标、几张图由会诊任务书定) | 看不见 |
| MR 报告「所见」段 | 看不见 | 看不见 | 看得见 |
| 病理诊断、报告「印象 / 结论」段、医生标注工作簿、真实病例号、Excel 行序、姓名 | 看不见 | 看不见 | 看不见 |
性别、年龄只给临床一路:医院在两份报告工作簿里也复制了这两列,但影像两路先看到它们会被人口学先验带偏(旧项目 design 0026 决定三)。
病例用化名,目录顺序打乱:交付文件名里带病例号(如 `U001_roi2.mp4`),行序可能和诊断有关。图片写入时去掉文件元数据。构建完不再修改;要改就做一次新构建。
### 标准答案库(`answers/<构建号>/`)
与同一次构建的资料库一一对应(同一个构建号),放:
- 病理诊断:三张表的诊断列各存一份,用哪一份算分由评价决定;
- 化名对照:化名、真实病例号、CEUS 视频号。会诊产物只用化名,评价靠这张表接回真实病例;
- 跳过清单:没进资料库的病例、没过的那一项检查、涉及的文件和列,以及对不上的各表原值,供医生核对。40 例里已知有 6 例临床表与超声表对不上(`../待定问题/04-交付数据的跨表不一致.md`),会出现在这份清单里;
- 将来加医生标注工作簿。
### CEUS 中间文件(`work/`)
CEUS 定量按下面的阶段进行(旧项目的阶段划分,迁移时逐模块核对):
| 阶段 | 做什么 | 跨运行复用 |
| --- | --- | --- |
| 1 解码与分屏 | 读视频;超声机录造影时屏幕左右分两半,一半造影画面、一半普通灰阶画面,找出分界 | 否 |
| 2 提示帧 | 在造影画面上找医生画的绿框,挑出绿框位置有变化的帧,换算到另一半画面上 | 否 |
| 3 分割 | segment 用 MedSAM2 把提示传播成逐帧病灶掩码;需要显卡,最贵 | **是**,按「视频内容哈希 + 阶段 1–3 的参数 + 模型权重哈希」存 |
| 4 多边形 | 掩码转成平滑轮廓,与绿框融合 | 否 |
| 5 暗区 | 找出病灶里不进造影剂、发黑的囊液部分 | 否 |
| 6 TIC 采样 | 逐帧取病灶(去不去暗区两种)平均亮度,得到 TIC(时间-强度曲线) | 否 |
| 7 拟合 | 用 Lognormal(对数正态)曲线拟合 TIC,过质量门 | 否 |
| 8 关键帧 | 按下面的定义挑 7 张 | 否 |
| 9 灌注指标 | 从拟合曲线算 11 个指标(峰值强度、达峰时间、洗入速率等,口径仿照商用造影定量软件 VueBOX) | 否 |
只有阶段 3 跨运行复用,其余每次重算:阶段 3 要显卡,最贵;其余阶段在 CPU 上算。改了后段参数不必重跑显卡。
关键帧:
| 帧 | 含义 | 怎么取 |
| --- | --- | --- |
| B0 | 刚开始变亮之前 | 从拟合曲线算出到达时刻,取其前 |
| D1 | 开始变亮 | 拟合曲线上的到达时刻 |
| D2 | 洗入拐点(变亮最快处) | 拟合曲线 |
| D3 | 最亮 | 拟合曲线 |
| D4 | 洗出拐点(变暗最快处) | 拟合曲线 |
| F1 | 推注后第 20 秒 | 视频第 20 − X 秒 |
| F2 | 推注后第 60 秒 | 视频第 60 − X 秒 |
X 是超声报告工作簿的「推注开始时间s」,含义是视频第 0 秒时已经推注了多少秒(design 0002 决定 5)。某个时刻落在视频之外,或附近没有可用帧,该帧记为未定位并写明原因,不拿别的帧补。各时刻的容差窗口和出处(哪些来自和临床方约定的口径,哪些是旧项目工程侧定的)在迁移关键帧模块时逐个核对,写进 ceus 的参数文件注释。
---
## 五、目录与检查
| 位置 | 内容 | 进 git |
| --- | --- | --- |
| `src/cadx/` | 一个 Python 包,每个模块一个子包;别的模块只能经子包的 `__init__.py` 引用它,内部文件加 `_` 前缀 | 是 |
| `scripts/` | 入口脚本 | 是 |
| `tests/` | 不花钱、不要显卡的测试;需要显卡的标 `gpu`,花钱的标 `live`,两者不进 `make check` | 是 |
| `config/` | 每段一份配置,另有编码映射表(见下文「配置」);密钥只写环境变量名 | 是 |
| `prompts/` | 版本化的提示词文件;每次运行记下所用提示词的哈希 | 是 |
| `data/` | 医院交付的原始资料 | 否 |
| `library/`、`answers/` | 两个库,每次构建一个目录 | 否 |
| `work/` | CEUS 中间文件 | 否 |
| `runs/` | 会诊与评价的运行,只追加不删 | 否 |
| `models/` | 模型权重 | 否 |
| `reference/`、`reference-pdf/` | 旧项目副本、外部代码、文献原文 | 否 |
不进 git 的目录由 `.gitignore` 和 pre-commit 的 `local-only-paths` 两道拦截,`git add -f` 也提交不了。
### 配置
所有参数都写在 `config/` 下的 YAML 文件里,每段一份:
| 文件 | 给谁用 | 内容 |
| --- | --- | --- |
| `build.yaml` | `scripts/build.py` | 交付包位置、两个库的输出位置、化名与打乱顺序的随机种子、编码解码开关与映射表位置 |
| `code_maps.yaml` | build(经 `build.yaml` 引用) | 编码映射表:交付包里每个用数字编码的列,每个数字代表什么(如性别的 1、2) |
| `ceus.yaml` | `scripts/ceus_quant.py` | CEUS 定量的全部参数,每个参数用注释写明出处 |
| `consult.yaml` | `scripts/consult.py` | 会诊的实验配置与模型用途 |
| `evaluate.yaml` | `scripts/evaluate.py` | 评价的指标与统计设置 |
每段一份,是因为四段分开运行,各自的快照只记自己的配置;改会诊参数不会改变装配配置的哈希,不会让人误以为资料库要重建。哪份文件在哪个模块落地时建,不提前写空文件。
规则:
- **入口脚本只接收配置文件路径**,不能在命令行单独改某个参数,「这次用了什么参数」只看文件就知道。
- **字段定义归使用它的模块**(第三部分),用 pydantic 写:每个字段必填,不设默认值;可以没有的值显式写 null;多写或拼错的字段名报错;读入后不能修改。
- **路径、随机种子、模型名、服务地址都写进配置**,代码里不写死。
- **每次运行把所用配置的原文复制进快照**,并记下 SHA-256 哈希;build 的快照同时复制编码映射表。
- **编码解码是一个开关。** `build.yaml` 的 `decode_codes` 必填,取 true 或 false。取 false 时,映射表位置写 null,编码列按原始数字写进资料库,并注明「编码含义未解码」;取 true 时,映射表位置必填,映射表必须覆盖交付包的全部编码列,缺一列就整次构建停下,某一例出现映射表里没有的值就跳过这一例。编码的含义要向临床方要映射表;到位之前用 false。
文件格式:数组用 npz(读取时 `allow_pickle=False`,pickle 读取时会执行任意代码),每例的标量和摘要用 json,跨病例的表用 csv,图用 png。缺失值写 null,不写 NaN 或 0:0 是合法的测量值,NaN 在 json 里不合法。
导入边界用 import-linter 检查,写进 `make check`。契约在 `pyproject.toml`,检查范围是 `src/cadx`,**不含 `scripts/` 和 `tests/`**。下面只列契约实际强制的,没有的保护不写:
- `cadx` 不引用 `torch`、`sam2`。
第三部分的依赖表和下面几条外部库独占,随模块落地逐条写成契约(import-linter 只能检查已经存在的模块):只有 delivery 引用 `openpyxl`;只有 library 引用 `PIL`(写资料库的图片时去掉元数据);只有 segment 引用 `torch`、`sam2`;只有 llm 引用 `polygateway`;只有 ledger 引用 `sqlite3`。
契约管不到的,不要当成机器守住了:
- `scripts/` 不在检查范围内,一个脚本可以同时读 `answers/` 和调会诊。consult 在启动时核对:传进来的路径必须在 `library/` 下的某次构建里,否则报错。会诊落地时另写一条泄露扫描测试,检查发给模型的请求全文里没有答案和真实病例号。
- 三路互相隔开靠 consult 的函数签名(拼某一路提示词的函数只接收那一路的对象),import-linter 管不到同一模块内部,靠审查和上面的泄露扫描。
- ceus 是纯计算,只有「不引用其他 `cadx` 模块」能写成契约;它要读视频文件,`open()` 之类内置函数拦不住。
Python 环境用 uv;安装与检查命令见 `../guides/开发环境.md`。
---
## 六、规模
下表是粗估,参考了旧项目对应部分的行数。**计数口径**:一个模块的行数是 `cat src/cadx/<模块>/*.py | wc -l`,不含测试和提示词文件;旧项目的数按同样口径数(2026-10-16)。动手写某个模块之前,由负责人逐个确认;写的过程中超过预期一倍,就停下来检查是需求本身复杂,还是专门路径在变多。
| 模块 | 预期行数 | 实际行数 | 旧项目对应部分的行数 |
| --- | --- | --- | --- |
| ceus | 6000–7500(基本照搬旧算法,去掉只为产品层服务的部分) | — | `ceus_roi/` 除 `service.py` 7330,`video_decoder.py` 135 |
| segment | 150–300 | — | `medsam2_local_track.py` 240 |
| delivery | 400–700 | — | `clinical.py` 266、`modalities.py` 522、`workbook.py` 99、`delivery.py` 134,共 1021,其中入库与数据库部分不迁 |
| library | 150–300 | — | `input_packages.py` 104 |
| answers | 100–200 | — | — |
| verdicts | 100–200 | — | — |
| build | 300–500 | — | `input_readers.py` 329 的装配部分 |
| llm | 200–450 | — | ClinDB `llm/` 415 |
| ledger | 200–300 | — | ClinDB `ledger/` 274 |
| runs | 100–250 | — | — |
| consult | 500–1000(会诊口径定后重估) | — | — |
| evaluate | 300–600(评价口径定后重估) | — | 已删的 `core/eval` 622 |
| scripts/ 每个 | ≤ 300 | — | `t1_local_debug.py` 762、`tic_lognormal_replay.py` 627 |
---
## 七、推进顺序
1. **骨架**(已完成):协作规则、文档体系、工具链。
2. **装配两个库(先不含 CEUS 结果):delivery → library、answers → runs 的快照部分 → build。** 先做它,是因为它最轻,做完就能用文字资料试会诊;而且隔离(专家看不到答案)是整个研究成立的前提,越早有代码和测试守住越好。顺带产出跳过清单,交给医生核对,也给 `待定问题/04` 提供证据。参数系统(第五部分「配置」)随这一步落地。
3. **CEUS 定量:ceus 按旧模块逐个迁移(带上旧的算法单测)→ segment → `scripts/ceus_quant.py` → build 加入 CEUS 结果。** 旧算法单测约 274 个,不依赖数据库,随模块带过来,作为迁移正确性的主要依据(design 0002 决定 4)。已知问题(映射锚点写死、两条旧路径的行为差别、造影画面在右半边)在任务书里逐条定。
4. **会诊:ledger → llm → consult 的「调一次模型」→ verdicts → consult 的会诊流程。** 动手前先专门讨论定下会诊口径:后发言的看得到什么、怎样算两路一致、超声一路给哪些指标和图、模型与重复次数等(旧项目留下的未裁决问题)。
5. **评价:evaluate。** 动手前先定指标、统计方法,以及 `待定问题/02`(参数何时冻结、开发集与测试集怎么分)、`待定问题/03`(病例排除标准)。
第 2 步和第 3 步互不依赖,可以换顺序;第 3 步完成后,build 要重新构建一次才把 CEUS 结果装进资料库。
---
## 八、各模块对外的函数与类型
写到哪个模块时,再把它对外的函数和类型补在这里。