import sys, io, json, re sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") import os SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) PROJECT_ROOT = os.path.dirname(SCRIPT_DIR) with open(os.path.join(PROJECT_ROOT, "data", "raw-messages", "all_messages_combined.json"), "r", encoding="utf-8") as f: data = json.load(f) feishu_re = re.compile(r"https?://[a-zA-Z0-9.-]+\.feishu\.cn/(?:wiki|docx)/[A-Za-z0-9]+") file_re = re.compile(r"\[文件\]\s+(.+?)\s+fileId:\s+(\S+)") kimi_re = re.compile(r"https?://[a-zA-Z0-9.-]+\.ok\.kimi\.link/\S*") weixin_re = re.compile(r"https?://mp\.weixin\.qq\.com/\S+") github_re = re.compile(r"https?://github\.com/\S+") # Person registry persons = {} # Link registry all_links = [] # File registry all_files = [] # Knowledge entities entities = [] # Relationships relationships = [] def get_or_create_person(name, dingtalk_id=None): if name not in persons: persons[name] = {"name": name, "dingtalk_id": dingtalk_id, "groups": [], "contributions": 0, "topics": set()} persons[name]["contributions"] += 1 if dingtalk_id: persons[name]["dingtalk_id"] = dingtalk_id return persons[name] for group_name, msgs in data.items(): group_label = "dc战略问题研究院" if group_name == "dc" else "创新组" for msg in msgs: content = msg.get("content", "") sender = msg.get("sender", "unknown") time = msg.get("createTime", "") sender_id = msg.get("senderOpenDingTalkId", "") person = get_or_create_person(sender, sender_id) if group_label not in person["groups"]: person["groups"].append(group_label) # Extract feishu links for link in feishu_re.findall(content): entry = { "url": link, "sender": sender, "time": time, "group": group_label, "type": "wiki" if "/wiki/" in link else "docx", "domain": link.split("/")[2] } # Try to get context from surrounding text ctx = content.replace(link, "").strip()[:100] if ctx: entry["context"] = ctx all_links.append(entry) # Extract file attachments fm = file_re.search(content) if fm: all_files.append({ "name": fm.group(1), "fileId": fm.group(2), "sender": sender, "time": time, "group": group_label }) # Extract Kimi links for link in kimi_re.findall(content): desc = content.split("https")[0].strip()[:100] all_links.append({ "url": link, "sender": sender, "time": time, "group": group_label, "type": "kimi", "domain": "kimi.link", "context": desc }) # Extract WeChat articles for link in weixin_re.findall(content): desc = content.split("https")[0].strip()[:100] all_links.append({ "url": link, "sender": sender, "time": time, "group": group_label, "type": "weixin_article", "domain": "mp.weixin.qq.com", "context": desc }) # Deduplicate links by URL unique_links = {} for link in all_links: url = link["url"] if url not in unique_links: unique_links[url] = {**link, "shared_count": 1} else: unique_links[url]["shared_count"] += 1 # Build knowledge graph graph = { "metadata": { "generated": "2026-06-02", "groups": ["dc战略问题研究院", "创新组"], "time_range": "2026-05-01 ~ 2026-06-02", "total_messages": len(data["dc"]) + len(data["cx"]) }, "persons": {}, "documents": [], "files": [], "topics": [], "relationships": [] } # Process persons for name, p in persons.items(): graph["persons"][name] = { "name": name, "groups": p["groups"], "contributions": p["contributions"], "dingtalk_id": p.get("dingtalk_id", "") } # Process documents (feishu links) domain_map = { "dianchukeji.feishu.cn": "dianchukeji (公司飞书)", "fcnlycv6dd0w.feishu.cn": "fcnlycv6dd0w (研究组飞书)", "ocnmca6f1o0p.feishu.cn": "ocnmca6f1o0p (另一飞书空间)", "my.feishu.cn": "my.feishu.cn (个人飞书)" } for url, link in unique_links.items(): doc = { "url": url, "type": link["type"], "domain": domain_map.get(link["domain"], link["domain"]), "shared_by": link["sender"], "first_shared": link["time"], "group": link["group"], "context": link.get("context", ""), "share_count": link["shared_count"] } graph["documents"].append(doc) # Process files seen_files = set() for f in all_files: key = f["fileId"] if key not in seen_files: seen_files.add(key) graph["files"].append(f) # Build topic clusters # Group documents by sender and domain to identify topic clusters sender_domains = {} for doc in graph["documents"]: key = (doc["shared_by"], doc["domain"]) if key not in sender_domains: sender_domains[key] = [] sender_domains[key].append(doc) # Identify main topics based on content analysis topics = [ { "name": "《我的花园世界》深度研究", "description": "围绕《我的花园世界》的全方位研究,包括产品机制、商业化、资源经济、竞品对比", "key_people": ["李志健", "黄静雯", "陈楚真", "傅明游"], "documents_count": 0, "files": ["主报告-我的花园世界.html", "2026-05-25_report_my_garden_world_us_project.html", "花园项目对策划的启示.md", "进一步的思考.md", "花园世界产品策略_GOS补充信息.md", "花园世界等相关产品25.10.28.xlsx"], "key_concepts": ["低压经营", "极致消耗", "非战斗GVG", "社交拉氪", "大通服", "腰部底座放大"] }, { "name": "云湖工作室项目选择", "description": "广州云湖工作室第一个转型项目的方向选择分析", "key_people": ["黄静雯"], "documents_count": 0, "files": ["2026-05-yunhu-project-selection-strategy-report.html"], "key_concepts": ["2个月MVP", "AI美术适配", "素材CPI验证", "欧美化花园经营"] }, { "name": "Game Analyst Agent 工具", "description": "通用产品分析自动化工具,自动探索游戏界面、提取机制与数值", "key_people": ["黄静雯"], "documents_count": 0, "files": ["game-analyst-agent-manual.html", "game-analyst-agent.zip"], "key_concepts": ["自动界面探索", "pHash去重", "9级决策链", "多游戏对比", "决策报告生成"] }, { "name": "战略思维与方法论", "description": "李志健关于幸存者偏差、筹码管理、生长逻辑的战略反思", "key_people": ["李志健"], "documents_count": 0, "files": ["取败之道.md", "模型报告.html"], "key_concepts": ["幸存者偏差", "筹码管理", "正期望博弈", "生长逻辑", "非对称博弈"] }, { "name": "AI落地与组织变革", "description": "AI在游戏研发中的实际落地、效率提升与组织变革讨论", "key_people": ["李志健", "林峰", "汪雄军", "上官成", "安东"], "documents_count": 0, "files": [], "key_concepts": ["AI军备竞赛", "端到端提效", "组织变革", "AI工作小组"] }, { "name": "创新组日报与研发进展", "description": "创新组成员的每日工作日报,涵盖二合类游戏研发、AI工具使用", "key_people": ["王雨默", "韦译", "刘鹏", "卓泽", "徐锐"], "documents_count": 0, "files": ["工作流总结-王雨默.html"], "key_concepts": ["二合类游戏", "AI关卡生成", "UI编辑器", "数值平衡", "竞品分析"] }, { "name": "战略组日报与知识产出", "description": "战略研究组成员每日提交的研究日报", "key_people": ["黄静雯", "陈楚真", "张家振", "夏莲", "莫润麟", "胡辉俊", "汪季"], "documents_count": 0, "files": [], "key_concepts": ["竞品拆解", "知识卡片", "MECH/REPORT/ORG卡片", "Agent架构"] } ] graph["topics"] = topics # Build relationships rels = [] # Person -> Topic relationships for topic in topics: for person in topic["key_people"]: rels.append({ "source": person, "target": topic["name"], "type": "contributes_to", "weight": 1 }) # Person -> Person relationships (co-contribution in same group) for group_name, msgs in data.items(): group_label = "dc战略问题研究院" if group_name == "dc" else "创新组" msg_senders = set(m.get("sender", "") for m in msgs) for p1 in msg_senders: for p2 in msg_senders: if p1 < p2: rels.append({ "source": p1, "target": p2, "type": "co_group_member", "group": group_label, "weight": 1 }) graph["relationships"] = rels # Save graph with open(os.path.join(PROJECT_ROOT, "output", "knowledge-graph", "knowledge_graph.json"), "w", encoding="utf-8") as f: json.dump(graph, f, ensure_ascii=False, indent=2, default=str) # Print summary print("=== Knowledge Graph Summary ===") print("Persons: %d" % len(graph["persons"])) print("Documents (feishu links): %d" % len(graph["documents"])) print("Files: %d" % len(graph["files"])) print("Topics: %d" % len(graph["topics"])) print("Relationships: %d" % len(graph["relationships"])) print("\n=== Persons ===") for name, p in sorted(graph["persons"].items(), key=lambda x: -x[1]["contributions"]): print(" %s: %d msgs, groups=%s" % (name, p["contributions"], ", ".join(p["groups"]))) print("\n=== Documents by Domain ===") domain_counts = {} for doc in graph["documents"]: d = doc["domain"] domain_counts[d] = domain_counts.get(d, 0) + 1 for d, c in sorted(domain_counts.items(), key=lambda x: -x[1]): print(" %s: %d links" % (d, c)) print("\n=== Files ===") for f in graph["files"]: print(" %s (by %s, %s)" % (f["name"], f["sender"], f["time"])) print("\n=== Topics ===") for t in graph["topics"]: print(" %s: %s" % (t["name"], t["description"][:60])) print(" Key people: %s" % ", ".join(t["key_people"])) print(" Key concepts: %s" % ", ".join(t["key_concepts"][:4])) print("\nSaved to knowledge_graph.json")