Files

123 lines
8.4 KiB
Python
Executable File
Raw Permalink 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.
#!/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())