#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 辅行诀五脏用药法要-药性探真 资料库 ETL 第 9 步(2026-09-17):**归档一致性审计** 用途:任意时刻复核「文档里引用的 md5 / 文件是否存在 / 有无残留」是否与库内实际一致。 检查项: ① 全库 md5 索引(排除 . 与 __pycache__) ② 关键文档中出现的完整 md5(32 位)与缩写 md5(前 8 位 + …)能否对应到现有文件 ③ 若为历史值(确属旧版本留痕),输出 ⚠ 提示但不算错;无法对应且非历史值者列 ❌ ④ 残留检查:__pycache__、.bak/.tmp/~ 备份、空目录 只读,不修改任何文件;退出码:有 ❌ 时返回 1,便于脚本化校验。 """ import os, re, sys, hashlib, glob BASE = os.path.expanduser("~/Documents/ai_agent_scraper_study/data/辅行诀五脏用药法要-药性探真") DOCS = ["README.md", "01_来源数据/来源说明.md", "01_来源数据/校核参考/README.md", "02_加工数据/药性探真_第1章_检查报告.md", "02_加工数据/药性探真_第1章_待核对清单.md", "02_加工数据/药性探真_第2章_检查报告.md", "02_加工数据/药性探真_第2章_待核对清单.md", "02_加工数据/药性探真_第3章_检查报告.md", "02_加工数据/药性探真_第3章_待核对清单.md", "02_加工数据/药性探真_第1-3章_合并结构.json", "02_加工数据/整订稿清洗报告.md", "02_加工数据/印刷本清洗报告.md", "02_加工数据/文本清洗报告.md", "02_加工数据/原文清洗报告.md", "02_加工数据/人工核对清单.md", "02_加工数据/印刷本人工核对清单.md", "03_关联融合/README.md", "03_关联融合/整订稿核对报告.md", "03_关联融合/整订稿-底本_方目对照表.md", "03_关联融合/药性探真-底本_药名方目对照表.md", "03_关联融合/药性探真-底本_核对报告.md", "03_关联融合/药性探真-底本_药名方目对照.json", "04_分析报告/README.md"] # 已知历史值(旧版本留痕,出现即视为正常) KNOWN_HISTORIC = { "ce800838b94512a00fbe8375570eb180", # 药性探真_第一章_清洗版.md 旧 md5(2026-09-17 重跑前) "e550bb651e026fb3a314a90faaa9937e", # 底本基础文本旧 md5(人工核验回填前) "0cea9938547ac8c1a0e9479cbda15e47", # 《…》第1章.md 标题层级优化前(快照现存库内,故通常能直接命中) "29097b725de0e3e5829a5c0acb132074", # 第2章清洗版(「拟正字」6 条 11 处订正前;README 更新日志(五)全值留痕) } # 旧值缩写(更新日志里刻意留痕的「改前 → 改后」写法,出现即视为正常) HISTORIC_PREFIX = {"5642e2d0", "ce800838", "e550bb65", "65c63d90", "978d57c9", # 第2章 检查报告/待核对清单 第1批判决回填时值 "52f79d41", "d76d710d", # 第2章 检查报告/待核对清单 第2批判决回填时值 "44eeca76", # 第2章 结构.json 第2批判决回填时值 "8a377e1e", # 第2章 清洗版(第1–3批判决期间值,第4批据核改后变更) "cd754c58", "45de11c7", "ecf80e86", # 第2章 报告/清单/结构 第3批判决回填时值 "e490eb85", "234bd0a3", "12d75c0e", # 第2章 报告/清单/脚本 第4批判决回填时值 "859450c5", "d64de6e5", "2b571161", # 第2章 报告/清单/脚本 「保留」归一(词表)时值 "3511f9b3", "20a2d8c2", # 第2章 脚本 7.x 重排/待补正字标记中间值 "29097b72", "c51ad6da", "8d6e4e24", # 第2章 清洗版/报告/清单 「拟正字」落盘前时值 "84ab1c43", "b435e320", # 第2章 结构/脚本 同期时值 "bee344b7", "ff367101", "93e003f8", # 第2章 报告/结构/脚本 行号更正前时值 "433a6a0a", "b0c7ff33", "db0b6227", # 第2章 报告/结构/脚本 「原书用字」栏补入前时值 "347aecce", "a0094713", # 第1章 报告/结构 引文异文登记前时值 "09087b8e", "b5d23ecc", "8926e4fe", "196f3678", # 第2章 报告/清单/结构/脚本 层级优化前时值 "716e4c56", "4acc6455", # 第1章 报告/结构 引文异文登记(待核)时值 "d838a537", # 07_clean_ch1.py 引文登记(待核)时值 "3e4f50b3", "c970c608", "ed85e822", # 第3章 报告/结构/脚本 判决回填前时值(更新日志(十三)留痕) "2c5378bd", "c18175f8", "575f94a7", "6e01d28e", "bcd90aa3", # 第3章 第2批判决前时值((十四)(十五) 留痕) "e68c05cb", "05cdbbf5", "9e4b2aad", "7833650f", "b0783fb3", # 第3章 第3批判决前时值((十五)(十六) 留痕) "194a913c", "86fb7658", "3fd5855b", "61c2fea3", "42c16ad4", "71d6ac95", "e542d14c", # 第3章 报告/脚本 中间时值((十七)留痕) "b221786e", "391a6454", "8cf5aecb", "dcd37b8a", "ac7d8774", "f191685e", "ac511120", # 第3章 清单/脚本 中间时值((十八)留痕) # 第3章 第5批判决前时值((十七)(十八) 留痕) # 第3章 第4批判决前时值((十六)(十七) 留痕) "72482eed", "af0e2cdb", "74cb970a", # 第3章 清单/结构/脚本 存疑补录前时值(更新日志(十三)前中间值) "f9611535", "8b40d02f", "672e2133", "48752a23", # 第3章 报告/脚本 行标与体例精简中间值 "c406a03f", "70b00b0f", "76063838", "97a7b47a", # 第3章 报告/清单/结构/脚本 第 6 批判决(#56)前时值(更新日志(十八)「新值」→(二十)「改前」) "04919121", "7988ddb9", "8b3ce170", "32399f74", # 第3章 报告/清单/结构/脚本 第 7 批(源件层级优化等)前时值(更新日志(二十)「新值」→(二十一)「改前」) } def md5_of(p): return hashlib.md5(open(p, "rb").read()).hexdigest() def main(): files = {} for root, dirs, fs in os.walk(BASE): dirs[:] = [d for d in dirs if not d.startswith((".", "__"))] for f in fs: p = os.path.join(root, f) files[os.path.relpath(p, BASE)] = md5_of(p) full = {v: k for k, v in files.items()} pref = {} for k, v in files.items(): pref.setdefault(v[:8], []).append(k) print(f"库内文件 {len(files)} 个\n") bad = 0 for d in DOCS: p = os.path.join(BASE, d) if not os.path.exists(p): print(f" ❌ 文档缺失:{d}") bad += 1 continue txt = open(p, encoding="utf-8").read() for m in sorted(set(re.findall(r"\b[0-9a-f]{32}\b", txt))): if m in full: print(f" ✅ {d[:34]:<36} {m[:12]}… → {full[m]}") elif m in KNOWN_HISTORIC: print(f" ⚠ {d[:34]:<36} {m[:12]}… → 历史值(旧版本留痕,正常)") else: print(f" ❌ {d[:34]:<36} {m[:12]}… → 库内无此 md5") bad += 1 for m in sorted(set(re.findall(r"`([0-9a-f]{8})…`", txt))): if m in pref: print(f" ✅ {d[:34]:<36} {m}… → {'、'.join(pref[m])}") elif m in HISTORIC_PREFIX: print(f" ⚠ {d[:34]:<36} {m}… → 历史值缩写(更新日志「改前」留痕,正常)") else: print(f" ❌ {d[:34]:<36} {m}… → 库内无此文件(缩写)") bad += 1 print("\n残留检查:") pyc = glob.glob(os.path.join(BASE, "**/__pycache__"), recursive=True) tmp = [f for f in files if f.endswith((".bak", ".tmp", "~"))] empt = [os.path.relpath(r, BASE) for r, ds, fs in os.walk(BASE) if not ds and not fs and not r.endswith((".git",))] print(f" __pycache__: {pyc or '无'}") print(f" 备份/临时文件: {tmp or '无'}") print(f" 空目录: {empt or '无'}") print(f"\n审计结果:{'通过(无 ❌)' if bad == 0 else f'有 {bad} 处 ❌ 需修正'}") return 0 if bad == 0 else 1 if __name__ == "__main__": sys.exit(main())