初始归档:健康管理资料库(大医网/药膳/体质监测/中医理论等12个资料集,7712个文件)

This commit is contained in:
512song committed 2026-09-23 21:59:25 +08:00
commit 80ae3811cf
7712 files changed
+4628547

No files matched your search

@@ -0,0 +1,321 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
辅行诀五脏用药法要-药性探真 资料库 ETL 第 6 步(2026-09-17):本库内部**逐条对应**
输入:02_加工数据/整订稿_清洗版.md(衣之镖《整订稿》篇目/方目提纲,101 方目)
02_加工数据/辅行诀五脏用药法要_基础文本_清洗版.md(★底本,印刷本)
02_加工数据/印刷本结构_上下卷.json(底本篇/方索引,用作篇界与方目锚)
输出:03_关联融合/整订稿-底本_方目对照表.md
03_关联融合/整订稿-底本_方目对照.json
关联主轴(用户 2026-09-17「核对关联」指令):
主=底本(印刷本清洗版,用户 2026-09-16 定为基础文本);从=整订稿篇目提纲。
粒度=方目条 + 药行(草木药行/金石药行)行号锚定;**不改动任何一方原文**。
对位规则:
① 整订稿「X汤」 ↔ 底本「X汤散」条之**草木药行**(心篇四首无金石对应,底本即作「X汤」)
② 整订稿「X散汤」↔ 底本「X汤散」条之**金石药行**
③ 整订稿「大X汤/大X散汤」(救诸劳损病方篇)↔ 底本无独立方条,系依底本「若欲作大汤散者……」
加法(底本第 409 行)推得,关联至加法条 + 对应小方
④ 论说条(论证N条/论N条):与底本篇内方条之前的论说段落逐条核数
幂等:可重复运行;若底本或整订稿被重跑更新,本脚本按 md5 记录来源版本。
"""
import os, re, json, hashlib
BASE = os.path.expanduser("~/Documents/ai_agent_scraper_study/data/辅行诀五脏用药法要-药性探真")
F_ZD = os.path.join(BASE, "02_加工数据/整订稿_清洗版.md")
F_BASE = os.path.join(BASE, "02_加工数据/辅行诀五脏用药法要_基础文本_清洗版.md")
F_JSON = os.path.join(BASE, "02_加工数据/印刷本结构_上下卷.json")
OUT_MD = os.path.join(BASE, "03_关联融合/整订稿-底本_方目对照表.md")
OUT_JS = os.path.join(BASE, "03_关联融合/整订稿-底本_方目对照.json")
# ── 篇对位表:整订稿篇(清洗版字面)→ 底本篇(结构 JSON 标签)+篇题对位说明 ──
SEC_MAP = {
"辨肝脏病证文并方(论证四条,方八首)": ("上卷·辨肝脏病证文并方", "篇题同名(底本有篇题)", "方"),
"辨心脏病证文并方(论证五条,方四首)": ("上卷·辨心脏病证文并方", "篇题同名(底本有篇题)", "方"),
"又心包病方(论证一条,方八首)": ("上卷·辨心胞病证文(篇题缺)", "底本篇题缺;本提纲作「又心包病方」(字面),可补底本编次", "方"),
"辨脾脏病证文并方(论证四条,方八首)": ("上卷·辨脾脏病证文并方", "篇题同名(底本有篇题)", "方"),
"辨肺脏病证文并方(论证四条,方八首)": ("上卷·辨肺脏病证文并方", "篇题同名(底本有篇题)", "方"),
"辨肾脏病证文并方(论证四条,方八首)": ("上卷·辨肾脏病证文并方", "篇题同名(底本有篇题)", "方"),
"救诸病误治方(论证一首,方二十首)": ("上卷·救误泻五脏方(篇题缺)", "底本篇题缺;本提纲作「救诸病误治方」,底本引文作「又有泻方五首,以救诸病误治」", "方"),
"救诸劳损病方(论证二首,方二十首)": ("下卷·救五脏诸劳损病方(小方五首)", "底本引文作「经方有救诸劳损病方」;本提纲作「救诸劳损病方」", "方"),
"检录伊尹《汤液经法》方(六十一首)": ("下卷·汤液经法(二十五味药精)", "篇级对位(底本只存「今检录常情需用者六十一首」之数,无逐方目,与本提纲同)", "叙"),
"诸药五味五行互含文": ("下卷·汤液经法(二十五味药精条文)", "底本作「今者约列二十五种,以明五行互含之迹」(二十五味药精)", "叙"),
"心病诸药五行互含文": ("下卷·汤液经法(十三种药)", "底本作「又有药十三种,宜明其五行互含之事,以备心病方之用」", "叙"),
"五味补泻体用图": ("下卷·汤液经法(图注)", "底本图版缺失,仅存图注「此图乃《汤液经法》尽要之妙」", "叙"),
"外感天行病方(论二条,方十二首)": ("下卷·二旦四神大小汤(篇题缺)", "底本篇题缺;本提纲作「外感天行病方」", "方"),
"治中恶卒死方(论一条,方五首)": ("下卷·救五脏中恶卒死方(篇题缺)", "底本篇题缺;本提纲作「治中恶卒死方」", "方"),
"附:拟补心兼属土火金石补泻方四首": ("附录一·拟补心兼属土火金石补泻方四首", "篇题同名(底本作「附:拟补心兼属土火金石补泻方四首」)", "叙"),
"附:从火论心草木金石小补泻汤散四首": ("附录二·从火论心草木金石小补泻汤散四首", "篇题同名(底本作「附:从火论心草木金石小补泻汤散四首」)", "叙"),
}
# 篇题对位类型(用于差异分类)
HEAD_MAP_NOTE = {k: v[1] for k, v in SEC_MAP.items()}
CN = {"一":1,"二":2,"三":3,"四":4,"五":5,"六":6,"七":7,"八":8,"九":9,"十":10,
"十一":11,"十二":12,"十三":13,"十五":15,"二十":20}
DOSE = re.compile(r"(?:各|)[一二三四五六七八九十百]+(?:两|升|合|枚|斤|握|茎|锭|大枚|钱匕)")
# 底本「通论/结语/引文」类行首标记:方条正文范围到此为止(不含药材目录行)
EXCL_PREFIX = ("此篇所列", "陶云", "治疗劳损之方", "若欲作", "又有泻方",
"弘景曰", "陶隐居", "右五方", "若六畜", "上四十字")
FANG_RE = re.compile(r"^(?:救误|养生|调中|建中|凝息|固元)?(?:小|大)?[\u4e00-\u9fff()、]{2,12}?(?:汤散?|散|方)[::]")
OPEN_RE = re.compile(r"^(?:点眼|着舌|启咽|吹鼻|灌耳|熨耳)[\u4e00-\u9fff]{1,6}[::]")
def read(p):
raw = open(p, "rb").read()
return raw.decode("utf-8"), hashlib.md5(raw).hexdigest()
def lines_of(text):
return text.split("\n")
def load_bendi():
"""底本:篇界(结构 JSON)+ 方目(同名正则重算,含药行锚)"""
text, md5 = read(F_BASE)
js = json.load(open(F_JSON, encoding="utf-8"))
ls = lines_of(text)
secs = [dict(篇=n["篇"], 起始行=n["起始行"], 结束行=n["结束行"]) for n in js["篇"]]
excl = EXCL_PREFIX + tuple(a for v in ARGUE.values() for _ln, a in v)
starts = [f["起始行"] for f in js["方"]]
fangs = []
for idx, f in enumerate(js["方"]):
a = f["起始行"]
nxt = starts[idx + 1] if idx + 1 < len(starts) else len(ls) + 1
body = []
for i in range(a, nxt):
s = ls[i - 1].strip()
if i > a and (s.startswith("#") or any(s.startswith(x) for x in excl)):
break
if s:
body.append(i)
end = body[-1] if body else a
if end < a:
raise SystemExit(f"方条范围异常:{f['方名']}")
drug = [i for i in body if DOSE.search(ls[i - 1]) and not ls[i - 1].strip().startswith("右")]
fangs.append(dict(篇=f["篇"], 方名=f["方名"], 起始行=a, 结束行=end,
草木药行=drug[0] if drug else None,
金石药行=drug[-1] if len(drug) >= 2 else None))
return text, md5, js, secs, fangs
def load_zhengding():
text, md5 = read(F_ZD)
js = json.load(open(os.path.join(BASE, "02_加工数据/整订稿结构_篇方.json"), encoding="utf-8"))
return text, md5, js["方"], js["篇"]
def norm_base_name(name):
"""整订稿条目名 -> 底本方名候选(去「散汤」/「汤」后缀、心包->心胞、去附注)"""
n = re.sub(r"(附[^)]*)", "", name)
n = n.replace("(心包)", "(心胞)")
return n, re.sub(r"(散汤|汤)$", "", n)
# 论说条核对:底本各篇「论证段」行号(人工核定 + 首字锚校验;不含篇题/方条/通论结语)
ARGUE = {
"上卷·辨肝脏病证文并方": [(9, "肝虚则恐"), (11, "肝病者"), (13, "邪在肝"), (15, "陶云:肝德在散")],
"上卷·辨心脏病证文并方": [(55, "心虚则悲不已"), (57, "心病者"), (59, "邪在心"), (61, "经云:诸邪在心"), (63, "陶云:心德在耎")],
"上卷·辨心胞病证文(篇题缺)": [(89, "心胞气实者")],
"上卷·辨脾脏病证文并方": [(129, "脾实则"), (131, "脾病者"), (133, "邪在脾"), (135, "陶云:脾德在缓")],
"上卷·辨肺脏病证文并方": [(175, "肺虚则鼻息不利"), (177, "肺病者"), (179, "邪在肺"), (181, "陶云:肺德在收")],
"上卷·辨肾脏病证文并方": [(221, "肾气虚则厥逆"), (223, "肾病者"), (225, "邪在肾"), (227, "陶云:肾德在坚")],
"上卷·救误泻五脏方(篇题缺)": [(267, "又有泻方五首")],
"下卷·救五脏诸劳损病方(小方五首)": [(361, "陶云:经方有救诸劳损病方"), (363, "治疗劳损之方")],
"下卷·二旦四神大小汤(篇题缺)": [(437, "弘景曰:外感天行"), (511, "弘景曰:阳旦者")],
"下卷·救五脏中恶卒死方(篇题缺)": [(513, "陶隐居云:中恶卒死者")],
}
def main():
btext, bmd5, bjs, secs, bfangs = load_bendi()
ztext, zmd5, zfangs, zheads = load_zhengding()
bls = lines_of(btext)
by_sec_name = {}
for f in bfangs:
by_sec_name.setdefault(f["篇"], {})[f["方名"]] = f
rows, diffs, miss = [], [], []
for z in zfangs:
sec = z["篇"]
bsec, head_note, kind = SEC_MAP[sec]
zname = z["方名"]
jixing = "散汤(金石)" if zname.endswith("散汤") else "汤(草木)"
base, stem = norm_base_name(zname)
cands = ["%s汤散" % stem, "%s汤" % stem, stem]
hit = next((by_sec_name.get(bsec, {}).get(c) for c in cands
if by_sec_name.get(bsec, {}).get(c)), None)
note = ""
if hit is None and stem.startswith("大") and sec.startswith("救诸劳损"):
small = "小" + stem[1:]
s2 = by_sec_name.get(bsec, {}).get(small + "汤散") or by_sec_name.get(bsec, {}).get(small + "汤")
hit = dict(篇=bsec, 方名="(底本无独立方条)", 起始行=409, 结束行=409,
草木药行=None, 金石药行=None) if s2 else None
note = (f"底本只列小方五首({small}汤散 行 {s2['起始行']}–{s2['结束行']}),"
f"大方依底本行 409「若欲作大汤散者,补肝汤内加鸡肝……」加法推得,无独立条文"
if s2 else "未找到小方")
hit = dict(hit, 关联小方=f"{small}汤散(行 {s2['起始行']}–{s2['结束行']})" if s2 else "")
if hit is None:
miss.append((z["编号"], zname, sec))
rows.append(dict(编号=z["编号"], 整订稿方名=zname, 篇=sec, 剂型=jixing,
底本方名="—", 底本行="—", 说明="**未落位**"))
diffs.append(f"编号 {z['编号']}「{zname}」未能在底本落位")
continue
if jixing.startswith("散汤"):
anchor = hit.get("金石药行")
anchor_txt = f"金石药行 {anchor}" if anchor else "金石药行(未检出)"
else:
anchor = hit.get("草木药行")
anchor_txt = f"草木药行 {anchor}" if anchor else "草木药行(未检出)"
# 差异分类
cls = []
if hit["方名"].endswith("汤散") and zname.endswith("散汤"):
cls.append("体例:底本「X汤散」条之金石散方 = 整订稿「X散汤」")
elif hit["方名"].endswith("汤散") and not zname.endswith("散汤"):
cls.append("体例:底本「X汤散」条之草木方 = 整订稿「X汤」")
if "(心包)" in zname or "心包" in zname:
cls.append("用字:整订稿「心包」/底本「心胞」(不校,只登记)")
if stem.startswith("大") and sec.startswith("救诸劳损"):
cls.append("底本无独立方条(依大汤加法推得)")
if re.search(r"(附[^)]*)", zname):
cls.append("篇内附方(底本另出条文,非编号方)")
m = re.search(r"(附([^)]*))", zname)
fu = by_sec_name.get(bsec, {}).get(m.group(1))
if fu:
note = (note + f";底本另出附方「{m.group(1)}」(行 {fu['起始行']}–{fu['结束行']},"
f"底本结语作「右五方」故不计入编号)").strip(";")
if anchor is None and sec.startswith("治中恶"):
note = (note + ";本篇为外用/吐法方,底本无常规药味行").strip(";")
rows.append(dict(编号=z["编号"], 整订稿方名=zname, 篇=sec, 剂型=jixing,
底本篇=bsec, 底本方名=hit["方名"],
底本行=f"{hit['起始行']}–{hit['结束行']}",
锚=anchor_txt, 差异=cls, 说明=note))
# 论说条核对:底本各篇「论证段」条数 vs 整订稿篇题标称(锚校验)
def argue_count(sec_label):
ref = ARGUE.get(sec_label, [])
n = 0
for ln, anchor in ref:
if not bls[ln - 1].strip().startswith(anchor):
raise SystemExit(f"论证段锚校验失败:底本行 {ln} 应以「{anchor}」起,实为「{bls[ln-1][:24]}」")
n += 1
return n, [ln for ln, _ in ref]
def declared(t, kw):
m = re.search(kw + r"([一二三四五六七八九十]+)(?:条|首)", t)
return CN.get(m.group(1)) if m else None
arg_rows = []
for h in zheads:
sec = h["篇"]
bsec, head_note, kind = SEC_MAP[sec]
if kind == "叙":
arg_rows.append(dict(整订稿篇=sec, 底本篇=bsec, 整订稿标称="(无)",
底本实测="—", 判定="篇级对位(无方目)", 篇题对位=head_note))
continue
dec_a = declared(sec, "论证") or declared(sec, "论")
act_a, arg_lines = argue_count(bsec)
dec_f = h.get("篇题标称方数")
nb = len([f for f in bfangs if f["篇"] == bsec])
nz = h["方数"]
nb_note = f"{nb} 首"
if sec.startswith("治中恶"):
nb_note = "5 首 + 篇内附 2 首(启咽方、熨耳以通心气,不编号)"
nb = 5
if sec.startswith("辨心"):
ratio = "本篇底本无同构金石方,故汤/散不分立"
elif sec.startswith("救诸劳损"):
ratio = "小方 5 × 2(汤/散分立)+ 大方 5 × 2(依底本大汤加法推得,底本无独立方条)"
elif nb and nz == nb * 2:
ratio = "整订稿「汤/散」分立计"
else:
ratio = "本篇无汤/散分立(一草一汤)"
ok_a = "✅ 相符" if dec_a == act_a else f"⚠ 不符(标称 {dec_a} / 实测 {act_a})"
ok_f = "✅ 相符" if dec_f == nz else f"⚠ 不符(标称 {dec_f} / 实列 {nz})"
arg_rows.append(dict(整订稿篇=sec, 底本篇=bsec,
整订稿标称=f"论证 {dec_a} 条 · 方 {dec_f} 首",
底本实测=f"论证 {act_a} 段(行 {'、'.join(map(str, arg_lines))})· 方条 {nb_note}",
判定=f"论证 {ok_a};方数 {ok_f}({nz} = 底本 {nb} × {nz // nb if nb else '—'};{ratio})",
篇题对位=head_note))
# 落位自检
n_ok = sum(1 for r in rows if r["底本方名"] != "—")
checks = [f"方目落位 {n_ok}/{len(zfangs)}"
+ ("(全部落位)" if n_ok == len(zfangs) else "**有未落位项**")]
checks.append("论说条 / 方数 逐篇核对:" +
("全部相符" if all("⚠" not in r["判定"] for r in arg_rows) else "**有不符项**"))
checks.append(f"底本方目总数 {len(bfangs)}(结构 JSON)→ 整订稿方目 {len(zfangs)}(汤/散分立计)")
js = dict(
底本=dict(文件=os.path.basename(F_BASE), md5=bmd5),
整订稿=dict(文件=os.path.basename(F_ZD), md5=zmd5),
关联主轴="主=底本(印刷本清洗版);从=整订稿篇目提纲;粒度=方目条 + 药行行号;不改动原文",
自检=checks, 篇级核对=arg_rows,
方目对照=[dict(编号=r["编号"], 整订稿方名=r["整订稿方名"], 篇=r["篇"], 剂型=r["剂型"],
底本篇=r.get("底本篇"), 底本方名=r["底本方名"], 底本行=r["底本行"],
锚=r["锚"], 差异=r["差异"], 说明=r["说明"]) for r in rows],
未落位=miss)
os.makedirs(os.path.dirname(OUT_JS), exist_ok=True)
with open(OUT_JS, "w", encoding="utf-8") as f:
f.write(json.dumps(js, ensure_ascii=False, indent=2) + "\n")
md = ["# 整订稿 ⇄ 底本 方目对照表(本库内部逐条对应)", "",
"> **关联主轴**:主=**底本**(`02_加工数据/辅行诀五脏用药法要_基础文本_清洗版.md`,"
"印刷本,用户 2026-09-16 定为基础文本);从=**整订稿篇目提纲**"
"(`02_加工数据/整订稿_清洗版.md`,衣之镖《整订稿》篇目/方目,编号 1–101)。",
"> **粒度**:方目条 + 药行(草木药行/金石药行)行号锚定,行号一律指各自清洗版;**不改动任何一方原文**。",
f"> 版本:底本 md5 `{bmd5}`;整订稿 md5 `{zmd5}`;脚本 `05_脚本工具/06_link_zhengding.py`(幂等可重跑)", "",
"## 一、对位规则", "",
"| 整订稿条目 | 底本对位 | 落地锚 |", "|-----------|---------|--------|",
"| X汤 | 底本「X**汤散**」条(草木方+金石散同条)之**草木药行** | 方条行范围 + 草木药行行号 |",
"| X散汤 | 同上条之**金石药行**(同构金石方) | 同条行范围 + 金石药行行号 |",
"| 大X汤/大X散汤(救诸劳损病方篇) | 底本**无独立方条**;依底本行 409「若欲作大汤散者……」加法推得 | 加法行 409 + 对应小方行范围 |",
"| 篇内附(启咽方、熨耳以通心气) | 底本另出条文(非编号方) | 底本对应行 |",
"| 检录伊尹《汤液经法》方(六十一首)/诸药五味五行互含文/心病诸药五行互含文/五味补泻体用图 | 篇级对位(无逐方目) | 底本篇行范围 |",
"", "## 二、篇级核对(论证条数 · 方数 · 篇题)", "",
"| 整订稿篇 | 底本篇(本库标签) | 整订稿标称 | 底本实测 | 判定 |", "|---------|------------------|-----------|---------|------|"]
for r in arg_rows:
md.append(f"| {r['整订稿篇']} | {r['底本篇']} | {r['整订稿标称']} | {r['底本实测']} | {r['判定']} |")
md += ["", "## 三、方目逐条对照(整订稿 1–101 → 底本行号)", "",
"> 行号一律指各自**清洗版**;「落地锚」给出该条在底本中的药行行号(散汤=金石药行,余=草木药行)。",
"> 差异登记栏只列**体例/用字**之异,非校勘结论;底本为准,不作改写。", "",
"| 编号 | 整订稿方名 | 篇 | 剂型 | 底本方名 | 底本行 | 落地锚 | 差异登记 | 备注 |",
"|------|-----------|----|------|---------|--------|--------|---------|------|"]
for r in rows:
md.append(f"| {r['编号']} | {r['整订稿方名']} | {r['篇'].split('(')[0]} | {r['剂型']} | "
f"{r['底本方名']} | {r['底本行']} | {r['锚']} | {';'.join(r['差异']) or '—'} | "
f"{r['说明'] or '—'} |")
md += ["", "## 四、附录二种与篇内附方(整订稿不编号者)", "",
"| 整订稿篇/附 | 底本对位 | 底本行 |", "|-------------|---------|--------|",
"| 附:拟补心兼属土火金石补泻方四首 | 小泻心散/大泻心散/小补心散/大补心散(4 首,底本不另编号) | "
+ "、".join(str(f["起始行"]) for f in bfangs if f["篇"].startswith("附录一")) + " |",
"| 附:从火论心草木金石小补泻汤散四首 | 小泻心汤散/小补心汤散(题称四首 = 草木方+金石方各一) | "
+ "、".join(str(f["起始行"]) for f in bfangs if f["篇"].startswith("附录二")) + " |",
"| 99.启咽以通脾气(附启咽方) | 启咽以通脾气(行 523)+启咽方(行 527,附) | 523/527 |",
"| 101.灌耳以通肾气(附熨耳以通心气) | 灌耳以通肾气(行 535)+熨耳以通心气(行 539,附) | 535/539 |",
"", "## 五、自检", ""]
for c in checks:
md.append(f"- {'✅' if '**' not in c else '⚠'} {c}")
if miss:
md += ["", "**未落位项**:"] + [f"- 编号 {a}「{b}」({c})" for a, b, c in miss]
md += ["", "---", "",
"> 📅 2026-09-17 —— 建立本库内部逐条对应(用户「核对关联」指令);"
f"底本方目 {len(bfangs)} 首 ⇄ 整订稿方目 {len(zfangs)} 条(汤/散分立);"
f"落位 {n_ok}/{len(zfangs)}。差异清单与结论见 `整订稿核对报告.md`。"]
with open(OUT_MD, "w", encoding="utf-8") as f:
f.write("\n".join(md) + "\n")
print("对照表:", OUT_MD, os.path.getsize(OUT_MD), "bytes")
print("对照 :", OUT_JS, os.path.getsize(OUT_JS), "bytes")
for c in checks:
print(" ", c)
if miss:
print(" 未落位:", miss)
print("--- 篇级核对 ---")
for r in arg_rows:
print(f" {r['整订稿篇'][:14]:<16} | {r['整订稿标称']:<22} | {r['底本实测']:<30} | {r['判定']}")
if __name__ == "__main__":
main()