Files
health/大医网/05_脚本工具/build_bridging.py
T

403 lines
15 KiB
Python

#!/usr/bin/env python3
"""
大医网「症状→方剂/药材/穴位」多段桥接关联 及 关联融合总览统计
"""
import json
import os
import sys
import math
from collections import defaultdict
BASE = "/home/songyi/Documents/ai_agent_scraper_study/data/大医网/03_关联融合"
CROSS_DIR = os.path.join(BASE, "交叉关联分析")
OUTPUT_DIR = CROSS_DIR
# ============================================================
# PART 1: 症状→方剂/药材/穴位 桥接
# ============================================================
def load_symptom_disease():
"""Load 症状-疾病关联.json - format: {症状: [[疾病, 置信度(0-100), [标签]], ...]}"""
path = os.path.join(CROSS_DIR, "症状-疾病关联.json")
print(f" 加载 {path} ...")
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
# Normalize confidence: values 35-100 range, divide by 100 to get 0-1
normalized = {}
for symptom, diseases in data.items():
normalized[symptom] = []
for entry in diseases:
disease_name = entry[0]
conf = entry[1]
# Normalize to 0-1
if conf > 1:
conf = conf / 100.0
normalized[symptom].append((disease_name, conf))
return normalized
def load_disease_target(file_key, target_name):
"""Load disease->target mapping: {disease: [(target, conf_0_1), ...]}"""
fname_map = {
'formula': '疾病-方剂关联数据.json',
'herb': '疾病-药材关联数据.json',
'acupoint': '疾病-穴位关联数据.json'
}
topkey_map = {
'formula': 'disease_to_formulas',
'herb': 'disease_to_herbs',
'acupoint': 'disease_to_acupoints'
}
path = os.path.join(CROSS_DIR, fname_map[file_key])
print(f" 加载 {path} ...")
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data[topkey_map[file_key]]
def build_bridging(sym_disease, disease_target, target_label):
"""
Symptom -> Disease -> Target bridging.
sym_disease: {symptom: [(disease, conf_sd), ...]}
disease_target: {disease: [(target, conf_dt), ...]}
Returns: {symptom: [{target名: ..., 桥接路径: ..., 得分: ...}, ...]}
"""
print(f" 构建症状→{target_label}桥接 ...")
result = {}
for symptom, disease_list in sym_disease.items():
# For each symptom, collect all paths
target_scores = defaultdict(float)
target_paths = defaultdict(list)
for disease_name, conf_sd in disease_list:
if disease_name not in disease_target:
continue
for target_item in disease_target[disease_name]:
target_name = target_item[0]
conf_dt = target_item[1] if len(target_item) > 1 else 1.0
# Chain score = symptom-disease confidence * disease-target confidence
chain_score = conf_sd * conf_dt
path_str = f"{symptom}→{disease_name}→{target_name}"
if chain_score > target_scores[target_name]:
target_scores[target_name] = chain_score
target_paths[target_name] = path_str
if target_scores:
# Sort by score descending, take TOP20
sorted_targets = sorted(target_scores.items(), key=lambda x: -x[1])
top20 = sorted_targets[:20]
result[symptom] = [
{
target_label: tname,
"桥接路径": target_paths[tname],
"得分": round(score, 4)
}
for tname, score in top20
]
return result
def do_bridging():
print("=" * 60)
print("PART 1: 症状→方剂/药材/穴位 桥接关联")
print("=" * 60)
# Load symptoms->disease
sym_disease = load_symptom_disease()
print(f" 症状总数: {len(sym_disease)}")
# Load disease->formula/herb/acupoint
disease_formula = load_disease_target('formula', '方剂名')
disease_herb = load_disease_target('herb', '药材名')
disease_acupoint = load_disease_target('acupoint', '穴位名')
print(f" 疾病→方剂: {len(disease_formula)} 疾病")
print(f" 疾病→药材: {len(disease_herb)} 疾病")
print(f" 疾病→穴位: {len(disease_acupoint)} 疾病")
# Build bridges
sym_formula = build_bridging(sym_disease, disease_formula, "方剂名")
sym_herb = build_bridging(sym_disease, disease_herb, "药材名")
sym_acupoint = build_bridging(sym_disease, disease_acupoint, "穴位名")
print(f" 桥接症状→方剂: {len(sym_formula)} 症状")
print(f" 桥接症状→药材: {len(sym_herb)} 症状")
print(f" 桥接症状→穴位: {len(sym_acupoint)} 症状")
# Count unique items
all_formulas = set()
for symptom, items in sym_formula.items():
for item in items:
all_formulas.add(item['方剂名'])
all_herbs = set()
for symptom, items in sym_herb.items():
for item in items:
all_herbs.add(item['药材名'])
all_acupoints = set()
for symptom, items in sym_acupoint.items():
for item in items:
all_acupoints.add(item['穴位名'])
total_paths = sum(len(v) for v in sym_formula.values()) + \
sum(len(v) for v in sym_herb.values()) + \
sum(len(v) for v in sym_acupoint.values())
summary = {
"桥接症状数": len(set(list(sym_formula.keys()) + list(sym_herb.keys()) + list(sym_acupoint.keys()))),
"桥接方剂数": len(all_formulas),
"桥接药材数": len(all_herbs),
"桥接穴位数": len(all_acupoints),
"总桥接路径": total_paths
}
print(f" 统计摘要: {json.dumps(summary, ensure_ascii=False)}")
# Build output structure
output = {
"症状-方剂": sym_formula,
"症状-药材": sym_herb,
"症状-穴位": sym_acupoint,
"统计摘要": summary
}
output_path = os.path.join(OUTPUT_DIR, "症状-方剂药材穴位桥接.json")
print(f" 写入 {output_path} ...")
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)
return output
# ============================================================
# PART 2: 关联融合总览统计
# ============================================================
def count_records(obj):
"""Count 'records' in a JSON structure - heuristic for different formats"""
if isinstance(obj, list):
return len(obj)
elif isinstance(obj, dict):
# Check common patterns
# Pattern 1: values are arrays (like symptom->disease mapping)
array_values = [v for v in obj.values() if isinstance(v, (list, dict))]
if array_values:
# Sum of array lengths, or count of dict values
total = 0
for v in obj.values():
if isinstance(v, list):
total += len(v)
elif isinstance(v, dict):
total += len(v)
return total
# Pattern 2: top-level keys as records
return len(obj)
return 0
def get_content_type(filename):
"""Determine content description from filename"""
descriptions = {
'01_配方-药材关联表.json': '配方-药材映射',
'02_药材-配方关联表.json': '药材-配方映射',
'03_Top50高频药材.json': '高频药材统计',
'04_Top50大复方.json': '大复方统计',
'05_药材分类统计.json': '药材分类统计',
'06_总摘要.json': '关联分析摘要',
'症状-疾病关联.json': '症状→疾病关联(原始)',
'疾病-症状关联.json': '疾病→症状关联',
'症状-疾病_高置信关联.json': '症状→疾病高置信关联',
'疾病-方剂关联数据.json': '疾病→方剂关联',
'疾病-药材关联数据.json': '疾病→药材关联',
'疾病-穴位关联数据.json': '疾病→穴位关联',
'四库交叉关联数据.json': '四库交叉关联总览',
'三库关联总览.json': '三库关联统计',
'术语与症状深度关联分析.json': '术语-症状深度关联',
'症状_全部.json': '症状全集',
'症状_列表.json': '症状列表',
'症状_归档统计.json': '症状归档统计',
'症状->疾病推算模型.json': '症状→疾病推算模型',
'症状->疾病->四库桥接数据.json': '症状→疾病→四库桥接',
'疾病->增强方剂药材穴位反查数据.json': '疾病→方剂药材穴位反查',
'疾病-症状严谨关联数据.json': '疾病-症状严谨关联',
'体质-疾病症状挖掘.json': '体质-疾病症状挖掘',
'体质-方剂药材穴位挖掘.json': '体质-方剂药材穴位挖掘',
'体质-药膳推荐体系.json': '体质-药膳推荐体系',
'体质-知识索引关联.json': '体质-知识索引关联',
'咳嗽哮喘_关联数据.json': '咳嗽哮喘专题关联',
'心脑血管_关联数据.json': '心脑血管专题关联',
'脾胃调理_关联数据.json': '脾胃调理专题关联',
'失眠不寐_关联数据.json': '失眠不寐专题关联',
'女性补气血_关联数据.json': '女性补气血专题关联',
'症状-方剂药材穴位桥接.json': '症状→方剂/药材/穴位桥接',
'关联融合总览.json': '关联融合总览',
}
# Substring matching
for key, desc in descriptions.items():
if filename == key or filename.endswith(key):
return desc
# Generic detection
if '方剂' in filename and '药材' in filename:
return '配方-药材关联'
if '药材' in filename and '配方' in filename:
return '药材-配方关联'
if '高频' in filename:
return '高频统计'
if '分类' in filename:
return '分类统计'
if '摘要' in filename or '总汇' in filename or '汇总' in filename:
return '分析摘要'
return '关联数据'
def get_file_stats(filepath):
"""Get file size in KB and record count"""
size_kb = os.path.getsize(filepath) / 1024.0
size_kb = round(size_kb, 1)
try:
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
records = count_records(data)
except:
records = 0
return size_kb, records
def scan_directory(dirpath, max_depth=1):
"""Scan a directory for JSON files and return stats"""
results = []
total_size_kb = 0
total_records = 0
for fname in sorted(os.listdir(dirpath)):
if not fname.endswith('.json'):
continue
fpath = os.path.join(dirpath, fname)
if os.path.isdir(fpath):
continue
size_kb, records = get_file_stats(fpath)
content = get_content_type(fname)
results.append({
"文件名": fname,
"大小KB": size_kb,
"记录数": records,
"内容": content
})
total_size_kb += size_kb
total_records += records
return results, total_size_kb, total_records
def scan_deep_mine():
"""Scan depth mining directory structure"""
deep_dir = os.path.join(BASE, "深度挖掘迭代")
versions = {}
total_files = 0
for item in sorted(os.listdir(deep_dir)):
item_path = os.path.join(deep_dir, item)
if not os.path.isdir(item_path):
continue
json_files = [f for f in os.listdir(item_path) if f.endswith('.json')]
count = len(json_files)
total_files += count
versions[item] = count
return total_files, versions
def build_overview():
print("\n" + "=" * 60)
print("PART 2: 关联融合总览统计")
print("=" * 60)
# 1. Scan 关联分析/
analysis_dir = os.path.join(BASE, "关联分析")
analysis_files, analysis_size, analysis_records = scan_directory(analysis_dir)
print(f" 关联分析: {len(analysis_files)} 文件, {analysis_size:.1f} KB")
# 2. Scan 交叉关联分析/
cross_files, cross_size, cross_records = scan_directory(CROSS_DIR)
print(f" 交叉关联分析: {len(cross_files)} 文件, {cross_size:.1f} KB")
# 3. Scan 深度挖掘迭代/
deep_total, versions = scan_deep_mine()
print(f" 深度挖掘迭代: {deep_total} 文件, {len(versions)} 版本")
# 4. 其他体质文件 in 交叉关联分析
tizhi_files = [f for f in cross_files if '体质' in f['文件名']]
print(f" 体质相关文件: {len(tizhi_files)}")
total_files = len(analysis_files) + len(cross_files) + deep_total
total_size_mb = (analysis_size + cross_size) / 1024.0
# Get deep_mine subdirectory sizes
deep_dir = os.path.join(BASE, "深度挖掘迭代")
total_deep_size_kb = 0
for root, dirs, files in os.walk(deep_dir):
for f in files:
if f.endswith('.json'):
total_deep_size_kb += os.path.getsize(os.path.join(root, f)) / 1024.0
total_size_mb = (analysis_size + cross_size + total_deep_size_kb) / 1024.0
overview = {
"总览": {
"子目录数": 3,
"总文件数": total_files,
"总数据量MB": round(total_size_mb, 2)
},
"关联分析": analysis_files,
"交叉关联分析": cross_files,
"深度挖掘迭代": {
"总文件数": deep_total,
"版本数": len(versions)
}
}
# Add version details
for ver_name, ver_count in sorted(versions.items()):
# Strip the '分析_' prefix since we already add '分析_' in the key
short_name = ver_name.replace('分析_', '', 1) if ver_name.startswith('分析_') else ver_name
overview["深度挖掘迭代"][f"分析_{short_name}文件数"] = ver_count
output_path = os.path.join(OUTPUT_DIR, "关联融合总览.json")
print(f" 写入 {output_path} ...")
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(overview, f, ensure_ascii=False, indent=2)
return overview
# ============================================================
# MAIN
# ============================================================
if __name__ == "__main__":
# Part 1: bridging
bridge_result = do_bridging()
# Part 2: overview
overview_result = build_overview()
# Verify
print("\n" + "=" * 60)
print("验证输出文件")
print("=" * 60)
for fname in ["症状-方剂药材穴位桥接.json", "关联融合总览.json"]:
fpath = os.path.join(OUTPUT_DIR, fname)
if os.path.exists(fpath):
fsize = os.path.getsize(fpath)
print(f" ✓ {fname}: {fsize:,} bytes ({fsize/1024:.1f} KB)")
else:
print(f" ✗ {fname}: NOT FOUND")
print("\n完成!")