从产品重构为研究原型的第一步(负责人 2026-10-15:「其其实不需要(至少目前不需要) 是一个工程产品」)。协作环境照 ClinDB-ReviewBench(负责人点名参考)。 - AGENTS.md 为唯一规则,CLAUDE.md 只引用它;硬性约束保留旧项目的答案隔离、各路隔离, 加上数字可追溯、花钱先报预估等 - WORK_LOG.md 记当前状态与负责人的关键决定 - research-wiki:README、explanation/结构.md(8 个模块、依赖边界、推进顺序)、 design/0001(为什么重构、继承与放弃什么、单一 uv 环境)、guides/开发环境.md - 三份新文档过了一轮「硕士生阅读」并按意见修订 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 KiB
结构:代码分哪些模块、谁能依赖谁
来源:
- 负责人 2026-10-15:「我们后面发现其其实不需要(至少目前不需要)是一个工程产品,所以我觉得现在需要在本项目中重构一下」。
- 负责人 2026-10-15 选定检查强度「ClinDB 级」、注释用中文、本机为主(「目前以本地为主,以后会根据具体情况调整」)。
- 旧项目的研究内容:
reference/CADx-4-CAD/research-wiki/explanation/agent-system.md、ceus-roi-pipeline.md、data-and-inputs.md。
本文是常青文档:模块划分变了就重写对应段落。为什么从产品改成研究原型、继承旧项目哪些东西,见 ../design/0001-从产品重构为研究原型.md。
一、从一个病例走一遍
先看数据怎么流过整个系统,再看代码怎么切。
医院按批次交付资料,病种的全称见 AGENTS.md「项目是什么」(目前是 data/交付包_20260908_40例/)。每个病例有三路资料:
- 临床:一张工作簿里的一行,性别、年龄、症状、既往史、抽血化验值。
- 超声:一段 CEUS 视频(
U0xx_roi2.mp4)、一张医生截的关键图、一份超声报告。报告分「所见」(描述看到了什么)和「印象 / 结论」(医生的诊断倾向)两段。 - MR(磁共振):一份 MR 报告文本。目前没有 MR 和 CT(计算机断层扫描)图像。
答案(病理结论)也在这些工作簿的某一列里,必须在读取的那一刻就分出去。
处理分四步:
- CEUS 视频 → 定量结果。 超声机录造影时屏幕左右分成两半,一半是造影画面、一半是普通灰阶画面,先识别分屏、取出造影那半;医生在病灶上画了一个绿框,用它告诉程序病灶在哪;再用 MedSAM2(一个医学图像分割模型)把病灶轮廓逐帧追踪下去;病灶里的囊液不进造影剂、在造影画面上发黑,这部分(暗区)要扣掉,剩下的实性部分的平均亮度随时间的曲线就是 TIC。再用 Lognormal(对数正态)曲线拟合,取关键时刻的帧,算出 11 个灌注指标,如峰值强度、达峰时间、洗入速率(仿照商用造影定量软件 VueBOX 的口径;为什么选这套口径和 Lognormal 模型,见旧项目 design 0042、0043、0047,迁移时重写进本仓库)。这一步不涉及模型 API,只用本机显卡。
- 整理三份输入。 每个病例分出三份互相看不见的输入:临床那份、超声那份(含第 1 步的定量结果、关键图和报告的「所见」段;关键帧图像给不给、怎么给,第 4 步定)、MR 那份(报告的「所见」段)。答案和医生的结构化判读列只进评价侧。
- 会诊。 三个 AI 专家各读自己那份输入。按 2026-08-02 和临床方的会议纪要(双方共同认下的结论;纪要只写了顺序和「主要看超声与 MR 两路能否一致」,没写为什么这样排),流程是单轮顺序:超声专家先给影像信息和判断,MR 专家再给,临床专家补充临床信息;主要看超声和 MR 两路能否一致。
- 评价。 拿会诊结论对病理金标准算准确率等指标;拿专家抽出的字段对医生的判读列算抽取准确率。
二、切分原则
模块按「以后可能会改的设计决定」切,每个模块把一个决定藏在里面,决定变了只改这一个模块(Parnas 1972 的信息隐藏)。不按上面的四步切,因为步骤会调整,而「交付包长什么样」「用哪个分割模型」这类决定和步骤无关。
另一条原则来自硬性约束 1:答案在哪、谁能读,由导入边界机器检查,不靠自觉。所以读金标准单独成一个模块,只有评价模块能导入它。
三、模块
| 层 | 模块 | 藏住的决定 | 旧项目对应 | 预期规模 |
|---|---|---|---|---|
| 入口 | scripts/(不在包内) |
一次研究步骤怎么组合:参数从哪来、产物写到哪个运行目录 | debugs/*.py |
每个 ≤ 300 行 |
| 组织 | cadx.consult |
会诊流程:几个专家、谁先说、看到别人的什么 | 无(从未实现) | 600 |
| 组织 | cadx.evaluate |
指标口径、统计方法 | 无(已删除) | 400 |
| 资料 | cadx.cases |
交付包的磁盘格式、列名、病例号形状、「结论」段怎么剔除、每路看得见哪些列 | app/providers/clinical.py 等、input_readers.py |
600 |
| 资料 | cadx.gold |
金标准在哪一列、数字编码对应哪一类(编码由临床方定,可能会改;取值只写在这个模块的代码里) | config/runtime.yaml 的 diagnosis_coding |
100 |
| 外部 | cadx.llm |
用哪家模型服务、怎么调用、怎么记录请求和回复 | model_gateway_config.py(只借思路) |
300 |
| 外部 | cadx.segment |
用哪个分割模型(目前 MedSAM2)、权重在哪 | debugs/medsam2_local_track.py |
300 |
| 外部 | cadx.runs |
运行目录的命名、快照里记什么、单进程写锁 | 无 | 200 |
| 纯计算 | cadx.ceus |
ROI 与 TIC 的算法和参数:分屏、绿框、暗区、曲线、拟合、关键帧、灌注指标 | app/domain/ceus_roi/(除 service.py)、app/providers/video_decoder.py |
6000 |
规模是粗估,按旧项目同类代码的行数推算,每个模块的任务书动手前由负责人确认。cadx.ceus 偏大,是因为旧项目 app/domain/ceus_roi/ 共 8770 行,去掉编排和数据库实体(service.py 1440 行)后约 7.3k 行,迁移时再删掉只为产品层服务的部分(2026-10-15 用 wc -l 量)。
这里没有旧项目的数据库、HTTP 接口、任务队列、对象存储、迁移脚本。运行产物直接写成文件,放在 runs/ 下的运行目录里。
四、谁能依赖谁
上层可以导入下层,同一层的模块互不导入(例外:gold 只给组织层的 evaluate 用):
入口 scripts/
组织 consult evaluate
资料 cases gold(只给 evaluate)
外部 llm segment runs
纯计算 ceus
具体边界:
- 只有
cadx.evaluate能导入cadx.gold。 这是硬性约束 1 的机器检查。会诊那一侧拿不到读金标准的函数,也就不可能把答案递给专家。 cadx.consult只经cadx.cases的公开接口拿输入包,不自己读工作簿。- 只有
cadx.segment导入torch、sam2。torch 和 MedSAM2 将来装在同一个 uv 环境里(CEUS 迁移时加入,见 design 0001 第 7 条),但会诊和评价那一半的代码碰不到它们。 - 只有
cadx.llm导入模型服务的 SDK。 - 只有
cadx.cases和cadx.gold导入openpyxl(读 Excel)。 cadx.ceus不导入cadx的其他模块。它只做计算和读视频,分割结果由调用方传进来。
这些边界写成 pyproject.toml 里的 import-linter 契约,随模块落地逐条加入:import-linter 对还不存在的模块无从检查。目前只有一条:cadx 整体不导入 torch、sam2;cadx.segment 落地时改成「除 cadx.segment 外」。
契约里没有的保护不要当成机器守住了。 比如 scripts/ 不在包内,import-linter 管不到它;一个脚本完全可以同时导入 gold 和 consult。脚本放在包外是有意的:「跑会诊再评分」这种组合天然要同时碰两边,放进包里也得给它开例外。所以脚本里的隔离靠审查,以及第 3 步的泄露扫描测试(直接检查发给模型的输入里有没有答案)。
每个模块是一个子包,内部文件加 _ 前缀,对外只经 __init__.py 的 __all__ 暴露。这样别的模块能用什么一眼可见,模块内部怎么改不牵连外面。
五、目录
| 位置 | 内容 | 进 git |
|---|---|---|
src/cadx/ |
上面的模块 | 是 |
scripts/ |
研究步骤的入口脚本 | 是 |
tests/ |
测试;需要显卡的标 gpu,花钱的标 live |
是 |
config/ |
算法参数、模型服务清单(只写环境变量名) | 是 |
prompts/ |
版本化提示词 | 是 |
research-wiki/ |
文档,见其 README.md |
是 |
data/ |
医院交付的原始资料 | 否 |
runs/ |
每次运行一个目录,含快照和全部产物 | 否 |
models/ |
模型权重(MedSAM2) | 否 |
reference/ |
旧项目副本、外部代码仓库 | 否 |
reference-pdf/ |
文献原文 | 否 |
不进 git 的目录由 .gitignore 和 pre-commit(提交前自动运行的检查)里的 local-only-paths 一项两道拦截,git add -f 也提交不了。
六、推进顺序
- 骨架(已完成):协作规则、文档体系、工具链。
- CEUS 定量:迁移
cadx.ceus、cadx.segment、cadx.runs和对应脚本。验收标准是在本机复现旧项目的 40 例 ROI 追踪与 TIC 结果和1.2-TEST(只有 1 例,但它是唯一带造影剂到达时间的病例)的 Lognormal 回放,逐例对比旧产物。旧产物还在远程,对比要用的摘要和轨迹文件在那份任务书里按需拉取,视频不拉。 - 资料与答案隔离:
cadx.cases、cadx.gold,加上泄露扫描测试(输入包里不出现金标准、结论段、病例号)。 - 会诊:
cadx.llm、cadx.consult。动手前要先定模型服务。 - 评价:
cadx.evaluate。
第 2 步和第 3 步互不依赖,可以换顺序。
七、还没定的事
- 用哪家模型服务。 旧项目经实验室的模型调用库 PolyGateway 调通义千问;更早的讨论选过 Claude Agent SDK。第 4 步任务书里定。
- 专家先抽事实再判断,还是一步出判断。 旧项目设计过两段式(先抽出可追溯的医学事实,再据此判断四分类),抽出的事实还能拿来对医生判读列算抽取准确率;但一行代码都没写。第 4 步讨论。
- 医生标注工作簿(见
AGENTS.md硬性约束 1)。40 例的标注工作簿还没交付,Lognormal 回放需要的造影剂到达时间(AT)和注射时间在里面。到之前只有1.2-TEST的 1 例能做完整回放。 - 分屏里造影画面在左还是在右。 旧项目 40 例跑通 34 例;失败的 6 例里 3 例(013、029、036)是左半边找不到绿框,013 已确认造影画面在右半边,配置却写死取左边;另 3 例(007、011、033)是分屏中缝对比度不够、识别失败(出自旧项目远程
data/交付包_20260908_40例_result/结果清单_20261016.md,2026-10-15 读过)。是否自动判别左右未定。