feat(review): 跳过病例表只进快照;规模表 review 行注明已接受

负责人 2026-10-17:跳过病例.csv 不放进医生目录,只留在 snapshot/,病例总表和问题清单已用大白话说了同样的内容;review 行数超预期一倍,原因对照表本来就该这么长,接受。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 10:33:10 -04:00
parent 35e77777e3
commit 9d82210f31
5 changed files with 23 additions and 14 deletions
+4 -4
View File
@@ -206,7 +206,7 @@ library/<构建号>/
- 病理诊断:三张表的诊断列各存一份,用哪一份算分由评价决定; - 病理诊断:三张表的诊断列各存一份,用哪一份算分由评价决定;
- 化名对照:化名、真实病例号、CEUS 视频号。会诊产物只用化名,评价靠这张表接回真实病例; - 化名对照:化名、真实病例号、CEUS 视频号。会诊产物只用化名,评价靠这张表接回真实病例;
- 跳过清单:没进资料库的病例、没过的那一项检查、涉及的文件和列,以及对不上的各表原值,供医生核对。40 例里已知有 6 例临床表与超声表对不上(`../待定问题/04-交付数据的跨表不一致.md`),会出现在这份清单里; - 跳过清单:没进资料库的病例、没过的那一项检查、涉及的文件和列,以及对不上的各表原值,供医生核对(核对包里由病例总表和问题清单写成医生能懂的话,原表只进核对包的快照)。40 例里已知有 6 例临床表与超声表对不上(`../待定问题/04-交付数据的跨表不一致.md`),会出现在这份清单里;
- 将来加医生标注工作簿。 - 将来加医生标注工作簿。
### 医生核对包(`runs/review_pack/<运行号>/`) ### 医生核对包(`runs/review_pack/<运行号>/`)
@@ -215,10 +215,10 @@ library/<构建号>/
``` ```
runs/review_pack/<运行号>/ runs/review_pack/<运行号>/
snapshot/ 快照,含程序原文的 问题清单_原文.csv(不给医生) snapshot/ 快照,含程序原文的 问题清单_原文.csv 和答案库跳过清单的拷贝 跳过病例.csv(不给医生)
医生核对包_<构建号>/ 医生核对包_<构建号>/
说明.pdf 问题清单.csv 病例总表.csv 说明.pdf 问题清单.csv 病例总表.csv
标准答案/ 答案库 4 张表原样拷贝 标准答案/ 答案库的化名对照、诊断、报告复核句 3 张表原样拷贝
病例/<病例号>/ 临床信息.csv 超声报告.pdf MR报告.pdf 病例/<病例号>/ 临床信息.csv 超声报告.pdf MR报告.pdf
关键帧/医生截图.png、<槽位>_第<秒>秒.png 关键帧/医生截图.png、<槽位>_第<秒>秒.png
TIC曲线.png TIC参数.csv 回放视频.mp4 TIC曲线.png TIC参数.csv 回放视频.mp4
@@ -350,7 +350,7 @@ Python 环境用 uv;安装与检查命令见 `../guides/开发环境.md`。
| runs | 100–250 | 201(快照 `_snapshot.py` 98、读 CEUS 运行的 `_ceus_run.py` 82、`__init__.py` 21) | — | | runs | 100–250 | 201(快照 `_snapshot.py` 98、读 CEUS 运行的 `_ceus_run.py` 82、`__init__.py` 21) | — |
| consult | 500–1000(会诊口径定后重估) | — | — | | consult | 500–1000(会诊口径定后重估) | — | — |
| evaluate | 300–600(评价口径定后重估) | — | 已删的 `core/eval` 622 | | evaluate | 300–600(评价口径定后重估) | — | 已删的 `core/eval` 622 |
| review | 300–500 | 1152(`_pack.py` 447、`_problems.py` 502、`_pdf.py` 129、`_config.py` 64、`__init__.py` 10;不含说明模板 `readme_template.md` 91 行)。超出一倍:任务书 04 要求的原因对照表(`_problems.py`)大半是逐条的原因与医生写法,待负责人确认是否接受 | — | | review | 300–500 | 1152(`_pack.py` 447、`_problems.py` 502、`_pdf.py` 129、`_config.py` 64、`__init__.py` 10;不含说明模板 `readme_template.md` 91 行)。超出一倍,负责人 2026-10-17 已接受:原因对照表(`_problems.py`)要逐条覆盖全部失败原因,本来就该这么长 | — |
| scripts/ 每个 | ≤ 300 | `build.py` 37、`scan_library.py` 88、`ceus_quant.py` 403(超出,含目视核对表的读取与核对、开跑即写快照)、`ceus_align_review.py` 243、`review_pack.py` 37 | `t1_local_debug.py` 762、`tic_lognormal_replay.py` 627 | | scripts/ 每个 | ≤ 300 | `build.py` 37、`scan_library.py` 88、`ceus_quant.py` 403(超出,含目视核对表的读取与核对、开跑即写快照)、`ceus_align_review.py` 243、`review_pack.py` 37 | `t1_local_debug.py` 762、`tic_lognormal_replay.py` 627 |
--- ---
+2 -1
View File
@@ -91,6 +91,7 @@ runs/review_pack/<出包号>/ (output_root 下)
- `review_pack.yaml`:配置原文副本; - `review_pack.yaml`:配置原文副本;
- `review.json`:构建号、CEUS 运行号、pandoc 与 soffice 的版本行; - `review.json`:构建号、CEUS 运行号、pandoc 与 soffice 的版本行;
- `问题清单_原文.csv`:问题清单的程序原文(检查编号、原因码、程序记的原因),列同包里的问题清单,给我们自己追溯; - `问题清单_原文.csv`:问题清单的程序原文(检查编号、原因码、程序记的原因),列同包里的问题清单,给我们自己追溯;
- `跳过病例.csv`:答案库跳过清单 `skipped.csv` 的原样拷贝。它是程序原文,病例总表和问题清单已用大白话写了同样的内容,所以不进医生的目录(负责人 2026-10-17);
- `finished.json`:结束时间、病例数、问题清单行数。 - `finished.json`:结束时间、病例数、问题清单行数。
- `医生核对包_<构建号>/`: - `医生核对包_<构建号>/`:
@@ -98,7 +99,7 @@ runs/review_pack/<出包号>/ (output_root 下)
说明.pdf 给医生的说明,源文件是 src/cadx/review/readme_template.md 说明.pdf 给医生的说明,源文件是 src/cadx/review/readme_template.md
问题清单.csv 病例号、视频号、环节、问题(一句话) 问题清单.csv 病例号、视频号、环节、问题(一句话)
病例总表.csv 全部病例一例一行 病例总表.csv 全部病例一例一行
标准答案/ 化名对照.csv、诊断.csv、跳过病例.csv、报告复核句.csv(答案库原样拷贝) 标准答案/ 化名对照.csv、诊断.csv、报告复核句.csv(答案库原样拷贝)
病例/<病例号>/ 只有进了资料库的病例才有 病例/<病例号>/ 只有进了资料库的病例才有
临床信息.csv 临床一路的字段 临床信息.csv 临床一路的字段
超声报告.pdf、MR报告.pdf 两路报告的描述段原文 超声报告.pdf、MR报告.pdf 两路报告的描述段原文
+6 -3
View File
@@ -2,10 +2,10 @@
``` ```
<output_root>/<运行号>/ <output_root>/<运行号>/
snapshot/ 快照(不给医生),含程序原文的 问题清单_原文.csv snapshot/ 快照(不给医生),含程序原文的 问题清单_原文.csv、跳过病例.csv
医生核对包_<构建号>/ 医生核对包_<构建号>/
说明.pdf 问题清单.csv 病例总表.csv 说明.pdf 问题清单.csv 病例总表.csv
标准答案/化名对照.csv 诊断.csv 跳过病例.csv 报告复核句.csv 标准答案/化名对照.csv 诊断.csv 报告复核句.csv
病例/<病例号>/临床信息.csv 超声报告.pdf MR报告.pdf 病例/<病例号>/临床信息.csv 超声报告.pdf MR报告.pdf
关键帧/医生截图.png <槽位>_第<秒>秒.png 关键帧/医生截图.png <槽位>_第<秒>秒.png
TIC曲线.png TIC参数.csv 回放视频.mp4 TIC曲线.png TIC参数.csv 回放视频.mp4
@@ -66,9 +66,11 @@ RAW_PROBLEMS = "问题清单_原文.csv"
ANSWER_TABLES = { ANSWER_TABLES = {
"aliases.csv": "化名对照.csv", "aliases.csv": "化名对照.csv",
"diagnoses.csv": "诊断.csv", "diagnoses.csv": "诊断.csv",
"skipped.csv": "跳过病例.csv",
"review.csv": "报告复核句.csv", "review.csv": "报告复核句.csv",
} }
# 跳过清单是程序原文(检查编号、交付路径),病例总表和问题清单已用大白话写了同样的内容,
# 所以只原样拷进快照,不进医生目录(负责人 2026-10-17)。
SKIPPED_TABLE = ("skipped.csv", "跳过病例.csv")
# TIC参数.csv 的两组(tic.json 里 perfusion_metrics 的键,按列的顺序)和 11 行: # TIC参数.csv 的两组(tic.json 里 perfusion_metrics 的键,按列的顺序)和 11 行:
# tic.json 里的键、中文名加缩写、单位。时间类(单位秒)保留 1 位小数,其余 2 位。 # tic.json 里的键、中文名加缩写、单位。时间类(单位秒)保留 1 位小数,其余 2 位。
TIC_GROUPS = ("roi_pan", "roi_perf") TIC_GROUPS = ("roi_pan", "roi_perf")
@@ -348,6 +350,7 @@ def run_review_pack(config_path: Path, repo_root: Path, now: datetime) -> PackSu
(pack / "标准答案").mkdir(parents=True) (pack / "标准答案").mkdir(parents=True)
for name, target in ANSWER_TABLES.items(): for name, target in ANSWER_TABLES.items():
_copy(verified_file(answers, name), pack / "标准答案" / target) _copy(verified_file(answers, name), pack / "标准答案" / target)
_copy(verified_file(answers, SKIPPED_TABLE[0]), snapshot / SKIPPED_TABLE[1])
# failed.csv:病例号(三位数字)→(视频号,第几步,原因原文)。 # failed.csv:病例号(三位数字)→(视频号,第几步,原因原文)。
failures: dict[str, tuple[str, int, str]] = {} failures: dict[str, tuple[str, int, str]] = {}
+1 -2
View File
@@ -10,7 +10,7 @@ $ceus_note
## 一、包里有什么 ## 一、包里有什么
- **说明.pdf**(这份说明)、**问题清单.csv**、**病例总表.csv**、**标准答案/**(4 张表):几张表见第六节。 - **说明.pdf**(这份说明)、**问题清单.csv**、**病例总表.csv**、**标准答案/**(3 张表):几张表见第六节。
- **病例/**:每个进了资料库的病例一个文件夹,名字是病例号(如 P001)。打开能看到: - **病例/**:每个进了资料库的病例一个文件夹,名字是病例号(如 P001)。打开能看到:
- **临床信息.csv**:临床那位 AI 专家看到的临床信息,列名沿用你们临床工作簿的原列名。$codes_note - **临床信息.csv**:临床那位 AI 专家看到的临床信息,列名沿用你们临床工作簿的原列名。$codes_note
- **超声报告.pdf**、**MR报告.pdf**:报告里的描述部分,一字未改。结论(印象)部分写着诊断倾向,没有给 AI 专家,这里也就没有。 - **超声报告.pdf**、**MR报告.pdf**:报告里的描述部分,一字未改。结论(印象)部分写着诊断倾向,没有给 AI 专家,这里也就没有。
@@ -87,5 +87,4 @@ TIC参数.csv 每行一个参数,两组数并排:**胰腺实质(紫圈)*
- **病例总表.csv**:全部病例一例一行:进没进资料库、没进的原因、造影视频处理成功没有(没成功时写停在第几步)、7 个关键帧各在视频第几秒。 - **病例总表.csv**:全部病例一例一行:进没进资料库、没进的原因、造影视频处理成功没有(没成功时写停在第几步)、7 个关键帧各在视频第几秒。
- **标准答案/化名对照.csv**:资料库里代替病例号的化名与病例号的对照。 - **标准答案/化名对照.csv**:资料库里代替病例号的化名与病例号的对照。
- **标准答案/诊断.csv**:三张表的病理诊断编码(1 IPMN,2 SPN,3 MCN,4 SCN)。 - **标准答案/诊断.csv**:三张表的病理诊断编码(1 IPMN,2 SPN,3 MCN,4 SCN)。
- **标准答案/跳过病例.csv**:内容和问题清单里没进资料库的那几条一样,是程序原文版本,一般不用看。
- **标准答案/报告复核句.csv**:报告描述里含「考虑」「可能」「倾向」「提示」的句子。这些句子目前仍留在给 AI 专家的报告里,想请你们看看它们算不算带出了诊断倾向、该不该删掉。 - **标准答案/报告复核句.csv**:报告描述里含「考虑」「可能」「倾向」「提示」的句子。这些句子目前仍留在给 AI 专家的报告里,想请你们看看它们算不算带出了诊断倾向、该不该删掉。
+10 -4
View File
@@ -89,7 +89,6 @@ BLANK = [""] * 11
ANSWER_HEADERS = { ANSWER_HEADERS = {
"化名对照.csv": ["化名", "病例号", "视频号"], "化名对照.csv": ["化名", "病例号", "视频号"],
"诊断.csv": ["化名", "病例号", "临床表诊断编码", "超声表诊断编码", "MR表诊断编码", "诊断名"], "诊断.csv": ["化名", "病例号", "临床表诊断编码", "超声表诊断编码", "MR表诊断编码", "诊断名"],
"跳过病例.csv": ["病例", "检查编号", "检查说明", "文件", "列", "涉及值"],
"报告复核句.csv": ["化名", "路", "命中的词", "所在句子"], "报告复核句.csv": ["化名", "路", "命中的词", "所在句子"],
} }
CASE_FILES = {"临床信息.csv", "超声报告.pdf", "MR报告.pdf", "关键帧"} CASE_FILES = {"临床信息.csv", "超声报告.pdf", "MR报告.pdf", "关键帧"}
@@ -281,7 +280,7 @@ def test_directory_layout(packed: Packed) -> None:
def test_every_csv_has_bom_and_no_placeholder_for_missing(packed: Packed) -> None: def test_every_csv_has_bom_and_no_placeholder_for_missing(packed: Packed) -> None:
csvs = sorted(packed.pack.rglob("*.csv")) csvs = sorted(packed.pack.rglob("*.csv"))
assert len(csvs) == 2 + 4 + 4 * 2 assert len(csvs) == 2 + 3 + 4 * 2
for path in csvs: for path in csvs:
for row in _csv(path): for row in _csv(path):
assert not {"null", "None", "NaN", "nan"} & set(row), path assert not {"null", "None", "NaN", "nan"} & set(row), path
@@ -435,7 +434,7 @@ def test_raw_problem_list_in_snapshot(packed: Packed) -> None:
"""程序原文照任务书 03 的写法留在快照里,不进医生的目录。""" """程序原文照任务书 03 的写法留在快照里,不进医生的目录。"""
assert not (packed.pack / "问题清单_原文.csv").exists() assert not (packed.pack / "问题清单_原文.csv").exists()
rows = _csv(packed.result.run_dir / "snapshot" / "问题清单_原文.csv") rows = _csv(packed.result.run_dir / "snapshot" / "问题清单_原文.csv")
skipped = {row[0]: row for row in _csv(packed.pack / "标准答案" / "跳过病例.csv")[1:]} skipped = {row[0]: row for row in _csv(packed.result.run_dir / "snapshot" / "跳过病例.csv")[1:]}
fit_failed = [f"{slot} 未取到:{FIT_FAILED}" for slot in DYNAMIC] fit_failed = [f"{slot} 未取到:{FIT_FAILED}" for slot in DYNAMIC]
p008 = { p008 = {
"B0": "视频开头没有观测到稳定的增强前基线(基线是估计的),B0 不定义", "B0": "视频开头没有观测到稳定的增强前基线(基线是估计的),B0 不定义",
@@ -551,12 +550,19 @@ def test_copies_are_byte_identical(packed: Packed) -> None:
for source, name in ( for source, name in (
("aliases.csv", "化名对照.csv"), ("aliases.csv", "化名对照.csv"),
("diagnoses.csv", "诊断.csv"), ("diagnoses.csv", "诊断.csv"),
("skipped.csv", "跳过病例.csv"),
("review.csv", "报告复核句.csv"), ("review.csv", "报告复核句.csv"),
): ):
assert (packed.pack / "标准答案" / name).read_bytes() == (answers / source).read_bytes() assert (packed.pack / "标准答案" / name).read_bytes() == (answers / source).read_bytes()
def test_skipped_table_only_in_snapshot(packed: Packed) -> None:
"""跳过清单是程序原文:不进医生目录,原样拷进快照(负责人 2026-10-17)。"""
assert not [p for p in packed.pack.rglob("*") if p.name == "跳过病例.csv"]
copied = packed.result.run_dir / "snapshot" / "跳过病例.csv"
assert copied.read_bytes() == (packed.build.answers_dir / "skipped.csv").read_bytes()
assert _csv(copied)[0] == ["病例", "检查编号", "检查说明", "文件", "列", "涉及值"]
def _tool_output(tool: str, *args: str) -> str: def _tool_output(tool: str, *args: str) -> str:
path = shutil.which(tool) path = shutil.which(tool)
assert path is not None, f"找不到 {tool}" assert path is not None, f"找不到 {tool}"