import json
import sys
from pathlib import Path
def generate_html_visualization(result_data: dict, cases_data: dict) -> str:
merged_demands = result_data.get("merged_demands", [])
mount_decisions = result_data.get("mount_decisions", [])
# 构建 decision_map:demand_name → mounted_nodes
decision_map = {}
for d in mount_decisions:
dec = d.get("decision", {})
if isinstance(dec, dict):
decision_map[d["demand_name"]] = dec.get("mounted_nodes", [])
else:
decision_map[d["demand_name"]] = []
# 优先从 result_data 读取 case_info,如果没有则从 cases_data 构建
if "case_info" in result_data:
case_map = result_data["case_info"]
else:
case_map = {}
for c in cases_data.get("cases", []):
case_map[c["case_id"]] = {
"title": c.get("title") or c.get("video_title") or c.get("post_title", ""),
"images": c.get("images") or c.get("effect_images", []),
"link": c.get("source_link") or c.get("video_url") or c.get("post_url", "")
}
nodes_js = []
edges_js = []
node_id = 0
COLOR_CASE_BG = "#e3f2fd"
COLOR_CASE_BORDER = "#2196f3"
COLOR_DEMAND_BG = "#fff3e0"
COLOR_DEMAND_BORDER = "#ff9800"
for md in merged_demands:
demand_id = node_id
node_id += 1
dn = md["demand_name"]
# 从挂载决策中获取最终选择的节点
mounted = decision_map.get(dn, [])
node_tags = []
for n in mounted:
node_tags.append(f"{n['name']}")
tags_str = " | ".join(node_tags) if node_tags else "无挂载节点"
demand_label = f"{dn}\n\n[{tags_str}]"
demand_title_html = f"""
需求详情
名称: {dn}
描述: {md["description"]}
挂载节点: {tags_str}
{"".join(f"
· {n['name']}({n.get('source_type','')}) — {n.get('reason','')}
" for n in mounted)}
"""
nodes_js.append({
"id": demand_id,
"label": demand_label,
"title": demand_title_html,
"group": "demand",
"level": 0 # 核心改动1:Demand 改为 Level 0 (根节点)
})
for cid in md.get("source_case_ids", []):
case_id = node_id
node_id += 1
# 类型转换:case_info 的 key 可能是字符串
cid_str = str(cid)
case_info = case_map.get(cid_str) or case_map.get(cid, {"title": f"Case {cid}", "images": [], "link": ""})
title_short = case_info['title'][:20] + "..." if len(case_info['title']) > 20 else case_info['title']
case_label = f"Case {cid}\n{title_short}"
img_html = ""
if case_info['images']:
img_url = case_info['images'][0]
img_html = f'
'
case_title_html = f"""
帖子详情 (点击跳转)
ID: {cid}
标题: {case_info["title"]}
{img_html}
"""
nodes_js.append({
"id": case_id,
"label": case_label,
"title": case_title_html,
"url": case_info['link'],
"group": "case",
"level": 1 # 核心改动2:Case 改为 Level 1 (叶子节点)
})
# 核心改动3:在数据上把连线方向反转为 Demand -> Case
edges_js.append({"from": demand_id, "to": case_id})
html = f"""
Case → Demand 语义映射图
"""
return html
def main():
if len(sys.argv) < 2:
print("用法: python visualize_results.py ")
sys.exit(1)
result_path = Path(sys.argv[1])
if not result_path.exists():
print(f"错误: 文件不存在: {result_path}")
sys.exit(1)
with open(result_path, "r", encoding="utf-8") as f:
result_data = json.load(f)
cases_path = result_path.parent / "02_cases.json"
if not cases_path.exists():
cases_data = {"cases": []}
else:
with open(cases_path, "r", encoding="utf-8") as f:
cases_data = json.load(f)
html = generate_html_visualization(result_data, cases_data)
output_path = result_path.parent / "match_nodes_graph.html"
with open(output_path, "w", encoding="utf-8") as f:
f.write(html)
print(f"[OK] Visualization generated: {output_path}")
if __name__ == "__main__":
main()