feat: 装配两个库(不含 CEUS 结果)
按任务书 01 实现 delivery、library、answers、runs(快照部分)、build 五个模块, scripts/build.py、scripts/scan_library.py 与 config/build.yaml(decode_codes: false)。 交付包整表检查 T1–T4、映射表检查 T5、逐例检查 C01–C13,一例遇到第一项不过即整例跳过。 与任务书原文的出入: - C04 报告表名随视频名配对(负责人 2026-10-16 定)。 - 按两轮独立审阅加严 C11(更多换行符、比例号冒号)、C12(同义词与词根)、 C13(空格、周岁、中文数字、繁体);泄露扫描做 NFKC;关键图只接受能保真重编码的 PNG; YAML 配置拒绝重复键和空路径。 - import-linter 第 1 条拆成互不依赖与不引用其他 cadx 模块两条。 文档:结构.md 第五、六、八部分按实际更新;新增 guides/装配两个库.md;WORK_LOG 更新。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -98,7 +98,7 @@ work/ 中间文件 ─────────► ② 装配两个库
|
||||
| 层 | 模块 | 藏住的决定 | 旧项目里的对应内容 |
|
||||
| --- | --- | --- | --- |
|
||||
| 入口 | `scripts/`(不在包内) | 人怎么操作:每段一个脚本(`ceus_quant`、`build`、`consult`、`evaluate`),另有一个按顺序调用四段的总入口;读配置文件,把模块装配起来 | `debugs/t1_local_debug.py`、`debugs/tic_lognormal_replay.py` |
|
||||
| 组织 | build | 两个库怎么装配:每一路放哪些内容(可见性的唯一决定处)、化名怎么取、病例顺序怎么打乱、清单记什么。全项目只有它同时碰得到原始资料和答案 | `app/domain/input_readers.py`、`ingestion*.py` 的装配部分 |
|
||||
| 组织 | build | 两个库怎么装配:每一路放哪些内容(可见性的唯一决定处)、化名怎么取(化名随机,资料库按化名排序,顺序就与交付顺序无关)、清单记什么。全项目只有它同时碰得到原始资料和答案 | `app/domain/input_readers.py`、`ingestion*.py` 的装配部分 |
|
||||
| 组织 | consult | 会诊怎么进行:几个专家、谁先说、后发言的看得到前面的什么、什么时候结束;「调一次模型」的统一入口(先存请求、记开始、调用、存回复、记结束)也在这里 | 没有 |
|
||||
| 组织 | evaluate | 评价口径:指标、统计方法、字段级对照怎么计分 | 没有(`core/eval` 已删) |
|
||||
| 格式 | library | 给专家的资料库的目录布局、文件格式和读取;按路分别读出,一路一个对象 | `app/domain/input_packages.py` 的数据形状 |
|
||||
@@ -137,7 +137,7 @@ work/ 中间文件 ─────────► ② 装配两个库
|
||||
### 几条边界
|
||||
|
||||
- **专家能看见什么,只在 build 一处决定。** build 按白名单把每路允许的内容写进 `library/`,此外任何模块都不挑选给专家的材料。consult 只拿到某一次构建的路径,经 library 按路读出;它不依赖 delivery、answers、build,读原始资料和答案的代码对它来说根本不存在。报告的「印象 / 结论」段(医生写的诊断倾向)由 delivery 认出、由 build 剔除,`library/` 里只有「所见」描述。
|
||||
- **交付资料的检查不过,整例跳过。** delivery 逐例检查(编号、文件、取值、跨表是否一致,具体检查项在装配两个库的任务书里列),build 把没过的病例整例跳过:资料库和答案库的病理诊断里都没有它,答案库的跳过清单写明它没过哪一项、涉及哪个文件哪一列。一例遇到第一项不过就不再往下查,清单上每例只记一项。整张表读不了时整次构建停下。规则见 `AGENTS.md`「错误怎么处理」。
|
||||
- **交付资料的检查不过,整例跳过。** delivery 逐例检查(编号、文件、取值、跨表是否一致;检查项 C01–C13 的速查表见 `../guides/装配两个库.md`,规则以 `src/cadx/delivery/_cases.py`、`_text.py` 为准),build 把没过的病例整例跳过:资料库和答案库的病理诊断里都没有它,答案库的跳过清单写明它没过哪一项、涉及哪个文件哪一列。一例遇到第一项不过就不再往下查,清单上每例只记一项。整张表读不了时整次构建停下。规则见 `AGENTS.md`「错误怎么处理」。
|
||||
- **三路互相隔开。** 每个专家的提示词只用自己那一路的对象拼。consult 里拼某一路提示词的函数只接收那一路的对象,不接收整个病例。这是研究设计本身:模拟各科医生各看各的检查结果,再在会诊时交换意见。会诊时一路能看到另一路的什么,由 consult 的会诊流程决定,在会诊的任务书里定。
|
||||
- **我们自己调模型只有一个入口:consult 的「调一次模型」。** 它按固定顺序做五步:把完整请求存进账本的大块存储、写「调用开始」、经 llm 调用、把完整回复存进大块存储、写「调用结束」。llm 只管调用,不写账本,所以任何一次模型调用都绕不开登记。中断后重启时,从账本找出开始了没结束的调用,记为「状态未知」,不重做已有结果的调用。做重复运行(同一病例、同一专家调多次,测一致性)时,每一次都恰好登记一次。
|
||||
- **CEUS 算法不知道分割模型是谁,也不管中间文件放哪。** ceus 接收视频路径、参数和分割结果,交出曲线、拟合、关键帧和指标;中间文件写在哪、按什么复用,由 `scripts/ceus_quant.py` 决定(目前只有它一个使用者,出现第二个使用者再收进模块)。segment 只管「给提示、出逐帧掩码」,不认识分屏和绿框。
|
||||
@@ -156,6 +156,7 @@ library/<构建号>/
|
||||
cases/<化名>/
|
||||
clinical/clinical.json 临床那一路
|
||||
ultrasound/ 超声那一路:report.txt、key.png、keyframes/*.png、tic.json
|
||||
(后两项等 CEUS 定量落地后由 build 加入,目前只有前两项)
|
||||
mr/report.txt MR 那一路
|
||||
```
|
||||
|
||||
@@ -172,7 +173,7 @@ library/<构建号>/
|
||||
|
||||
性别、年龄只给临床一路:医院在两份报告工作簿里也复制了这两列,但影像两路先看到它们会被人口学先验带偏(旧项目 design 0026 决定三)。
|
||||
|
||||
病例用化名,目录顺序打乱:交付文件名里带病例号(如 `U001_roi2.mp4`),行序可能和诊断有关。图片写入时去掉文件元数据。构建完不再修改;要改就做一次新构建。
|
||||
病例用化名,目录和清单按化名排序:交付文件名里带病例号(如 `U001_roi2.mp4`),行序可能和诊断有关;化名是按种子随机抽的 4 位小写字母加数字,按它排序后的顺序与交付顺序无关。图片写入时去掉文件元数据。构建完不再修改;要改就做一次新构建。
|
||||
|
||||
### 标准答案库(`answers/<构建号>/`)
|
||||
|
||||
@@ -241,8 +242,8 @@ X 是超声报告工作簿的「推注开始时间s」,含义是视频第 0
|
||||
|
||||
| 文件 | 给谁用 | 内容 |
|
||||
| --- | --- | --- |
|
||||
| `build.yaml` | `scripts/build.py` | 交付包位置、两个库的输出位置、化名与打乱顺序的随机种子、编码解码开关与映射表位置 |
|
||||
| `code_maps.yaml` | build(经 `build.yaml` 引用) | 编码映射表:交付包里每个用数字编码的列,每个数字代表什么(如性别的 1、2) |
|
||||
| `build.yaml` | `scripts/build.py` | `delivery_dir`(交付包位置)、`library_root`、`answers_root`(两个库的上级目录)、`alias_seed`(生成化名的随机种子)、`decode_codes`(编码解码开关)、`code_maps`(映射表位置或 null);字段定义和校验在 `src/cadx/build/_config.py`,逐项说明见 `../guides/装配两个库.md` |
|
||||
| `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` | 评价的指标与统计设置 |
|
||||
@@ -259,11 +260,16 @@ X 是超声报告工作簿的「推注开始时间s」,含义是视频第 0
|
||||
|
||||
文件格式:数组用 npz(读取时 `allow_pickle=False`,pickle 读取时会执行任意代码),每例的标量和摘要用 json,跨病例的表用 csv,图用 png。缺失值写 null,不写 NaN 或 0:0 是合法的测量值,NaN 在 json 里不合法。
|
||||
|
||||
导入边界用 import-linter 检查,写进 `make check`。契约在 `pyproject.toml`,检查范围是 `src/cadx`,**不含 `scripts/` 和 `tests/`**。下面只列契约实际强制的,没有的保护不写:
|
||||
导入边界用 import-linter 检查,写进 `make check`。契约在 `pyproject.toml`,检查范围是 `src/cadx`,**不含 `scripts/` 和 `tests/`**。下面只列契约实际强制的(共 6 条),没有的保护不写:
|
||||
|
||||
- `cadx` 不引用 `torch`、`sam2`。
|
||||
- delivery、library、answers、runs 互不引用。
|
||||
- delivery、library、answers、runs 不引用其他 `cadx` 模块(build 和规划中的各模块)。
|
||||
- build 不引用 delivery、library、answers、runs 以外的 `cadx` 模块(ceus 落地时放行)。
|
||||
- 只有 delivery 引用 `openpyxl`(交付包的 Excel 只有它碰)。
|
||||
- 只有 library 引用 `PIL`(写资料库的图片时去掉元数据)。
|
||||
|
||||
第三部分的依赖表和下面几条外部库独占,随模块落地逐条写成契约(import-linter 只能检查已经存在的模块):只有 delivery 引用 `openpyxl`;只有 library 引用 `PIL`(写资料库的图片时去掉元数据);只有 segment 引用 `torch`、`sam2`;只有 llm 引用 `polygateway`;只有 ledger 引用 `sqlite3`。
|
||||
第三部分依赖表的其余部分和下面几条外部库独占,随模块落地逐条写成契约(import-linter 只能检查已经存在的模块):只有 segment 引用 `torch`、`sam2`(落地时把上面第一条改成放行 segment);只有 llm 引用 `polygateway`;只有 ledger 引用 `sqlite3`。
|
||||
|
||||
契约管不到的,不要当成机器守住了:
|
||||
|
||||
@@ -283,17 +289,17 @@ Python 环境用 uv;安装与检查命令见 `../guides/开发环境.md`。
|
||||
| --- | --- | --- | --- |
|
||||
| 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 | — | — |
|
||||
| delivery | 400–700 | 733 | `clinical.py` 266、`modalities.py` 522、`workbook.py` 99、`delivery.py` 134,共 1021,其中入库与数据库部分不迁 |
|
||||
| library | 150–300 | 300 | `input_packages.py` 104 |
|
||||
| answers | 100–200 | 201 | — |
|
||||
| verdicts | 100–200 | — | — |
|
||||
| build | 300–500 | — | `input_readers.py` 329 的装配部分 |
|
||||
| build | 300–500 | 307 | `input_readers.py` 329 的装配部分 |
|
||||
| llm | 200–450 | — | ClinDB `llm/` 415 |
|
||||
| ledger | 200–300 | — | ClinDB `ledger/` 274 |
|
||||
| runs | 100–250 | — | — |
|
||||
| runs | 100–250 | 86(只含快照部分) | — |
|
||||
| consult | 500–1000(会诊口径定后重估) | — | — |
|
||||
| evaluate | 300–600(评价口径定后重估) | — | 已删的 `core/eval` 622 |
|
||||
| scripts/ 每个 | ≤ 300 | — | `t1_local_debug.py` 762、`tic_lognormal_replay.py` 627 |
|
||||
| scripts/ 每个 | ≤ 300 | `build.py` 37、`scan_library.py` 88 | `t1_local_debug.py` 762、`tic_lognormal_replay.py` 627 |
|
||||
|
||||
---
|
||||
|
||||
@@ -311,4 +317,54 @@ Python 环境用 uv;安装与检查命令见 `../guides/开发环境.md`。
|
||||
|
||||
## 八、各模块对外的函数与类型
|
||||
|
||||
写到哪个模块时,再把它对外的函数和类型补在这里。
|
||||
写到哪个模块时,再把它对外的函数和类型补在这里。以各子包 `__init__.py` 导出的为准;参数和字段的细节看代码的 docstring。
|
||||
|
||||
### delivery(`src/cadx/delivery/`)
|
||||
|
||||
- `read_delivery(delivery_dir, code_maps)`:读交付包,做整张表的检查 T1–T4 和逐例检查 C01–C13,交出 `DeliveryResult`;`code_maps` 为 None 表示不解码。
|
||||
- `DeliveryResult`:一次读取的结果:通过的病例(按病例号排序)、跳过记录、病例总数、认不出编号的行数、因没有映射表而没比对的项。
|
||||
- `CaseRecord`:一个检查全部通过的病例:三位病例号、视频号、临床一路的 22 列、超声描述、MR 描述、关键图路径、三张表的诊断编码。
|
||||
- `DiagnosisCodes`:一例在临床表、超声表、MR 表里各自的诊断编码(C10 保证三者相等)。
|
||||
- `SkipRecord`:一条跳过记录:病例号(认不出编号时是「文件 第 N 行」或「病例目录 名字」)、检查编号、检查说明、涉及的文件和列、涉及值的 JSON 字符串。
|
||||
- `DeliveryError`:整张表读不了(T1–T4)时抛出,消息以检查编号开头,整次构建停下。
|
||||
- `CODED_COLUMNS`:用数字编码、需要映射表才知道含义的 17 项列名,映射表必须恰好覆盖它们。
|
||||
- `CodeMaps`:映射表的类型,「列名 → {数字 → 含义}」。
|
||||
- `SKIPPED_NOTE`:跳过清单的说明句(每例只记第一项没过的检查),写进答案库清单并在终端打印。
|
||||
- `review_sentences(text)`:报告文字里含「考虑」「可能」「倾向」「提示」的句子,每句一条(命中的词,句子),用来生成复核清单。
|
||||
|
||||
### library(`src/cadx/library/`)
|
||||
|
||||
- `write_library(build_dir, build_id, git_commit, git_clean, config_sha256, codes_decoded, uncompared, ceus_source, cases)`:把一次构建的各例三路内容写进一个已存在的空目录,关键图去掉附加数据重新编码,最后写 `manifest.json`。
|
||||
- `LibraryCase`:要写进资料库的一例:化名、临床一路的字段、超声描述、关键图源文件、MR 描述。
|
||||
- `list_aliases(build_dir)`:一次构建的全部化名(按化名排序)。
|
||||
- `read_clinical(build_dir, alias)`、`read_ultrasound(build_dir, alias)`、`read_mr(build_dir, alias)`:按路读出一例,读时核对 SHA-256,拒绝 `.partial` 目录。
|
||||
- `ClinicalRoute`:临床一路:是否已解码、22 列字段。
|
||||
- `UltrasoundRoute`:超声一路:报告「所见」原文、关键图路径。
|
||||
- `MrRoute`:MR 一路:报告「所见」原文。
|
||||
- `scan_for_leaks(build_dir, forbidden_strings, forbidden_keys)`:在构建目录的全部 `.txt`、`.json` 里找禁止的字符串(NFKC 归一、不分大小写、按子串)和禁止的 JSON 键名,返回命中列表。
|
||||
- `LeakHit`:一处命中:文件(相对构建目录)、类别(「字符串」或「键名」)、命中的词。
|
||||
|
||||
### answers(`src/cadx/answers/`)
|
||||
|
||||
- `write_answers(answers_dir, build_id, skipped_note, aliases, diagnoses, skipped, reviews)`:写 `aliases.csv`、`diagnoses.csv`、`skipped.csv`、`review.csv`,再给目录里全部文件(含 `snapshot/`)算 SHA-256 写进 `manifest.json`。
|
||||
- `AliasRow`:化名、三位病例号、视频号。
|
||||
- `DiagnosisRow`:化名、病例号、三张表的诊断编码、诊断名(不解码时为 None)。
|
||||
- `SkippedRow`:跳过清单的一行,字段同 `SkipRecord`。
|
||||
- `ReviewRow`:复核清单的一行:化名、路(「超声」或「MR」)、命中的词、所在句子。
|
||||
- `read_aliases(answers_dir)`、`read_diagnoses(answers_dir)`:评价用的读取,读时核对 SHA-256 和表头,拒绝 `.partial` 目录。
|
||||
|
||||
### runs(`src/cadx/runs/`,目前只有快照部分)
|
||||
|
||||
- `read_git_state(repo_root)`:读出完整和短提交号、工作区是否干净(含未跟踪文件)、`git diff HEAD` 输出的 SHA-256。
|
||||
- `GitState`:上面这四项。
|
||||
- `make_build_id(now, git)`:构建号 `YYYYMMDD-HHMMSS-<git 短哈希>`,工作区不干净时末尾加 `-dirty`。
|
||||
- `write_snapshot(snapshot_dir, build_id, git, started_at, finished_at, uv_lock, configs)`:新建快照目录,复制配置原文并写 `snapshot.json`(构建号、git 状态、Python 版本、`uv.lock` 哈希、起止时间、各配置的哈希)。
|
||||
|
||||
### build(`src/cadx/build/`)
|
||||
|
||||
- `run_build(config_path, repo_root, now)`:完整做一次构建:读配置和映射表、读交付包、取化名、先写进 `.partial` 目录再改名,交出 `BuildSummary`。
|
||||
- `BuildSummary`:供终端打印的计数:构建号、两个库的路径、病例总数、进库数、跳过数、认不出编号的行数、按检查编号分的跳过数、复核清单条数、跳过清单说明句。
|
||||
- `BuildConfig`:`build.yaml` 的字段定义(pydantic,字段必填、不许多写、读入后不可改)。
|
||||
- `load_build_config(path, repo_root)`:读并校验 `build.yaml`,相对路径按仓库根目录解析。
|
||||
- `load_code_maps(path)`:读映射表并做 T5(恰好覆盖 17 项,性别映射到「男」「女」,诊断映射到四个亚型缩写)。
|
||||
- `make_aliases(case_nos, seed)`:病例号排序后依次抽 4 位化名,同种子同病例得到同样的化名。
|
||||
|
||||
Reference in New Issue
Block a user