From 1f7a6a3d9c4d2441f61dbecd736db9c0889b4fc8 Mon Sep 17 00:00:00 2001 From: iomgaa Date: Thu, 15 Oct 2026 23:42:37 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=8D=8F=E4=BD=9C=E8=A7=84=E5=88=99?= =?UTF-8?q?=E3=80=81=E5=B7=A5=E4=BD=9C=E8=AE=B0=E5=BD=95=E4=B8=8E=20resear?= =?UTF-8?q?ch-wiki=20=E6=96=87=E6=A1=A3=E4=BD=93=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 从产品重构为研究原型的第一步(负责人 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 --- AGENTS.md | 151 ++++++++++++++++++ CLAUDE.md | 3 + README.md | 8 + WORK_LOG.md | 29 ++++ research-wiki/README.md | 22 +++ .../design/0001-从产品重构为研究原型.md | 50 ++++++ research-wiki/explanation/结构.md | 113 +++++++++++++ research-wiki/guides/开发环境.md | 37 +++++ research-wiki/reference/.gitkeep | 0 research-wiki/results/.gitkeep | 0 research-wiki/scratch/.gitkeep | 0 research-wiki/survey/.gitkeep | 0 research-wiki/任务书/.gitkeep | 0 13 files changed, 413 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 WORK_LOG.md create mode 100644 research-wiki/README.md create mode 100644 research-wiki/design/0001-从产品重构为研究原型.md create mode 100644 research-wiki/explanation/结构.md create mode 100644 research-wiki/guides/开发环境.md create mode 100644 research-wiki/reference/.gitkeep create mode 100644 research-wiki/results/.gitkeep create mode 100644 research-wiki/scratch/.gitkeep create mode 100644 research-wiki/survey/.gitkeep create mode 100644 research-wiki/任务书/.gitkeep diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0990bff --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,151 @@ +# AGENTS.md + +本文件是给所有编码 Agent(Claude Code、Codex 等)的工作说明。`CLAUDE.md` 只引用本文件,内容以这里为准。 + +## 项目是什么 + +CADx-4 研究胰腺囊性肿瘤的鉴别诊断:给同一个病人的三路资料(临床信息、CEUS 超声造影视频与超声报告、MR 报告)各配一个只看自己那一路的 AI 专家,模拟多学科会诊,判断病灶属于四类胰腺囊性肿瘤中的哪一类:IPMN(导管内乳头状黏液性肿瘤)、SPN(实性假乳头状肿瘤)、MCN(黏液性囊性肿瘤)、SCN(浆液性囊性肿瘤)。前三类有恶变可能或本身是低度恶性,通常要手术或密切随访;SCN 几乎都是良性,多数不用手术。分错直接决定病人是手术、随访还是不用管。金标准是术后或穿刺取得的病理结论。最终产出是一篇医学 AI 交叉论文,重点在临床问题、与人类医生的对比和可解释性,不在方法复杂度。 + +几个词先解释:**MR** 是磁共振检查,本项目只有它的文字报告;**CEUS** 是往静脉打造影剂后连续录下的超声视频;**ROI**(感兴趣区)是视频里圈出的病灶区域;**TIC**(时间-强度曲线)是 ROI 平均亮度随时间的变化,造影剂进来变亮、洗出变暗,曲线形状就是灌注的量化。本仓库的术语表随模块迁移逐步建在 `research-wiki/reference/术语表.md`;建成前查旧项目的 `reference/CADx-4-CAD/research-wiki/reference/glossary.md`。 + +本仓库是对旧项目的**重构**。旧项目在远程服务器上按工程产品搭建(HTTP 服务、数据库、任务队列),后来发现目前不需要做成产品,于是在这里按研究原型重写,只保留有研究价值的算法和知识。旧项目的完整副本在 `reference/CADx-4-CAD/`(只读,见下文)。重构的来由和取舍见 `research-wiki/design/0001-从产品重构为研究原型.md`。 + +和临床合作方之间的议题、会议纪要、交付清单和临床口径在 Linear(团队用的议题跟踪系统,临床方也在上面)上,不在本仓库。 + +## 新会话先读什么 + +1. `WORK_LOG.md`:当前目标、做到哪一步、下一步、运行中的任务。 +2. `research-wiki/README.md`:文档体系与各目录的用途。 +3. `research-wiki/explanation/结构.md`,以及与当前任务相关的其他 `explanation/` 文档。 + +## 事实来源 + +文档之间有冲突时按下表定谁说了算,找负责人确认,不自行折中成第三种说法。 + +| 问题 | 看哪里 | +| --- | --- | +| 当前进度、下一步 | `WORK_LOG.md` | +| 代码分哪些模块、谁能依赖谁、各模块预期规模 | `research-wiki/explanation/结构.md` | +| 各环节在做什么、输入输出 | `research-wiki/explanation/` 的其他文档 | +| 某个决策为什么这样定 | `research-wiki/design/NNNN-*.md`(写完冻结) | +| 命令怎么敲、环境怎么配 | `research-wiki/guides/` | +| 医学词、参数的原始口径、临床方给的字段定义 | `research-wiki/reference/`(每条自带出处) | +| 配置值、字段定义 | 代码里的定义与校验;文档只解释含义,不抄数值 | +| 临床口径、双方约定、待交付清单 | Linear(临床方维护的那几份 document 是权威,仓库里的抄本以它为准) | +| 旧项目做了什么、踩过什么坑 | `reference/CADx-4-CAD/`(只当线索,见下文) | + +## reference/ 怎么用 + +`reference/CADx-4-CAD/` 是旧项目的副本,含完整 git 历史(366 个提交)和当时没进 git 的 `debugs/` 脚本。 + +- 只读。不在里面改文件、不在里面跑会写盘的命令。 +- **算法代码可以迁移**:`app/domain/ceus_roi/`(除 `service.py`)、`app/providers/video_decoder.py` 和 `debugs/` 里的三个 `.py` 脚本是负责人自己的成果,迁移时去掉数据库、HTTP、任务队列等产品层依赖,按本文件的标准改写,并补上测试。读交付包的逻辑(`app/providers/` 里的列白名单、病例号规范化、剔除报告结论段)只借规则,按普通函数重写。产品层代码(`app/api/`、`persistence/`、`workers/`、`migrations/` 等)不搬。 +- 旧文档和报告里的数字(40 例跑通 34 例、参数取值等)只是线索。要写进本仓库的文档或结论,先在原始产物或本仓库的真实运行里核到。 +- 旧 `research-wiki/design/0001–0049` 讲的是当时为什么那样定。其中与研究有关的结论(优先读哪几份见本仓库 `research-wiki/design/0001` 第 8 条的表)在本仓库重新写成 explanation 或 design。可以注明「出自旧 design 00NN」交代来历,但现行规则以本仓库的文档为准。 + +`reference-pdf/` 放文献原文,命名 `年份-第一作者-短名.pdf`,索引在 `reference-pdf/INDEX.md`。两者都不进 git。 + +## 硬性约束 + +1. **答案不进 AI 专家能看到的任何东西。** 病理金标准、工作簿里的诊断列、按规则算出诊断倾向的公式列、医生的结构化判读列(**标注工作簿**是临床方另交的一份表,医生在里面逐例标了病灶位置、大小、囊壁、增强模式等三十来列,它只用于评价专家抽取得准不准)、报告的「印象 / 结论」段、真实病例号、Excel 行序,一律不能进入提示词、工具返回或专家可读的文件。**行序也算**:原始表格按诊断分组录入,行号本身就带着答案。这一条错了不会报错,只会让所有「系统诊断得准不准」的结论变成「系统能不能读到答案」。任何会削弱这条的改动,先停下来问。来源:旧项目 CLAUDE.md §1.2、旧 design 0026。 +2. **每个 AI 专家只看自己那一路资料。** 性别和年龄只给临床那一路;超声和 MR 两路看不到,即使医院把这几列也复制进了报告工作簿。各路能看见什么见 `research-wiki/explanation/` 里的会诊文档(迁移完成前看旧项目 `research-wiki/explanation/agent-system.md` §2)。来源:同上。 +3. **患者资料不进 git、文档和提交说明。** 原始资料只在 `data/`,运行产物只在 `runs/`,两者都不进 git。文档里可以写结构和规模,不写报告原文、姓名、病例号和诊断。把病例资料发给外部模型服务之前,先确认负责人已同意该服务。超声帧上烧录的信息栏(拼音姓名、检查号、性别年龄、医院与设备参数)不做遮蔽:临床方判定拼音和不唯一的检查号不构成身份泄露(出自旧 design 0041)。 +4. **密钥不进 git、文档、日志和运行快照。** 只放环境变量或本地 `.env`;仓库只提交不含真实值的 `.env.example`。 +5. **写进论文和结论的数字能追到一次真跑的原始记录**(`runs/` 下的运行目录)。不把预期、估算、旧项目报告或别人论文里的数字当成本仓库的结果;没有就写「没有」。文献里的数字核到 `reference-pdf/` 的原文。 +6. **错误不静默吞掉。** 不吞异常,不用默认值把问题盖过去,缺关键配置直接报错。细则见「写代码的标准」。 +7. **花钱的批量运行先说预估成本。** 调模型 API 跑全部病例这类动作,开跑前在回复里给出预估;量级明显超过以往的,先问。 + +## 怎么协作 + +- **用简体中文回复,先给一句话结论。** +- **先讲后写。** 新环节或新设计先在终端讲:旧项目怎么做的、问题在哪、打算怎么做、为什么。负责人认可后再写文件或代码。需要负责人决定时,一次列出全部问题、选项和你的倾向。 +- **能自己定的直接定**,按设计判断或文献先例给方案,不把问题抛回去。 +- **范围要窄,改得要深。** 只动这次要改的那一块;范围内要改到读起来像一开始就这么设计的,被取代的旧内容同一次删掉。范围外看到问题,记下来告诉负责人。 +- **讨论会话与执行会话分开。** 和负责人讨论方向的会话写任务书(`research-wiki/任务书/NN-*.md`),由负责人另开会话执行、讨论会话验收。小改动不必写任务书。 +- **子 Agent 交证据不交判断**:带 `文件:行号`、命令原始输出或产物路径;prompt 里不带邮箱等个人信息和患者资料;要用浏览器只能用无头模式。收回的报告先抽查两三条原文,其中一条挑带评价性修饰的句子;子 Agent 说「通过」不算数,要自己核对实际改动和测试产物。 +- **审查**:改动完成后派一个新鲜上下文的子 Agent,只给 diff、验收标准和相关文档,不给实现时的推理。下文「账本级」的代码另派一个全新上下文的子 Agent 再审一轮;Codex 可用时再加一轮对抗审查(`/codex:rescue --fresh --wait`)。审查意见只采纳影响正确性或明确需求的项,采纳前问:日常使用中会不会真撞上。 +- **汇报**时分开写已验证的事实、推断和未验证的部分。 +- **新加的规则注明来源**:负责人哪句话、哪份 design 文档,还是 Agent 自己提的。本文件的改动来源写在提交说明里。 +- 推送、建 PR、往 Linear 写东西这类外部可见的动作先问;Linear 上有临床方在看。 + +## 写代码的标准 + +### 两档标准 + +- **账本级**(像记账一样只追加、可核对;出错会悄悄污染结论的部分):答案隔离(各路资料怎么读、怎么分给专家)、金标准的读取、评分与统计、写进论文的定量结果。要求: + - 运行产物只追加,已有的运行目录不删、不覆盖,要删先问; + - 每条规则有独立预期值的测试,预期值来自手算、独立脚本或人工核对过的样例,不从实现里抄公式。 +- **其余部分**:视频解码、ROI 追踪、绘图、提示词拼装、脚本。写错了马上能看出来,按研究原型的标准写:用最直接的写法,遇到罕见情况就停下来把原因写清楚。 + +### 可复现 + +随机种子、模型的精确版本号(以服务商返回为准)、提示词版本、git 提交号和全部配置,写进每次运行的快照。复现不了的结果等于没有结果。每次运行一个新目录,同一个输出目录同时只允许一个进程写。 + +### 不设默认值 + +配置的每个字段都必填,函数参数不写默认值;可以没有的值显式写 null,不靠省略。缺了就报错。默认值会掩盖错误:字段名拼错、文档改了代码没跟上,流水线照样跑完,没人知道用的是哪个值。路径、模型名、服务地址同样不写死在代码里。 + +### 罕见情况怎么办 + +「可能会出现」不是写分支的理由。按顺序选: + +1. 改数据的表示或格式要求,让它不可能出现。 +2. 做不到,就在系统边界检查,不满足就报错停下。边界指交付的资料、模型的输出、负责人的配置。模块之间的调用不算边界,不互相防御。 + +一个抽象在出现第二个真实调用点之前不引入。不考虑向后兼容:不写兼容层,读到旧格式直接报错;发现更好的设计就按它重写,同步改文档和测试。 + +### 遇到问题先找原因 + +出了问题先查清为什么,再到原因所在的地方改。重试、跳过、补默认值、放宽解析、捕获异常后接着跑,只是让症状消失。动手前问:这个改动是让问题不再发生,还是让问题发生了也没人看见? + +### 错误怎么处理 + +- **外部错误**(模型接口超时、网络断开):只在调用外部服务的那一处做有限次重试,仍失败就把这一步记为失败并写明状态。 +- **内部错误**(资料不满足规则、不该有的状态组合):立刻停下,报错写清是哪条规则、哪个病例的哪个文件。模型交不出合格输出按内部错误处理。 + +中间层不写 try/except,除非它能把错误完整处理掉且上层不需要知道。不写 `except Exception: pass` 及其变体。 + +### 提示词 + +- 提示词引导模型怎么想,不设必填栏目:逼模型填栏目,它会为了填而编。只有程序确实要读的值才做成输出字段。 +- 提示词是 `prompts/` 下的版本化文件,不散落在代码字符串里;给模型的提示词用英文。 + +### 测试 + +- 真实模型链路要用真跑证明,假模型测试证明不了;需要显卡或花钱的测试用 pytest 标记 `gpu`、`live` 区分,不进 `make check`。 +- 修缺陷时附一条能复现原问题的测试。 +- 不为方便测试给产品代码加参数、开关或分支;不靠删断言、改预期值或只保留成功的运行让测试通过。 + +### 规模与注释 + +- 每个模块的预期规模写在结构文档里;超过预期一倍时停下检查:是需求本身复杂,还是专门路径在变多。 +- 注释只写这段代码现在做什么、为什么这样做;修改过程写进提交说明和 `WORK_LOG.md`。 + +## 文档 + +- **代码标识符用英文;注释、docstring、文档和回复用简体中文。** +- 读者是没参与过讨论的医学 AI 方向硕士生,也包括负责人:懂 Python 和深度学习,不认识 IPMN,也不知道我们讨论过什么。医学词、缩写、代号首次出现处就地解释;每个决策写清为什么。 +- 同一个配置值只在一处定义,别处引用。 +- `explanation/`、`guides/`、`reference/` 是常青文档:改了就重写那一段,不在后面追加「某日又改了」。 +- 决策的来历写进 `research-wiki/design/NNNN-简短主题.md`,按顺序编号,写完冻结;决策变了新写一份,开头写明取代哪份,旧的开头加一行指向新的。 +- `WORK_LOG.md` 只记当前状态:目标、已完成(附证据)、未完成、关键决定(附负责人原话)、下一步。一个阶段完成后压缩成几行。没有真实证据的「可能情况」只在汇报里提一句,不写进 `WORK_LOG.md`。 +- **新写或大改一份文档**(新文档、重写整节、改动超过约 100 行),过一轮「硕士生阅读」:新鲜上下文的子 Agent 扮演这样的读者,只读这份文档,先列出最可能读不下去或要回头重读的段落、再复述要点,然后报哪句读不懂、哪个医学词没解释、哪个决策缺理由、哪个值两处不一致。不要让它描述自己的思考过程。格式和措辞建议不采纳。 + +## 提交规范 + +提交信息用英文前缀加中文正文,前缀按 Conventional Commits:`feat:`、`fix:`、`docs:`、`test:`、`refactor:`、`chore:` 等,可加作用域,例如 `feat(ceus): 暗区掩膜改用奇偶规则`。 + +## 本机环境的坑 + +- `grep`、`find`、`ps`、`diff` 可能被 shell 包装函数改写,静默少输出。搜索用 `/usr/bin/grep`、`/usr/bin/find`;查进程用 `pgrep -af`;比较文件用 `cmp` 或 `/usr/bin/diff`;看 git 差异用 `git --no-pager diff`。派出去的子 Agent 的 prompt 里也写上这一条。 +- 超过约一分钟的命令放进 tmux 跑,日志写到文件。 +- 日常开发和实验在本机(一张 RTX 4070 Ti SUPER 16GB)。需要用远程服务器(旧项目所在的 4090 服务器)时,ssh 和 rsync 一律走 `wangwenhan@202.38.247.58`,不走 Tailscale 地址 `100.83.164.94`(同一台机器,后者实测约 150KB/s,前者约 100MB/s)。它的显卡是共用的,命令必须显式设 `CUDA_VISIBLE_DEVICES`。 + +## 开发命令 + +- `make sync`:用 uv 同步项目环境和依赖。 +- `make fmt`:用 ruff 自动修正并格式化。 +- `make test`:运行不花钱、不需要显卡的测试。 +- `make check`:依次检查 lint、格式、类型、导入边界并运行测试;失败即停止。 + +环境怎么搭见 `research-wiki/guides/开发环境.md`。 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4e45b4a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +Instructions for Claude Code in this repository live in AGENTS.md (shared with all coding agents). + +@AGENTS.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3084210 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# CADx-4 + +胰腺囊性肿瘤(IPMN / SPN / SCN / MCN)的多模态 AI 会诊诊断研究:临床、CEUS 超声造影、MR 报告三路资料各交给一个只看自己那一路的 AI 专家,模拟多学科会诊,结论对病理金标准评价。 + +- 协作规则:`AGENTS.md` +- 当前进度:`WORK_LOG.md`(本文件不记进度) +- 文档入口:`research-wiki/README.md`,代码结构见 `research-wiki/explanation/结构.md` +- 搭环境:`research-wiki/guides/开发环境.md` diff --git a/WORK_LOG.md b/WORK_LOG.md new file mode 100644 index 0000000..8cc4cf2 --- /dev/null +++ b/WORK_LOG.md @@ -0,0 +1,29 @@ +# 工作记录 + +只记当前状态。一个阶段完成后压缩成几行,细节看提交说明和 `research-wiki/`。 + +## 当前目标 + +重构第一步:搭好协作环境和项目骨架(`research-wiki/explanation/结构.md` 第六节第 1 步)。下一步是 CEUS 定量的迁移。 + +## 已完成 + +- 2026-10-15 盘点旧项目(远程 `CADx-4-CAD`)与两个参考项目(ClinDB-ReviewBench、dissect3),结论写进 `research-wiki/design/0001-从产品重构为研究原型.md`。 +- 2026-10-15 旧项目复制到 `reference/CADx-4-CAD/`:工作区(含没进 git 的 `debugs/t1_local_debug.py`、`debugs/medsam2_local_track.py` 和 `tic_lognormal_replay.py` 的未提交改动)加 `git fetch` 恢复的 366 个提交、5 个远程分支。 +- 2026-10-15 `data/` 同步完成:`交付包_20260908_40例`(124 个文件)、`1.2-TEST`(1460 个文件),文件数与文件总字节数(4,034,244,118)和远程一致。 +- 2026-10-15 骨架(三份新文档过了一轮「硕士生阅读」并按意见修订):`AGENTS.md`、`CLAUDE.md`、`research-wiki/` 目录与 `README.md`、`explanation/结构.md`、`design/0001`、`guides/开发环境.md`;uv 工具链、`make sync/fmt/test/check`、pre-commit。`make check` 通过(1 项测试、1 条导入契约)。 + +## 未完成 + +- 首次提交待负责人确认。 + +## 关键决定 + +- 负责人 2026-10-15「其其实不需要(至少目前不需要)是一个工程产品」:按研究原型重写,产品层不迁移(design 0001)。 +- 负责人 2026-10-15 选定:检查强度「ClinDB 级」(不要提交审查闸);注释和 docstring 用中文;本机为主(「目前以本地为主,以后会根据具体情况调整」)。 +- 负责人 2026-10-15 同意:MedSAM2 与 torch 装在同一个 uv 环境,靠导入边界隔离,不单独开环境(design 0001 第 7 条)。 + +## 下一步 + +- 写任务书 02:CEUS 定量迁移(`cadx.ceus`、`cadx.segment`、`cadx.runs` 和脚本),验收为本机复现旧项目 40 例 T1 结果与 `1.2-TEST` 的 Lognormal 回放。动手前先和负责人讲旧代码的结构与打算。 +- 等临床方交付 40 例的医生标注工作簿(含造影剂到达时间和注射时间)。 diff --git a/research-wiki/README.md b/research-wiki/README.md new file mode 100644 index 0000000..b9eafd1 --- /dev/null +++ b/research-wiki/README.md @@ -0,0 +1,22 @@ +# research-wiki + +本项目的文档。读者是没参与过讨论的医学 AI 方向硕士生:懂 Python 和深度学习,不懂胰腺囊性肿瘤。 + +| 位置 | 内容 | 维护方式 | +|---|---|---| +| `explanation/` | 系统由哪些环节组成、各环节的输入输出、为什么这样分。从 `结构.md` 读起 | 常青:改了就重写那一段 | +| `design/` | 每次重要决策的来历,文件名 `NNNN-简短主题.md` | 写完冻结;决策变了新写一份并注明取代关系 | +| `guides/` | 怎么做某件事:建环境、跑某个环节 | 常青,随实际情况更新 | +| `reference/` | 查得到的外部事实:术语表、算法参数的原始口径、临床方给的字段定义。每条自带出处;抄自 Linear 的写明抄自哪份、哪天同步 | 常青,随出处更新 | +| `任务书/` | 每个模块动手前的任务书,文件名 `NN-模块名.md`:要做什么、依据哪些文档、怎么验收 | 负责人确认后交执行者;要改就在开头写「这一版改了什么」 | +| `survey/` | 文献调研:同类工作怎么做、对我们意味着什么,引文带 PDF 页码(PDF 在根目录 `reference-pdf/`,不进 git) | 写完冻结;有新调研另写一份 | +| `results/` | 实验结果报告,每个数字标明来自哪次运行(`runs/` 下的目录名) | 写完冻结 | +| `scratch/` | 讨论中的草稿、上下文压缩前的快照 | 由负责人清理,Agent 不删 | + +## 几类文档的开头 + +不用 YAML 头,靠固定的开头段落: + +- **design**:`# NNNN 标题` → `状态:负责人 YYYY-MM-DD 认可(「原话」) | 日期:…` → 取代了谁就写一行 `> 取代 NNNN 的第 X 条` → `## 先认识几个词` → `## 背景` → `## 决定`(编号条目,写明哪条是负责人定的、哪条是 Agent 提的) → 需要时加 `## 不做的事`。 +- **任务书**:标题 → 一段目的 → `来源:`(负责人原话) → `## 先认识几个词` → `## 开工前先读` → 分阶段的工作内容(每阶段写收尾点) → `## 测试` → `## 规模` → `## 审阅` → `## 报告`(分开写已验证的事实、推断和未验证的部分,附运行目录、提交号、花费)。 +- **results**:`日期 | 对应结构.md 第六节第 N 步 | 原始记录:runs/…` → `## 先认识几个词` → `## 一 跑了什么` → 结果,每个数字带运行目录。 diff --git a/research-wiki/design/0001-从产品重构为研究原型.md b/research-wiki/design/0001-从产品重构为研究原型.md new file mode 100644 index 0000000..7ac9b44 --- /dev/null +++ b/research-wiki/design/0001-从产品重构为研究原型.md @@ -0,0 +1,50 @@ +# 0001 从产品重构为研究原型 + +状态:负责人 2026-10-15 认可(方案在终端讲过,负责人逐项选定后回复「同意」) | 日期:2026-10-15 + +## 先认识几个词 + +- **旧项目**:远程服务器 `100.83.164.94` 上的 `/home/wangwenhan/CADx-4-CAD`(分支 `feat/ceus-tic-cad`,366 个提交),本仓库的前身。本地副本在 `reference/CADx-4-CAD/`。 +- **T1**:旧项目对「CEUS 视频 → ROI 追踪 → TIC 曲线」这条链路的叫法。 +- **S0–S3**:旧项目对会诊流水线的分段:S0 整理并隔离输入,S1 各路抽取医学事实,S2 各路给出判断,S3 三路讨论。 +- **产品层**:为了把系统做成服务而写的代码:HTTP 接口、数据库与迁移、任务队列、对象存储、模型网关治理。 + +## 背景 + +负责人 2026-10-15:「我觉得远程服务器……上的 /home/wangwenhan/CADx-4-CAD 弄的有点过于复杂了。因为我们后面发现其其实不需要(至少目前不需要)是一个工程产品,所以我觉得现在需要在本项目中重构一下。」 + +2026-10-15 对旧项目的盘点(只读,原始报告未存档,下列路径都可在 `reference/CADx-4-CAD/` 核对): + +- `app/` 共 17,283 行(`wc -l`)。其中真正的研究成果是 `app/domain/ceus_roi/`(8770 行):分屏识别、绿框识别、坐标映射、暗区检测、TIC、Lognormal 拟合、关键帧、灌注指标。这些文件除 `service.py` 外不依赖数据库和接口。 +- 其余绝大部分是产品层:`app/api/`、`persistence/`、`storage/`、`workers/`、`container.py`、`ports.py`、`migrations/`、`roi_inference/`(把 MedSAM2 包成 HTTP 服务)等。 +- 会诊(S1–S3)和评价**一行代码都没有**:`app/agents/` 从未建立,评价代码已删除。 +- 40 例 T1 批量结果实际是由 `debugs/t1_local_debug.py` 和 `debugs/medsam2_local_track.py` 绕过接口、数据库直接调用算法跑出来的,而这两个脚本没进 git,`debugs/tic_lognormal_replay.py` 也有未提交的改动。 +- `CLAUDE.md` 33KB,大部分是文档权威、迁移不可改、多轮审查等产品治理规则。 + +也就是说,产品层占了一半代码和大部分规则,却没有被实际的研究用到;研究真正走的是绕过它的脚本。 + +## 决定 + +1. **本仓库按研究原型重写,不做产品。** 没有数据库、HTTP 接口、任务队列和对象存储;运行产物写成文件放在 `runs/` 的运行目录里。以后真要做成产品,那时再按产品需求设计。 +2. **旧项目整体复制为只读参考。** `reference/CADx-4-CAD/` 含完整 git 历史和没进 git 的 `debugs/` 脚本,不进本仓库的 git。研究数据只同步两批:`交付包_20260908_40例` 和 `1.2-TEST`(唯一带造影剂到达时间的那 1 例),放在 `data/`。旧项目的 T1 结果(约 12G,主要是叠加视频)能重新生成,不整批同步;CEUS 迁移验收要对比的摘要和轨迹文件届时按需拉取。 +3. **算法代码迁移,产品层不迁移。** 迁移 `app/domain/ceus_roi/`(除 `service.py`)、`app/providers/video_decoder.py` 和三个 `debugs/` 脚本,改写成 `cadx.ceus`、`cadx.segment` 和 `scripts/`。读交付包的列白名单、病例号规范化、剔除报告结论段这些逻辑,在 `cadx.cases` 里按普通函数重写。 +4. **协作规则与文档体系照 ClinDB-ReviewBench 的做法。** ClinDB-ReviewBench 和 dissect3 是负责人另外两个研究项目,协作环境运转良好,负责人点名参考。 规则只写一份 `AGENTS.md`,`CLAUDE.md` 只引用它;进度在 `WORK_LOG.md`;文档在 `research-wiki/`,常青文档改了就重写,design 写完冻结。旧项目的硬性约束里只保留研究本身需要的几条(答案隔离、各路隔离、患者资料、密钥、不吞错误),再加上 ClinDB 的两条(数字可追溯到真跑、花钱先报预估);产品治理规则不保留。负责人在两档之间选定「ClinDB 级」检查:ruff(代码规范)、pyright strict(类型检查)、import-linter(导入边界)、pre-commit(gitleaks 查密钥、拦截本地材料);不要 dissect3 那样每次提交都要有审查凭证的提交闸。理由(Agent 提的):研究原型改动频繁,提交闸的成本高于它在这里防住的风险。 +5. **注释和 docstring 用中文。** 负责人选定;负责人和合作的读者都以中文为母语,注释要讲清「为什么」,母语写得准。代码标识符仍用英文,给模型的提示词用英文。 +6. **本机为主。** 负责人原话:「目前以本地为主,以后会根据具体情况调整」。本机有一张 RTX 4070 Ti SUPER 16GB,够跑 MedSAM2 的 tiny 版本。 +7. **一个 uv 环境,torch 也装在里面,由导入边界隔离。** 负责人问过 MedSAM2 是否一定要单独环境;查过 MedSAM2 的 `setup.py`,它只依赖 `torch>=2.5.1`、`torchvision`、`numpy>=2.0.1`、`hydra-core`、`iopath`、`pillow`、`SimpleITK`、`tqdm`,和本项目其他依赖不冲突,CUDA 扩展可用 `SAM2_BUILD_CUDA=0` 关掉。旧项目拆成三个 conda 环境,理由是把分割做成了 HTTP 服务;现在不做服务,这个理由不成立,拆开只会多出子进程通信和两套环境。隔离改由 import-linter 契约保证:只有 `cadx.segment` 能导入 `torch` 和 `sam2`。以后真出现依赖冲突再拆,那时调用点只有这一个模块。负责人回复「同意」。torch 等依赖在 CEUS 迁移那份任务书里加入,本次骨架不装。 +8. **旧 design 的编号不继承。** 旧项目 `research-wiki/design/0001–0049` 留在 `reference/` 里当历史。与研究有关的结论,在本仓库对应模块落地时重新写成 explanation 或新的 design;新文档可以注明「出自旧 design 00NN」,但现行规则以本仓库为准。理由:旧 design 里大量内容依附于数据库和服务,直接当权威会把产品层的约束带回来。需要重写时优先读下表这几份: + +| 旧编号 | 主题 | 落到本仓库的哪一步 | +| --- | --- | --- | +| 0015、0024 | CEUS ROI 的 CPU 流水线、视频流式解码 | CEUS 定量 | +| 0030、0031 | TIC 定量与关键帧、临床口径与工程编码分开 | CEUS 定量 | +| 0042–0049 | VueBOX 差距分析、Lognormal 拟合、截图就绪判定、Excel 定时的关键时刻、11 个灌注指标、三线 TIC 图 | CEUS 定量 | +| 0026 | 标注工作簿只归评价侧、证据边界 | 资料与答案隔离 | +| 0041 | 不做图像遮蔽 | 资料与答案隔离 | +| 0033–0036 | S1 三路证据的设计(从未实现) | 会诊 | +| 0006 | 会诊辩论机制(已被 2026-08-02 纪要的单轮顺序流程取代) | 会诊 | + +## 不做的事 + +- 不复刻旧项目的数据库表结构、处理状态机和按数据库记录派发任务的机制。旧项目推迟待办的几件数据库修补(视频状态列、坏视频重跑等)随产品层一起放弃。 +- 不迁移 `tests/fitness`、`tests/contract`、`tests/integration`、`tests/e2e`:它们测的是产品层。`tests/unit` 里 `ceus_roi` 相关的测试在迁移时挑着带过来。 diff --git a/research-wiki/explanation/结构.md b/research-wiki/explanation/结构.md new file mode 100644 index 0000000..dc046a7 --- /dev/null +++ b/research-wiki/explanation/结构.md @@ -0,0 +1,113 @@ +# 结构:代码分哪些模块、谁能依赖谁 + +来源: +- 负责人 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(计算机断层扫描)图像。 + +答案(病理结论)也在这些工作簿的某一列里,必须在读取的那一刻就分出去。 + +处理分四步: + +1. **CEUS 视频 → 定量结果。** 超声机录造影时屏幕左右分成两半,一半是造影画面、一半是普通灰阶画面,先识别分屏、取出造影那半;医生在病灶上画了一个绿框,用它告诉程序病灶在哪;再用 MedSAM2(一个医学图像分割模型)把病灶轮廓逐帧追踪下去;病灶里的囊液不进造影剂、在造影画面上发黑,这部分(暗区)要扣掉,剩下的实性部分的平均亮度随时间的曲线就是 TIC。再用 Lognormal(对数正态)曲线拟合,取关键时刻的帧,算出 11 个灌注指标,如峰值强度、达峰时间、洗入速率(仿照商用造影定量软件 VueBOX 的口径;为什么选这套口径和 Lognormal 模型,见旧项目 design 0042、0043、0047,迁移时重写进本仓库)。这一步不涉及模型 API,只用本机显卡。 +2. **整理三份输入。** 每个病例分出三份互相看不见的输入:临床那份、超声那份(含第 1 步的定量结果、关键图和报告的「所见」段;关键帧图像给不给、怎么给,第 4 步定)、MR 那份(报告的「所见」段)。答案和医生的结构化判读列只进评价侧。 +3. **会诊。** 三个 AI 专家各读自己那份输入。按 2026-08-02 和临床方的会议纪要(双方共同认下的结论;纪要只写了顺序和「主要看超声与 MR 两路能否一致」,没写为什么这样排),流程是单轮顺序:超声专家先给影像信息和判断,MR 专家再给,临床专家补充临床信息;主要看超声和 MR 两路能否一致。 +4. **评价。** 拿会诊结论对病理金标准算准确率等指标;拿专家抽出的字段对医生的判读列算抽取准确率。 + +## 二、切分原则 + +模块按「以后可能会改的设计决定」切,每个模块把一个决定藏在里面,决定变了只改这一个模块(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` 也提交不了。 + +## 六、推进顺序 + +1. **骨架**(已完成):协作规则、文档体系、工具链。 +2. **CEUS 定量**:迁移 `cadx.ceus`、`cadx.segment`、`cadx.runs` 和对应脚本。验收标准是在本机复现旧项目的 40 例 ROI 追踪与 TIC 结果和 `1.2-TEST`(只有 1 例,但它是唯一带造影剂到达时间的病例)的 Lognormal 回放,逐例对比旧产物。旧产物还在远程,对比要用的摘要和轨迹文件在那份任务书里按需拉取,视频不拉。 +3. **资料与答案隔离**:`cadx.cases`、`cadx.gold`,加上泄露扫描测试(输入包里不出现金标准、结论段、病例号)。 +4. **会诊**:`cadx.llm`、`cadx.consult`。动手前要先定模型服务。 +5. **评价**:`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 读过)。是否自动判别左右未定。 diff --git a/research-wiki/guides/开发环境.md b/research-wiki/guides/开发环境.md new file mode 100644 index 0000000..a0acd0f --- /dev/null +++ b/research-wiki/guides/开发环境.md @@ -0,0 +1,37 @@ +# 开发环境 + +读者:第一次在本机搭环境的人。 + +## 一、前提 + +- [uv](https://docs.astral.sh/uv/)(Python 包和环境管理工具),本机 `~/.local/bin/uv`。 +- Python 3.12:uv 会按 `.python-version` 自动准备,不需要另装。 + +## 二、同步环境 + +```bash +make sync # 按 uv.lock 建 .venv 并安装依赖 +uv run pre-commit install # 每次提交前跑 gitleaks、本地材料拦截和格式检查 +``` + +## 三、日常命令 + +| 命令 | 做什么 | +|---|---| +| `make fmt` | ruff 自动修正并格式化 | +| `make test` | 不花钱、不需要显卡的测试 | +| `make check` | lint、格式、类型(pyright strict)、导入边界(import-linter)、测试,依次执行,失败即停 | + +需要显卡的测试用 `uv run pytest -m gpu`,花钱的真实模型测试用 `uv run pytest -m live`,都不在 `make check` 里。 + +## 四、数据和本地材料 + +以下目录不进 git,新机器上要自己准备: + +| 目录 | 从哪来 | +|---|---| +| `data/` | 远程 `wangwenhan@202.38.247.58:CADx-4-CAD/data/` 下的 `交付包_20260908_40例`、`1.2-TEST`,用 rsync 同步 | +| `reference/CADx-4-CAD/` | 旧项目:先 rsync 工作区(排除 `data/`),再 `git fetch` 远程 `CADx-4` 仓库的全部分支恢复历史 | + +远程服务器走 `202.38.247.58`,不走 Tailscale 地址 `100.83.164.94`:同一台机器,后者慢几百倍。 +| `models/` | MedSAM2 权重,CEUS 迁移的任务书落地后补充来源 | diff --git a/research-wiki/reference/.gitkeep b/research-wiki/reference/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/research-wiki/results/.gitkeep b/research-wiki/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/research-wiki/scratch/.gitkeep b/research-wiki/scratch/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/research-wiki/survey/.gitkeep b/research-wiki/survey/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/research-wiki/任务书/.gitkeep b/research-wiki/任务书/.gitkeep new file mode 100644 index 0000000..e69de29