Files
CADx-4/scripts/scan_library.py
T
iomgaa bceb01e8fa 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>
2026-10-16 03:00:20 -04:00

89 lines
3.1 KiB
Python
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.
"""泄露扫描:查一次构建的资料库里有没有真实病例号、交付文件名、亚型名、诊断列名。
用法:uv run python scripts/scan_library.py --library library/<构建号> --answers answers/<构建号>
病例号和视频号从答案库读;亚型名、列名、文件名的词表在本脚本里独立写,不取自 delivery。
只输出命中的条数和类别,不输出命中的内容。这个脚本同时读两个库,只能放在 scripts/。
"""
import argparse
from collections import Counter
from pathlib import Path
from cadx.answers import read_aliases
from cadx.library import scan_for_leaks
SUBTYPE_NAMES = (
"IPMN",
"IPMT",
"SPN",
"SPT",
"SPTP",
"MCN",
"SCN",
"导管内乳头状黏液性肿瘤",
"导管内乳头状",
"导管内乳头",
"实性假乳头状",
"实性假乳头",
"黏液性囊性肿瘤",
"黏液性囊",
"粘液性囊",
"黏液囊性",
"粘液囊性",
"浆液性囊性肿瘤",
"浆液性囊",
"浆液性微囊",
"囊腺瘤",
)
COLUMN_NAMES = (
"Case_ID",
"Diagnosis",
"病理类型",
"病理类型(1 IPMN,2 SPN,3 MCN,4 SCN)",
"患者ID",
"CEUS视频ID",
"推注开始时间",
)
DELIVERY_FILE_NAMES = (
"临床工作簿_40例.xlsx",
"超声报告文本_40例.xlsx",
"MR报告文本_40例.xlsx",
"Clinical_agent",
"CEUS_agent",
"MR_agent",
)
FORBIDDEN_KEYS = ("diagnosis", "label", "gold", "pathology", "name", "row")
def main() -> None:
parser = argparse.ArgumentParser(description="扫描资料库里的泄露")
parser.add_argument("--library", type=Path, required=True, help="资料库的构建目录")
parser.add_argument("--answers", type=Path, required=True, help="同一构建号的答案库目录")
args = parser.parse_args()
library: Path = args.library
answers: Path = args.answers
if library.name != answers.name:
raise SystemExit(f"两个目录的构建号不同:{library.name} 与 {answers.name}")
categories: dict[str, str] = {}
for row in read_aliases(answers):
categories[f"P{row.case_no}"] = "病例号"
categories[row.video_id] = "视频号"
for suffix in ("_key.png", "_roi2.mp4", ".mp4", "_roi2_report.xlsx", "_report.xlsx"):
categories[f"{row.video_id}{suffix}"] = "交付文件名"
categories.update(dict.fromkeys(DELIVERY_FILE_NAMES, "交付文件名"))
categories.update(dict.fromkeys(SUBTYPE_NAMES, "亚型名"))
categories.update(dict.fromkeys(COLUMN_NAMES, "列名"))
hits = scan_for_leaks(library, list(categories), FORBIDDEN_KEYS)
counts = Counter("键名" if hit.kind == "键名" else categories[hit.term] for hit in hits)
print(f"扫描的病例数:{len(read_aliases(answers))}")
print(f"禁止的字符串:{len(categories)} 个;禁止的键名:{len(FORBIDDEN_KEYS)} 个")
print(f"命中总数:{len(hits)}")
for category in ("病例号", "视频号", "交付文件名", "亚型名", "列名", "键名"):
print(f" {category}:{counts[category]}")
if __name__ == "__main__":
main()