Phase 0-2: Schema cleanup, typed relations, event-driven automation
- Phase 0: AGENTS.md cleanup (dedup quotes, renumber sections, merge qmd) - Phase 1: typed relations (manage-relations.py, graph-search.py, check-staleness.py, detect-conflicts.py) - Phase 2: frontmatter validator, weekly lint, knowledge promotion, git hooks - Fix .gitignore to track tools/ and .githooks/ - Fix git remote URL (remove plaintext token) - New wiki pages: 504 pages, 34 raw sources
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import io
|
||||
import json
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
import random
|
||||
|
||||
if sys.platform == "win32":
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
||||
|
||||
|
||||
class KnowledgeGraphUpdater:
|
||||
def __init__(self, canvas_path: Path, extracted_data_path: Path):
|
||||
self.canvas_path = canvas_path
|
||||
self.data_path = extracted_data_path
|
||||
self.canvas_data = self.load_canvas()
|
||||
self.extracted_data = self.load_extracted_data()
|
||||
|
||||
def load_canvas(self) -> Dict[str, Any]:
|
||||
try:
|
||||
with open(self.canvas_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error loading canvas: {str(e)}")
|
||||
return {}
|
||||
|
||||
def load_extracted_data(self) -> Dict[str, Any]:
|
||||
try:
|
||||
with open(self.data_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error loading extracted data: {str(e)}")
|
||||
return {}
|
||||
|
||||
def get_existing_institutions(self) -> set:
|
||||
institutions = set()
|
||||
for node in self.canvas_data.get("nodes", []):
|
||||
if node.get("type") == "text" and "机构档案" in node.get("id", ""):
|
||||
institutions.add(node["id"])
|
||||
return institutions
|
||||
|
||||
def generate_node_id(self, prefix: str, name: str) -> str:
|
||||
safe_name = name.replace(" ", "_").replace("/", "_").replace("\\", "_")[:20]
|
||||
return (
|
||||
f"{prefix}_{safe_name}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}"
|
||||
)
|
||||
|
||||
def create_institution_node(
|
||||
self, inst: Dict[str, Any], x: int, y: int
|
||||
) -> Dict[str, Any]:
|
||||
inst_name = inst.get("name", "Unknown")
|
||||
inst_type = inst.get("type", "Unknown")
|
||||
quality_score = inst.get("quality_score", 0)
|
||||
key_data = inst.get("key_data", "")
|
||||
|
||||
node_id = self.generate_node_id("node", inst_name)
|
||||
|
||||
node = {
|
||||
"id": node_id,
|
||||
"type": "text",
|
||||
"x": x + random.randint(-50, 50),
|
||||
"y": y + random.randint(-50, 50),
|
||||
"width": 300,
|
||||
"height": 120,
|
||||
"color": "1" if quality_score >= 85 else "2",
|
||||
"text": f"## {inst_name}\n**类型**: {inst_type}\n**质量分**: {quality_score:.1f}\n**核心数据**: {key_data[:50]}...",
|
||||
}
|
||||
|
||||
return node_id, node
|
||||
|
||||
def create_edge(
|
||||
self, source_id: str, target_id: str, label: str = ""
|
||||
) -> Dict[str, Any]:
|
||||
edge = {
|
||||
"id": f"edge_{source_id}_{target_id}",
|
||||
"from": source_id,
|
||||
"to": target_id,
|
||||
"label": label,
|
||||
}
|
||||
return edge
|
||||
|
||||
def add_new_institutions(self):
|
||||
new_nodes = []
|
||||
new_edges = []
|
||||
|
||||
institutions = self.extracted_data.get("reports", [])
|
||||
existing_institutions = self.get_existing_institutions()
|
||||
|
||||
group_positions = {
|
||||
"教师工具": {"x": 1500, "y": -850, "group": "group_edtech000001"},
|
||||
"协作辅导": {"x": 1850, "y": -750, "group": "group_edtech000001"},
|
||||
"K-12平台": {"x": 1500, "y": -650, "group": "group_edtech000001"},
|
||||
}
|
||||
|
||||
for report in institutions:
|
||||
for inst in report.get("institutions", []):
|
||||
inst_name = inst.get("name", "")
|
||||
inst_type = inst.get("type", "")
|
||||
|
||||
if inst_name and inst_name not in existing_institutions:
|
||||
position = group_positions.get(
|
||||
inst_type, {"x": 1200, "y": -500, "group": "group_reports00001"}
|
||||
)
|
||||
node_id, node = self.create_institution_node(
|
||||
inst, position["x"], position["y"]
|
||||
)
|
||||
|
||||
new_nodes.append(node)
|
||||
existing_institutions.add(inst_name)
|
||||
|
||||
if position.get("group"):
|
||||
edge = self.create_edge(
|
||||
position["group"], node_id, f"属于{inst_type}"
|
||||
)
|
||||
new_edges.append(edge)
|
||||
|
||||
if new_nodes:
|
||||
self.canvas_data["nodes"].extend(new_nodes)
|
||||
print(f"✅ Added {len(new_nodes)} new institution nodes")
|
||||
|
||||
if new_edges:
|
||||
existing_edges = self.canvas_data.get("edges", [])
|
||||
existing_edges.extend(new_edges)
|
||||
self.canvas_data["edges"] = existing_edges
|
||||
print(f"✅ Added {len(new_edges)} new edges")
|
||||
|
||||
return len(new_nodes), len(new_edges)
|
||||
|
||||
def update_metadata(self):
|
||||
today = datetime.date.today()
|
||||
week_number = today.isocalendar()[1]
|
||||
|
||||
total_institutions = sum(
|
||||
len(report.get("institutions", []))
|
||||
for report in self.extracted_data.get("reports", [])
|
||||
)
|
||||
|
||||
for node in self.canvas_data.get("nodes", []):
|
||||
if node.get("id") == "title001canvasv12":
|
||||
node["text"] = (
|
||||
f"# 全球教育AI知识图谱\n更新日期:{today} | W{week_number} | 机构总计:{total_institutions}家"
|
||||
)
|
||||
print(f"✅ Updated metadata in title node")
|
||||
break
|
||||
|
||||
def add_research_report_nodes(self):
|
||||
new_nodes = []
|
||||
reports = self.extracted_data.get("reports", [])
|
||||
existing_reports = set()
|
||||
|
||||
for node in self.canvas_data.get("nodes", []):
|
||||
if node.get("type") == "text" and "研究计划" in node.get("text", ""):
|
||||
existing_reports.add(node["id"])
|
||||
|
||||
report_count = 0
|
||||
base_y = -300
|
||||
base_x = 1600
|
||||
|
||||
for report in reports:
|
||||
theme = report.get("theme", "")
|
||||
week_num = report.get("week_number", "")
|
||||
|
||||
if theme and f"W{week_num}" not in existing_reports and report_count < 5:
|
||||
node_id = f"report_W{week_num}"
|
||||
y_pos = base_y + (report_count * 150)
|
||||
|
||||
node = {
|
||||
"id": node_id,
|
||||
"type": "text",
|
||||
"x": base_x + random.randint(-50, 50),
|
||||
"y": y_pos,
|
||||
"width": 400,
|
||||
"height": 100,
|
||||
"color": "6",
|
||||
"text": f"**W{week_num}**: {theme[:40]}...",
|
||||
}
|
||||
|
||||
new_nodes.append(node)
|
||||
existing_reports.add(f"W{week_num}")
|
||||
report_count += 1
|
||||
|
||||
if new_nodes:
|
||||
self.canvas_data["nodes"].extend(new_nodes)
|
||||
print(f"✅ Added {len(new_nodes)} research report nodes")
|
||||
|
||||
reports_group_id = "group_reports00001"
|
||||
for node_id in [node["id"] for node in new_nodes]:
|
||||
edge = self.create_edge(reports_group_id, node_id, "研究")
|
||||
existing_edges = self.canvas_data.get("edges", [])
|
||||
existing_edges.append(edge)
|
||||
self.canvas_data["edges"] = existing_edges
|
||||
|
||||
print(f"✅ Added {len(new_nodes)} edges to research reports group")
|
||||
|
||||
return len(new_nodes)
|
||||
|
||||
def save_updated_canvas(self):
|
||||
output_path = (
|
||||
self.canvas_path.parent
|
||||
/ f"全球教育AI机构关系图谱_v{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.canvas"
|
||||
)
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(self.canvas_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"✅ Updated canvas saved to: {output_path}")
|
||||
return output_path
|
||||
|
||||
def generate_update_report(self) -> str:
|
||||
update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
report = f"""# 知识图谱更新报告
|
||||
|
||||
## 基本信息
|
||||
|
||||
- **更新时间**: {update_time}
|
||||
- **源文件**: {self.canvas_path.name}
|
||||
- **数据来源**: {self.data_path.name}
|
||||
- **新版本**: v{datetime.datetime.now().strftime("%Y%m%d")}
|
||||
|
||||
## 更新内容
|
||||
|
||||
### 新增节点
|
||||
|
||||
- **机构档案节点**: 基于每周报告提取的新机构
|
||||
- **研究报告节点**: 近期研究计划/报告主题
|
||||
|
||||
### 更新的元数据
|
||||
|
||||
- **标题节点**: 更新日期和机构总数
|
||||
- **版本号**: 自动递增
|
||||
|
||||
## 图谱统计
|
||||
|
||||
- **总节点数**: {len(self.canvas_data.get("nodes", []))}
|
||||
- **总边数**: {len(self.canvas_data.get("edges", []))}
|
||||
- **分组数**: {len([n for n in self.canvas_data.get("nodes", []) if n.get("type") == "group"])}
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. 手动检查节点布局,调整重叠
|
||||
2. 添加更多关系边以增强连接性
|
||||
3. 定期更新以保持图谱时效性
|
||||
|
||||
---
|
||||
|
||||
*更新工具: Knowledge Graph Updater*
|
||||
*自动生成时间: {update_time}*
|
||||
"""
|
||||
|
||||
return report
|
||||
|
||||
def save_update_report(self, report_path: Path):
|
||||
report_content = self.generate_update_report()
|
||||
report_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
f.write(report_content)
|
||||
|
||||
print(f"✅ Update report saved to: {report_path}")
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Knowledge Graph Visualization Update Tool")
|
||||
print("=" * 60)
|
||||
print(f"Execution time: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print()
|
||||
|
||||
project_dir = Path(__file__).parent.parent
|
||||
canvas_path = project_dir / "全球教育AI机构关系图谱.canvas"
|
||||
extracted_data_path = (
|
||||
project_dir / "outputs" / "weekly_reports_extraction_2026-04-16.json"
|
||||
)
|
||||
|
||||
if not canvas_path.exists():
|
||||
print(f"❌ Canvas file not found: {canvas_path}")
|
||||
return
|
||||
|
||||
if not extracted_data_path.exists():
|
||||
print(f"❌ Extracted data file not found: {extracted_data_path}")
|
||||
print("Please run extract_weekly_reports.py first")
|
||||
return
|
||||
|
||||
updater = KnowledgeGraphUpdater(canvas_path, extracted_data_path)
|
||||
|
||||
print("Step 1: Adding new institution nodes...")
|
||||
new_insts, new_edges = updater.add_new_institutions()
|
||||
|
||||
print("\nStep 2: Adding research report nodes...")
|
||||
new_reports = updater.add_research_report_nodes()
|
||||
|
||||
print("\nStep 3: Updating metadata...")
|
||||
updater.update_metadata()
|
||||
|
||||
print("\nStep 4: Saving updated canvas...")
|
||||
output_canvas = updater.save_updated_canvas()
|
||||
|
||||
print("\nStep 5: Generating update report...")
|
||||
report_path = (
|
||||
output_canvas.parent
|
||||
/ "outputs"
|
||||
/ f"knowledge_graph_update_report_{datetime.date.today()}.md"
|
||||
)
|
||||
updater.save_update_report(report_path)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("Update Complete")
|
||||
print("=" * 60)
|
||||
print(f"New institutions: {new_insts}")
|
||||
print(f"New edges: {new_edges}")
|
||||
print(f"New research reports: {new_reports}")
|
||||
print(f"Output canvas: {output_canvas.name}")
|
||||
print(f"Update report: {report_path.name}")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user