665 lines
23 KiB
Python
665 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
9种中医体质 × 方剂/药材/穴位 深层挖掘
|
|
=========================================
|
|
数据源:
|
|
- 方剂库 (1000个JSON)
|
|
- 中药材库 (1000个JSON)
|
|
- 针灸穴位库 (1000个JSON)
|
|
- 九种体质详细数据.json
|
|
|
|
输出:
|
|
1. JSON: 体质-方剂药材穴位挖掘.json
|
|
2. MD: 体质数据挖掘报告.md (追加在疾病症状部分之后)
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
from collections import defaultdict, Counter
|
|
from pathlib import Path
|
|
|
|
# ============================================================
|
|
# 0. 路径设置
|
|
# ============================================================
|
|
BASE = Path("/home/songyi/Documents/ai_agent_scraper_study/data/大医网")
|
|
DIR_FANGJI = BASE / "01_来源数据/方剂"
|
|
DIR_YAOCAI = BASE / "01_来源数据/中药材"
|
|
DIR_XUEWEI = BASE / "01_来源数据/针灸穴位"
|
|
CONST_PATH = BASE / "02_加工数据/中医体质/九种体质详细数据.json"
|
|
OUT_JSON = BASE / "03_关联融合/交叉关联分析/体质-方剂药材穴位挖掘.json"
|
|
OUT_MD = BASE / "04_分析报告/体质数据挖掘报告.md"
|
|
|
|
# 确保输出目录存在
|
|
OUT_JSON.parent.mkdir(parents=True, exist_ok=True)
|
|
OUT_MD.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# ============================================================
|
|
# 1. 加载体质数据 & 提取关键字
|
|
# ============================================================
|
|
print("[1/6] 加载体质数据...")
|
|
with open(CONST_PATH, "r", encoding="utf-8") as f:
|
|
constitutions = json.load(f)
|
|
|
|
# 9种体质: 名称 + 关联关键字
|
|
const_info = {} # { "气虚质": { "keywords": [...], "name": "气虚质" } }
|
|
for c in constitutions:
|
|
name = c["名称"]
|
|
kw = c.get("关联关键字", [])
|
|
const_info[name] = {
|
|
"keywords": kw,
|
|
"name": name,
|
|
"data": c
|
|
}
|
|
|
|
const_names = list(const_info.keys())
|
|
print(f" 加载 {len(const_names)} 种体质: {const_names}")
|
|
|
|
# ============================================================
|
|
# 2. 加载全部数据
|
|
# ============================================================
|
|
print("[2/6] 加载方剂、药材、穴位数据...")
|
|
|
|
def load_json_files(directory):
|
|
"""加载目录下所有JSON文件,返回列表"""
|
|
items = []
|
|
files = sorted(os.listdir(directory))
|
|
for fname in files:
|
|
if not fname.endswith(".json"):
|
|
continue
|
|
fpath = os.path.join(directory, fname)
|
|
try:
|
|
with open(fpath, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
items.append(data)
|
|
except Exception as e:
|
|
print(f" ⚠ 加载失败 {fname}: {e}")
|
|
return items
|
|
|
|
# 方剂
|
|
fangji_list = load_json_files(DIR_FANGJI)
|
|
print(f" 方剂: {len(fangji_list)} 个")
|
|
|
|
# 中药材
|
|
yaocai_list = load_json_files(DIR_YAOCAI)
|
|
print(f" 药材: {len(yaocai_list)} 个")
|
|
|
|
# 针灸穴位
|
|
xuewei_list = load_json_files(DIR_XUEWEI)
|
|
print(f" 穴位: {len(xuewei_list)} 个")
|
|
|
|
# ============================================================
|
|
# 3. 构建药材名索引 (用于最长前缀匹配)
|
|
# ============================================================
|
|
print("[3/6] 构建药材名称索引 (最长前缀匹配)...")
|
|
|
|
# 收集所有药材名称 (按长度降序排序,避免 金 vs 金银花 误配)
|
|
herb_names_all = []
|
|
for h in yaocai_list:
|
|
name = h.get("名称", "").strip()
|
|
if name:
|
|
herb_names_all.append(name)
|
|
# 也加入别名中的药材名
|
|
aliases = h.get("别名", "")
|
|
if aliases:
|
|
# 别名格式: "木丹、鲜支、越桃、支子、山栀子、栀子"
|
|
for a in re.split(r'[、,,]', aliases):
|
|
a = a.strip()
|
|
if a and len(a) >= 2:
|
|
herb_names_all.append(a)
|
|
|
|
# 去重并按长度降序排序
|
|
herb_names_all = sorted(set(herb_names_all), key=lambda x: (-len(x), x))
|
|
print(f" 药材名称库: {len(herb_names_all)} 个 (含别名)")
|
|
|
|
def longest_prefix_match(text, herb_list=None):
|
|
"""
|
|
在 text 中搜索药材名(最长前缀匹配)
|
|
返回匹配到的药材名列表
|
|
"""
|
|
if herb_list is None:
|
|
herb_list = herb_names_all
|
|
found = []
|
|
# 按长度降序遍历,确保先匹配长名
|
|
remaining = text
|
|
# 简单方法:对每个已知药材名,看是否出现在text中
|
|
# 但为避免重叠,我们使用贪心匹配
|
|
for hname in herb_list:
|
|
if hname in remaining:
|
|
found.append(hname)
|
|
# 移除已匹配部分,避免重复/重叠
|
|
# 但这里我们不用移除,因为方解-组成中是"、"分隔的,不会有重叠
|
|
return found
|
|
|
|
def extract_herbs_from_formula(formula):
|
|
"""
|
|
从方剂数据中提取药材名
|
|
优先从 方解-组成 字段提取
|
|
其次是 组成 字段
|
|
最后从 方义 字段中提取
|
|
"""
|
|
herbs = []
|
|
|
|
# 1. 尝试 方解-组成
|
|
if "方解-组成" in formula:
|
|
text = formula["方解-组成"]
|
|
# 用、分割
|
|
parts = re.split(r'[、,,]', text)
|
|
for p in parts:
|
|
p = p.strip()
|
|
if p:
|
|
herbs.append(p)
|
|
|
|
# 2. 尝试 组成
|
|
if not herbs and "组成" in formula:
|
|
text = formula["组成"]
|
|
# 用、或空格分割,去掉剂量信息
|
|
# 例如: 桂枝(别切)四两、甘草(炙)四两、白术三两
|
|
# 提取括号前的药材名
|
|
parts = re.split(r'[、,,]', text)
|
|
for p in parts:
|
|
p = p.strip()
|
|
# 去掉剂量部分
|
|
# 匹配 "药材名(炮制)剂量" 或 "药材名 剂量"
|
|
m = re.match(r'^([\u4e00-\u9fff\w]+)', p)
|
|
if m:
|
|
name = m.group(1).strip()
|
|
if name and len(name) >= 2:
|
|
herbs.append(name)
|
|
|
|
# 3. 从方义中提取
|
|
if not herbs and "方义" in formula:
|
|
text = formula["方义"]
|
|
# 使用最长前缀匹配提取药材名
|
|
herbs = longest_prefix_match(text)
|
|
|
|
# 去重
|
|
seen = set()
|
|
unique_herbs = []
|
|
for h in herbs:
|
|
if h not in seen:
|
|
seen.add(h)
|
|
unique_herbs.append(h)
|
|
|
|
return unique_herbs
|
|
|
|
|
|
# ============================================================
|
|
# 4. 评分函数
|
|
# ============================================================
|
|
|
|
def score_formula(formula, keywords):
|
|
"""
|
|
对方剂进行体质关键字评分
|
|
名称 +5, 方义/简介 +3, 歌诀/出处 +2, 其他 +1
|
|
返回总分
|
|
"""
|
|
score = 0.0
|
|
# 名称
|
|
name = formula.get("名称", "")
|
|
for kw in keywords:
|
|
if kw in name:
|
|
score += 5.0
|
|
|
|
# 方义 + 简介 (+3)
|
|
fangyi = formula.get("方义", "")
|
|
intro = formula.get("简介", "")
|
|
for field_text in [fangyi, intro]:
|
|
if field_text:
|
|
for kw in keywords:
|
|
count = field_text.count(kw)
|
|
score += count * 3.0
|
|
|
|
# 歌诀 + 出处 (+2)
|
|
gejue = formula.get("歌诀", "")
|
|
chuchu = formula.get("出处", "")
|
|
for field_text in [gejue, chuchu]:
|
|
if field_text:
|
|
for kw in keywords:
|
|
count = field_text.count(kw)
|
|
score += count * 2.0
|
|
|
|
# 其他字段 (+1)
|
|
other_fields = ["分类", "运用", "配伍特点", "加减化裁", "化裁方之间的鉴别",
|
|
"重要文献摘要", "用法用量", "使用注意", "趣味记忆", "注意事项",
|
|
"各家论述", "方解-功效", "方解-主治"]
|
|
for field in other_fields:
|
|
val = formula.get(field, "")
|
|
if val:
|
|
for kw in keywords:
|
|
count = val.count(kw)
|
|
score += count * 1.0
|
|
|
|
return round(score, 1)
|
|
|
|
|
|
def score_herb(herb, keywords):
|
|
"""
|
|
对药材进行体质关键字评分
|
|
名称 +5, 简介 +3, 功效作用 +3, 性味归经 +2, 临床应用 +2, 其他 +1
|
|
"""
|
|
score = 0.0
|
|
|
|
# 名称 (+5)
|
|
name = herb.get("名称", "")
|
|
for kw in keywords:
|
|
if kw in name:
|
|
score += 5.0
|
|
|
|
# 简介 (+3)
|
|
intro = herb.get("简介", "")
|
|
if intro:
|
|
for kw in keywords:
|
|
count = intro.count(kw)
|
|
score += count * 3.0
|
|
|
|
# 功效作用 (+3) - 嵌套对象
|
|
effect = herb.get("功效作用", {})
|
|
if isinstance(effect, dict):
|
|
effect_text = " ".join(str(v) for v in effect.values())
|
|
elif isinstance(effect, str):
|
|
effect_text = effect
|
|
else:
|
|
effect_text = ""
|
|
if effect_text:
|
|
for kw in keywords:
|
|
count = effect_text.count(kw)
|
|
score += count * 3.0
|
|
|
|
# 性味归经 (+2)
|
|
xingwei = herb.get("性味归经", "")
|
|
if xingwei:
|
|
for kw in keywords:
|
|
count = xingwei.count(kw)
|
|
score += count * 2.0
|
|
|
|
# 临床应用 (+2)
|
|
clinical = herb.get("临床应用", {})
|
|
if isinstance(clinical, dict):
|
|
clinical_text = " ".join(str(v) for v in clinical.values())
|
|
elif isinstance(clinical, str):
|
|
clinical_text = clinical
|
|
else:
|
|
clinical_text = ""
|
|
if clinical_text:
|
|
for kw in keywords:
|
|
count = clinical_text.count(kw)
|
|
score += count * 2.0
|
|
|
|
# 其他字段 (+1)
|
|
other_fields = ["别名", "中文名称", "拉丁文名", "道地产区", "加工炮制",
|
|
"药材鉴别", "保存方法", "植物学信息", "动物学信息",
|
|
"矿物学信息", "毒性", "相关方剂", "医保类型"]
|
|
for field in other_fields:
|
|
val = herb.get(field, "")
|
|
if isinstance(val, str) and val:
|
|
for kw in keywords:
|
|
count = val.count(kw)
|
|
score += count * 1.0
|
|
elif isinstance(val, dict):
|
|
val_text = " ".join(str(v) for v in val.values())
|
|
if val_text:
|
|
for kw in keywords:
|
|
count = val_text.count(kw)
|
|
score += count * 1.0
|
|
|
|
return round(score, 1)
|
|
|
|
|
|
def score_xuewei(xuewei, keywords):
|
|
"""
|
|
对穴位进行体质关键字评分
|
|
名称 +5, 主治 +3, 详细主治 +3, 功能 +2, 其他 +1
|
|
"""
|
|
score = 0.0
|
|
|
|
# 名称 (+5)
|
|
name = xuewei.get("名称", "")
|
|
for kw in keywords:
|
|
if kw in name:
|
|
score += 5.0
|
|
|
|
# 主治 (+3)
|
|
zhuzhi = xuewei.get("主治", "")
|
|
if zhuzhi:
|
|
for kw in keywords:
|
|
count = zhuzhi.count(kw)
|
|
score += count * 3.0
|
|
|
|
# 详细主治 (+3)
|
|
detail = xuewei.get("详细主治", "")
|
|
if detail:
|
|
for kw in keywords:
|
|
count = detail.count(kw)
|
|
score += count * 3.0
|
|
|
|
# 功能 (+2)
|
|
func = xuewei.get("功能", "") or xuewei.get("功能作用", "")
|
|
if func:
|
|
for kw in keywords:
|
|
count = func.count(kw)
|
|
score += count * 2.0
|
|
|
|
# 其他字段 (+1)
|
|
other_fields = ["简介", "隶属", "位置", "定位", "解剖", "详细操作",
|
|
"临床运用", "主要配伍", "配伍", "附注", "相关论述",
|
|
"出处", "功能作用"]
|
|
for field in other_fields:
|
|
val = xuewei.get(field, "")
|
|
if isinstance(val, str) and val:
|
|
for kw in keywords:
|
|
count = val.count(kw)
|
|
score += count * 1.0
|
|
|
|
return round(score, 1)
|
|
|
|
|
|
# ============================================================
|
|
# 5. 主挖掘流程
|
|
# ============================================================
|
|
print("[4/6] 执行方剂/药材/穴位评分挖掘...")
|
|
|
|
# 结果容器
|
|
result = {
|
|
"体质-方剂": {},
|
|
"体质-药材": {},
|
|
"体质-穴位": {},
|
|
"体质-药材分类统计": {},
|
|
"体质-核心药对": {},
|
|
"体质-穴位归经统计": {},
|
|
"统计摘要": {}
|
|
}
|
|
|
|
# ---- 5A. 方剂挖掘 ----
|
|
print(" → 方剂评分...")
|
|
for const_name in const_names:
|
|
keywords = const_info[const_name]["keywords"]
|
|
|
|
scored = []
|
|
for f in fangji_list:
|
|
s = score_formula(f, keywords)
|
|
if s >= 3.0:
|
|
herbs = extract_herbs_from_formula(f)
|
|
scored.append({
|
|
"名称": f.get("名称", ""),
|
|
"得分": s,
|
|
"药材": herbs
|
|
})
|
|
|
|
# 排序取TOP20
|
|
scored.sort(key=lambda x: (-x["得分"], x["名称"]))
|
|
top20 = scored[:20]
|
|
result["体质-方剂"][const_name] = top20
|
|
print(f" {const_name}: {len(scored)} 个通过阈值, TOP20已选取")
|
|
|
|
# ---- 5B. 药材挖掘 ----
|
|
print(" → 药材评分...")
|
|
for const_name in const_names:
|
|
keywords = const_info[const_name]["keywords"]
|
|
|
|
scored = []
|
|
for h in yaocai_list:
|
|
s = score_herb(h, keywords)
|
|
if s >= 3.0:
|
|
# 分类:使用 药材分类 字段
|
|
classification = h.get("药材分类", "其他")
|
|
xingwei = h.get("性味归经", "")
|
|
scored.append({
|
|
"名称": h.get("名称", ""),
|
|
"得分": s,
|
|
"分类": classification,
|
|
"性味归经": xingwei
|
|
})
|
|
|
|
scored.sort(key=lambda x: (-x["得分"], x["名称"]))
|
|
top30 = scored[:30]
|
|
result["体质-药材"][const_name] = top30
|
|
|
|
# 药材分类统计
|
|
class_counter = Counter()
|
|
for item in top30:
|
|
class_counter[item["分类"]] += 1
|
|
result["体质-药材分类统计"][const_name] = dict(class_counter.most_common())
|
|
|
|
# 核心药对 (TOP30药材两两组合)
|
|
top_names = [item["名称"] for item in top30]
|
|
pairs = []
|
|
for i in range(len(top_names)):
|
|
for j in range(i+1, len(top_names)):
|
|
a, b = top_names[i], top_names[j]
|
|
# 统计共现次数 = 同时在多少方剂中出现
|
|
co_count = 0
|
|
for f in fangji_list:
|
|
herbs = extract_herbs_from_formula(f)
|
|
if a in herbs and b in herbs:
|
|
co_count += 1
|
|
if co_count > 0:
|
|
pairs.append([a, b, co_count])
|
|
# 按共现数降序排序
|
|
pairs.sort(key=lambda x: -x[2])
|
|
result["体质-核心药对"][const_name] = pairs[:30] # 取TOP30药对
|
|
|
|
print(f" {const_name}: {len(scored)} 个通过阈值, TOP30选取, {len(pairs)} 个药对")
|
|
|
|
# ---- 5C. 穴位挖掘 ----
|
|
print(" → 穴位评分...")
|
|
for const_name in const_names:
|
|
keywords = const_info[const_name]["keywords"]
|
|
|
|
scored = []
|
|
for x in xuewei_list:
|
|
s = score_xuewei(x, keywords)
|
|
if s >= 3.0:
|
|
scored.append({
|
|
"名称": x.get("名称", ""),
|
|
"得分": s,
|
|
"隶属": x.get("隶属", ""),
|
|
"主治": x.get("主治", "")
|
|
})
|
|
|
|
scored.sort(key=lambda x: (-x["得分"], x["名称"]))
|
|
top20 = scored[:20]
|
|
result["体质-穴位"][const_name] = top20
|
|
|
|
# 穴位归经统计
|
|
meridian_counter = Counter()
|
|
for item in top20:
|
|
meridian_counter[item["隶属"]] += 1
|
|
result["体质-穴位归经统计"][const_name] = dict(meridian_counter.most_common())
|
|
|
|
print(f" {const_name}: {len(scored)} 个通过阈值, TOP20选取")
|
|
|
|
|
|
# ---- 5D. 统计摘要 ----
|
|
print(" → 生成统计摘要...")
|
|
summary = {}
|
|
for const_name in const_names:
|
|
summary[const_name] = {
|
|
"方剂数": len(result["体质-方剂"].get(const_name, [])),
|
|
"药材数": len(result["体质-药材"].get(const_name, [])),
|
|
"穴位数": len(result["体质-穴位"].get(const_name, [])),
|
|
"药材分类数": len(result["体质-药材分类统计"].get(const_name, {})),
|
|
"核心药对数": len(result["体质-核心药对"].get(const_name, [])),
|
|
"穴位归经数": len(result["体质-穴位归经统计"].get(const_name, {}))
|
|
}
|
|
result["统计摘要"] = summary
|
|
|
|
# ============================================================
|
|
# 6. 输出 JSON
|
|
# ============================================================
|
|
print("[5/6] 写入 JSON...")
|
|
with open(OUT_JSON, "w", encoding="utf-8") as f:
|
|
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
print(f" ✓ 已写入: {OUT_JSON}")
|
|
|
|
|
|
# ============================================================
|
|
# 7. 输出 MD 报告 (追加方式)
|
|
# ============================================================
|
|
print("[6/6] 写入 MD 报告...")
|
|
|
|
def generate_md_section(const_name, top_formulas, top_herbs, herb_class_stats,
|
|
core_pairs, top_xuewei, meridian_stats):
|
|
"""生成一种体质的MD报告节"""
|
|
lines = []
|
|
lines.append(f"\n### {const_name}\n")
|
|
|
|
# --- 方剂 ---
|
|
lines.append("#### ① 体质-方剂 (TOP20)\n")
|
|
lines.append("| 排名 | 方剂名称 | 匹配得分 | 所含药材 |")
|
|
lines.append("|------|----------|----------|----------|")
|
|
for i, item in enumerate(top_formulas, 1):
|
|
herbs_str = "、".join(item["药材"][:8]) if item["药材"] else "—"
|
|
if len(item["药材"]) > 8:
|
|
herbs_str += "…"
|
|
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {herbs_str} |")
|
|
|
|
# --- 药材 ---
|
|
lines.append("\n#### ② 体质-药材 (TOP30)\n")
|
|
lines.append("| 排名 | 药材名称 | 匹配得分 | 药材分类 | 性味归经 |")
|
|
lines.append("|------|----------|----------|----------|----------|")
|
|
for i, item in enumerate(top_herbs, 1):
|
|
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {item['分类']} | {item['性味归经']} |")
|
|
|
|
# 药材分类统计
|
|
lines.append("\n**药材分类统计**\n")
|
|
lines.append("| 分类 | 数量 |")
|
|
lines.append("|------|------|")
|
|
for cls, cnt in herb_class_stats.items():
|
|
lines.append(f"| {cls} | {cnt} |")
|
|
|
|
# --- 核心药对 ---
|
|
lines.append("\n**核心药对 (TOP30)**\n")
|
|
lines.append("| 排名 | 药对 | 共现次数 |")
|
|
lines.append("|------|------|----------|")
|
|
for i, pair in enumerate(core_pairs[:15], 1): # 显示前15
|
|
lines.append(f"| {i} | {pair[0]} + {pair[1]} | {pair[2]} |")
|
|
|
|
# --- 穴位 ---
|
|
lines.append("\n#### ③ 体质-穴位 (TOP20)\n")
|
|
lines.append("| 排名 | 穴位名称 | 匹配得分 | 隶属经脉 | 主治 |")
|
|
lines.append("|------|----------|----------|----------|------|")
|
|
for i, item in enumerate(top_xuewei, 1):
|
|
zhuzhi_short = item['主治'][:40] + "…" if len(item['主治']) > 40 else item['主治']
|
|
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {item['隶属']} | {zhuzhi_short} |")
|
|
|
|
# 穴位归经统计
|
|
lines.append("\n**穴位归经统计**\n")
|
|
lines.append("| 经脉 | 数量 |")
|
|
lines.append("|------|------|")
|
|
for mer, cnt in meridian_stats.items():
|
|
lines.append(f"| {mer} | {cnt} |")
|
|
|
|
lines.append("\n---\n")
|
|
return "\n".join(lines)
|
|
|
|
|
|
# 构造MD内容
|
|
md_sections = []
|
|
md_sections.append("# 体质数据挖掘报告\n")
|
|
md_sections.append("## 一、疾病-症状关联分析(已有)\n\n*此部分为原有内容,此处省略* \n")
|
|
|
|
md_sections.append("\n## 二、体质-方剂挖掘\n")
|
|
for const_name in const_names:
|
|
lines = []
|
|
top_formulas = result["体质-方剂"].get(const_name, [])
|
|
lines.append(f"\n### {const_name}\n")
|
|
lines.append("| 排名 | 方剂名称 | 匹配得分 | 所含药材 |")
|
|
lines.append("|------|----------|----------|----------|")
|
|
for i, item in enumerate(top_formulas, 1):
|
|
herbs_str = "、".join(item["药材"][:8]) if item["药材"] else "—"
|
|
if len(item["药材"]) > 8:
|
|
herbs_str += "…"
|
|
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {herbs_str} |")
|
|
md_sections.append("\n".join(lines))
|
|
|
|
md_sections.append("\n## 三、体质-药材挖掘\n")
|
|
for const_name in const_names:
|
|
lines = []
|
|
top_herbs = result["体质-药材"].get(const_name, [])
|
|
herb_class_stats = result["体质-药材分类统计"].get(const_name, {})
|
|
core_pairs = result["体质-核心药对"].get(const_name, [])
|
|
|
|
lines.append(f"\n### {const_name}\n")
|
|
lines.append("**TOP30药材**\n")
|
|
lines.append("| 排名 | 药材名称 | 匹配得分 | 药材分类 | 性味归经 |")
|
|
lines.append("|------|----------|----------|----------|----------|")
|
|
for i, item in enumerate(top_herbs, 1):
|
|
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {item['分类']} | {item['性味归经']} |")
|
|
|
|
lines.append("\n**药材分类统计**\n")
|
|
lines.append("| 分类 | 数量 |")
|
|
lines.append("|------|------|")
|
|
for cls, cnt in herb_class_stats.items():
|
|
lines.append(f"| {cls} | {cnt} |")
|
|
|
|
lines.append("\n**核心药对 (TOP15)**\n")
|
|
lines.append("| 排名 | 药对 | 共现次数 |")
|
|
lines.append("|------|------|----------|")
|
|
for i, pair in enumerate(core_pairs[:15], 1):
|
|
lines.append(f"| {i} | {pair[0]} + {pair[1]} | {pair[2]} |")
|
|
|
|
md_sections.append("\n".join(lines))
|
|
|
|
md_sections.append("\n## 四、体质-穴位挖掘\n")
|
|
for const_name in const_names:
|
|
lines = []
|
|
top_xuewei = result["体质-穴位"].get(const_name, [])
|
|
meridian_stats = result["体质-穴位归经统计"].get(const_name, {})
|
|
|
|
lines.append(f"\n### {const_name}\n")
|
|
lines.append("**TOP20穴位**\n")
|
|
lines.append("| 排名 | 穴位名称 | 匹配得分 | 隶属经脉 | 主治 |")
|
|
lines.append("|------|----------|----------|----------|------|")
|
|
for i, item in enumerate(top_xuewei, 1):
|
|
zhuzhi_short = item['主治'][:40] + "…" if len(item['主治']) > 40 else item['主治']
|
|
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {item['隶属']} | {zhuzhi_short} |")
|
|
|
|
lines.append("\n**穴位归经统计**\n")
|
|
lines.append("| 经脉 | 数量 |")
|
|
lines.append("|------|------|")
|
|
for mer, cnt in meridian_stats.items():
|
|
lines.append(f"| {mer} | {cnt} |")
|
|
|
|
md_sections.append("\n".join(lines))
|
|
|
|
# 写入MD
|
|
with open(OUT_MD, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(md_sections))
|
|
print(f" ✓ 已写入: {OUT_MD}")
|
|
|
|
# ============================================================
|
|
# 8. 验证
|
|
# ============================================================
|
|
print("\n" + "="*60)
|
|
print("验证结果")
|
|
print("="*60)
|
|
|
|
# 验证JSON
|
|
with open(OUT_JSON, "r", encoding="utf-8") as f:
|
|
v = json.load(f)
|
|
print(f"\n✅ JSON 验证通过")
|
|
print(f" - 体质-方剂: {len(v['体质-方剂'])} 种体质")
|
|
print(f" - 体质-药材: {len(v['体质-药材'])} 种体质")
|
|
print(f" - 体质-穴位: {len(v['体质-穴位'])} 种体质")
|
|
print(f" - 体质-药材分类统计: {len(v['体质-药材分类统计'])} 种体质")
|
|
print(f" - 体质-核心药对: {len(v['体质-核心药对'])} 种体质")
|
|
print(f" - 体质-穴位归经统计: {len(v['体质-穴位归经统计'])} 种体质")
|
|
|
|
# 检查每种体质都有数据
|
|
for cn in const_names:
|
|
f_count = len(v["体质-方剂"].get(cn, []))
|
|
h_count = len(v["体质-药材"].get(cn, []))
|
|
x_count = len(v["体质-穴位"].get(cn, []))
|
|
print(f" {cn}: 方剂={f_count}, 药材={h_count}, 穴位={x_count}")
|
|
|
|
# 验证MD
|
|
md_size = os.path.getsize(OUT_MD)
|
|
print(f"\n✅ MD 验证通过 ({md_size} bytes)")
|
|
print(f"\n输出文件:")
|
|
print(f" 1. {OUT_JSON}")
|
|
print(f" 2. {OUT_MD}")
|
|
print("\n完成!")
|