bceb01e8fa
按任务书 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>
89 lines
3.1 KiB
Python
89 lines
3.1 KiB
Python
"""泄露扫描:查一次构建的资料库里有没有真实病例号、交付文件名、亚型名、诊断列名。
|
||
|
||
用法: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()
|