#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 《中医入门》理论依据查询接口(应用调用层) 用法: from theory_api import TCMTheory t = TCMTheory() t.herb("黄芪") # 药材理论依据: 效能分类/功效药组/治法/用药安全 t.formula("大承气汤") # 方剂理论依据: 主方定位/主治/七方归属/组成药组 t.safety_check(["甘草","大戟"]) # 配伍安全核查: 十八反/十九畏/妊娠禁忌 t.method("补中益气") # 治法查询(名称模糊) t.constitution("气虚") # 体质调理依据(按气虚/阳虚/阴虚等关键词聚合) 依据均含 行号(02_加工数据/中医入门.md) 可人工溯源。 """ import json, os, re LIB = os.path.join(os.path.dirname(__file__), "../02_加工数据/理论依据库.json") BOOK = os.path.join(os.path.dirname(__file__), "../02_加工数据/中医入门.md") # 书侧简写 → 正名(与 reverse_tag 脚本一致的归一) BOOK_ALIAS = {"山萸":"山茱萸","萸肉":"山茱萸","麻仁":"火麻仁","云苓":"茯苓","赤苓":"茯苓", "云茯神":"茯神","银花":"金银花","杭菊花":"菊花","花粉":"天花粉","寸冬":"麦冬", "生地":"生地黄","熟地":"熟地黄","鲜生地":"生地黄","肉果":"肉豆蔻","淡苁蓉":"肉苁蓉", "吴萸":"吴茱萸","川连":"黄连","潼沙苑":"沙苑子","仙灵脾":"淫羊藿","旱莲草":"墨旱莲", "丹皮":"牡丹皮","茅根":"白茅根","象贝母":"浙贝母","川贝":"川贝母","白蒺藜":"刺蒺藜", "菟丝饼":"菟丝子","桑椹子":"桑椹","使君肉":"使君子","山查炭":"山楂","焦麦芽":"麦芽", "炒莱菔子":"莱菔子","驴皮胶":"阿胶","龟版":"龟板","菟蓉":"肉苁蓉","金铃子":"川楝子", "延胡":"延胡索","参三七":"三七","全当归":"当归","当归身":"当归","冬虫草":"冬虫夏草", "蝉衣":"蝉蜕","瓜蒌仁":"瓜蒌子","左牡蛎":"牡蛎","生石决":"石决明","羚羊尖":"羚羊角", "炙全蝎":"全蝎","炒谷芽":"谷芽","春砂仁":"砂仁","白蔻仁":"豆蔻","御米壳":"罂粟壳", "煨葛根":"葛根","炙升麻":"升麻","软柴胡":"柴胡","煨草果":"草果","炒苡仁":"薏苡仁", "饭赤豆":"赤小豆","白藓皮":"白鲜皮","炙兜铃":"马兜铃","黑山栀":"栀子","炒竹茹":"竹茹", "炙桑皮":"桑白皮","炒牛蒡":"牛蒡子","苦桔梗":"桔梗","石草":"石韦","淡菓蓉":"肉苁蓉", "复盆子":"覆盆子","菖蒲":"石菖蒲","萹蓄草":"萹蓄","海金砂":"海金沙",} PREP_PREFIX = ["生晒","炙","煨","煅","焦","炒","制","飞","清","法","霜","酒","醋","盐", "蜜","麸","鲜","干","姜","蒸","煮"] # 只剥炮制前缀;产地品质限定词不剥(土人参陷阱) def _norm(name: str): c = name.strip() for p in PREP_PREFIX: if c.startswith(p) and len(c) > len(p) + 1: c = c[len(p):]; break return BOOK_ALIAS.get(c, c) class TCMTheory: def __init__(self): self.lib = json.load(open(LIB, encoding="utf-8")) self.book_lines = open(BOOK, encoding="utf-8").read().splitlines() # 索引: 药名(正名+别名+炮制品) → 锚点 self.herb_index = {} for cat, info in self.lib["效能分类"].items(): for h in info["代表药"]: self.herb_index.setdefault(_norm(h), []).append({"通道": "效能分类", "值": cat, "行号": info["行号"]}) for g in self.lib["功效药组"]["条目"]: for h in g["药"]: self.herb_index.setdefault(_norm(h), []).append({"通道": "功效药组", "值": g["组"], "行号": g["行号"]}) for num, z in self.lib["治法72"]["条目"].items(): for h in z["药如"]: self.herb_index.setdefault(_norm(h), []).append({"通道": "治法", "值": f"治法{num}.{z['名称']}", "行号": z["行号"]}) self.safety_index = {} for grp in self.lib["用药安全"]["十八反"]["相反组"]: for a in grp["组"]: self.safety_index.setdefault(a, []).append(("十八反", f'与{"、".join(grp["反"])}相反', "书列")) for a in grp.get("组_通行扩展", []): self.safety_index.setdefault(a, []).append(("十八反", f'与{"、".join(grp["反"])}相反', "通行扩展")) for h in grp["反"]: who = "、".join(grp["组"] + grp.get("组_通行扩展", [])) self.safety_index.setdefault(h, []).append(("十八反", f"反{who}", "书列")) for h in grp.get("反_通行扩展", []): who = "、".join(grp["组"] + grp.get("组_通行扩展", [])) self.safety_index.setdefault(h, []).append(("十八反", f"反{who}", "通行扩展")) for a, b in self.lib["用药安全"]["十九畏"]["畏对"]: self.safety_index.setdefault(a, []).append(("十九畏", f"畏{b}", "书列")) self.safety_index.setdefault(b, []).append(("十九畏", f"为{a}所畏", "书列")) for h in self.lib["用药安全"]["妊娠禁忌"]["药"]: self.safety_index.setdefault(h, []).append(("妊娠禁忌", "妊娠禁忌药", "书列")) def _quote(self, line_no, width=60): if 1 <= line_no <= len(self.book_lines): return self.book_lines[line_no - 1][:width] return None # ---------- 药材理论依据 ---------- def herb(self, name: str): n = _norm(name) hits = self.herb_index.get(n, []) or self.herb_index.get(name.strip(), []) if not hits: return {"药材": name, "结果": "《中医入门》理论药组未收录(该书为入门纲要,未收不等于无理论依据)"} out = {"药材": name, "正名": n, "依据": []} seen = set() for h in hits: key = (h["通道"], h["值"]) if key in seen: continue seen.add(key) out["依据"].append({**h, "原文": self._quote(h["行号"])}) out["用药安全"] = self._safety_of(n) return out # ---------- 方剂理论依据 ---------- def formula(self, name: str): s = self.lib["示例方40"].get(name.strip()) if not s: # 组成含书侧示例方药的模糊建议 return {"方剂": name, "结果": "非书侧40示例方;可按组成逐味调用 herb() 获取理论依据", "剂型理论": self._jixing_hint(name)} out = {"方剂": name, "主方定位": s["主方定位"], "主治": s["主治"], "组成_书": s["组成_书"], "行号": s["行号"], "原文": self._quote(s["行号"], 120), "七方归属": [q for q, v in self.lib["七方"].items() if name in v["书内举例"]] or None, "组成理论": []} safety_all = [] for h in s["组成_书"]: hb = self.herb(h) ch = [f'{d["通道"]}:{d["值"]}' for d in hb.get("依据", [])][:3] out["组成理论"].append({"药": h, "依据": ch}) if hb.get("用药安全"): safety_all.append({h: hb["用药安全"]}) if safety_all: out["组成用药安全"] = safety_all return out # ---------- 配伍安全核查 ---------- def safety_check(self, herbs): herbs = [h.strip() for h in herbs if h.strip()] conflicts, warnings = [], [] normalized = {h: _norm(h) for h in herbs} names = list(normalized.items()) # 十八反: 组内对立(含通行扩展,结果标注来源) for grp in self.lib["用药安全"]["十八反"]["相反组"]: side_a = {(h, n) for h, n in names if n in grp["组"] or n in grp.get("组_通行扩展", [])} side_b = {(h, n) for h, n in names if n in grp["反"] or n in grp.get("反_通行扩展", [])} if side_a and side_b: src = "书列" a_ext = any(n in grp.get("组_通行扩展", []) for _, n in side_a) b_ext = any(n in grp.get("反_通行扩展", []) for _, n in side_b) if a_ext or b_ext: src = "通行扩展" conflicts.append({"类型": "十八反", "来源": src, "组": sorted(h for h, _ in side_a), "反": sorted(h for h, _ in side_b), "行号": self.lib["用药安全"]["十八反"]["行号"]}) # 十九畏: 组内畏对 pairset = {frozenset(p) for p in self.lib["用药安全"]["十九畏"]["畏对"]} for i in range(len(names)): for j in range(i + 1, len(names)): if frozenset([names[i][1], names[j][1]]) in pairset: warnings.append({"类型": "十九畏", "对": [names[i][0], names[j][0]], "行号": self.lib["用药安全"]["十九畏"]["行号"]}) # 妊娠禁忌 preg = [h for h, n in names if n in self.lib["用药安全"]["妊娠禁忌"]["药"] or h in self.lib["用药安全"]["妊娠禁忌"]["药"]] return {"核查药味": herbs, "十八反冲突": conflicts or None, "十九畏警式": warnings or None, "妊娠禁忌药": preg or None, "结论": "存在配伍禁忌,须调整" if conflicts else ("有相畏提示,慎用并权衡" if warnings else ("含妊娠禁忌药,孕妇须避" if preg else "书侧十八反/十九畏/妊娠禁忌未命中"))} # ---------- 治法 ---------- def method(self, keyword: str): out = [] for num, z in self.lib["治法72"]["条目"].items(): hay = z["名称"] + (z["适应症"] or "") if keyword in z["名称"] or keyword in hay or z["名称"][:2] in keyword: out.append({"治法": f"{num}.{z['名称']}", "适应症": z["适应症"], "药如": z["药如"], "行号": z["行号"], "原文": self._quote(z["行号"])}) for bn, bv in self.lib["八法"].items(): if keyword in bn or keyword in bv["定义原文"] or bn[0] in keyword: out.append({"八法": bn, "定义": bv["定义原文"], "行号": bv["行号"]}) return {"关键词": keyword, "命中": out or None} # ---------- 体质调理依据 ---------- CONSTITUTION_SYNONYMS = { "气虚": ["补气", "中气"], "血虚": ["补血", "养血"], "阴虚": ["补阴", "滋阴", "养阴"], "阳虚": ["补阳", "助阳", "温阳", "扶阳"], "痰湿": ["化湿", "化痰", "淡渗"], "湿热": ["清湿热", "化湿", "利湿"], "血瘀": ["活血", "破瘀"], "气滞": ["行气", "破气", "疏肝"], "寒": ["温中", "祛寒", "散寒", "扶阳"], "热": ["清热", "泻火", "凉血"], "食积": ["消食", "消导"]} def constitution(self, keyword: str): """按 气虚/血虚/阴虚/阳虚/痰湿/湿热/血瘀/气滞/寒/热/食积 等关键词聚合理论依据""" syns = self.CONSTITUTION_SYNONYMS.get(keyword, [keyword]) res = {"关键词": keyword, "同义词展开": syns, "补益药组": [], "治法": [], "八法": []} for g in self.lib["功效药组"]["条目"]: if any(s in g["组"] for s in syns): res["补益药组"].append({"组": g["组"], "药": g["药"], "行号": g["行号"]}) for num, z in self.lib["治法72"]["条目"].items(): hay = z["名称"] + (z["适应症"] or "") if any(s in hay for s in syns): res["治法"].append({"治法": f"{num}.{z['名称']}", "药如": z["药如"], "行号": z["行号"]}) for bn, bv in self.lib["八法"].items(): if any(s in bv["定义原文"] for s in syns): res["八法"].append({"八法": bn, "行号": bv["行号"]}) res["提示"] = "示例方与药组的完整组成请调用 formula()/herb() 逐层获取" if (res["补益药组"] or res["治法"]) else None return res # ---------- 内部 ---------- def _safety_of(self, n): recs = self.safety_index.get(n, []) out = {} for typ, desc, src in recs: out.setdefault(typ, []).append(f"{desc}[{src}]" if src != "书列" else desc) return out or None def _jixing_hint(self, name): m = re.search(r'(丸|散|膏|丹|酒|汤|饮)', name) if not m: return None jx = m.group(1) return {"剂型": jx, "说明": "丸散膏丹等剂型各具性质效用(方剂之部·剂型)"} if __name__ == "__main__": t = TCMTheory() print("== herb 黄芪 =="); print(json.dumps(t.herb("黄芪"), ensure_ascii=False, indent=1)[:600]) print("\n== formula 大承气汤 =="); print(json.dumps(t.formula("大承气汤"), ensure_ascii=False, indent=1)[:700]) print("\n== safety 甘草+大戟 =="); print(json.dumps(t.safety_check(["甘草", "大戟", "茯苓"]), ensure_ascii=False, indent=1)) print("\n== method 补中 =="); print(json.dumps(t.method("补中"), ensure_ascii=False, indent=1)[:400]) print("\n== constitution 气虚 =="); print(json.dumps(t.constitution("气虚"), ensure_ascii=False, indent=1)[:500])