#!/usr/bin/env python3 """从实测产物构建前端展示种子:NeuroPulse (project 5201)。""" from __future__ import annotations import csv import json import os import re import subprocess from datetime import datetime from pathlib import Path ROOT = Path(__file__).resolve().parents[1] OUT = ROOT / "web" / "src" / "data" / "seeds" / "neuropulseSeed.json" PROJECT_ID = 5201 PROJECT_NAME = "神经外科双频超声吸引系统 (CUSA-NeuroPulse)" DEVICE_TYPE = "超声软组织手术吸引设备" DOC_MAP = { "intake": { "work_dir": "data/dev/workspaces/bizdev/intake_1788920542147409000", "title": "用户初始需求收集表", "stage": "V0.1", "state": "approved", "summary": "CUSA-NeuroPulse 立项需求摸底与临床预期用途基线(真实 MiniMax 产出)。", }, "research": { "work_dir": "data/dev/workspaces/bizdev/research_1788921024562992000", "title": "调研报告(竞品、市场与法规初筛)", "stage": "V0.5", "state": "approved", "summary": "竞品/市场/法规初筛调研报告(真实 MiniMax 产出)。", }, "feasibility_dev": { "work_dir": "data/dev/workspaces/bizdev/feasibility_dev_1788922272891355000", "title": "设计开发可行性评估报告", "stage": "V0.5", "state": "approved", "summary": "设计开发可行性评估(真实 MiniMax 产出)。", }, "feasibility_reg": { "work_dir": "data/dev/workspaces/bizdev/feasibility_reg_1788922655324377000", "title": "注册路径可行性评估报告", "stage": "V0.5", "state": "approved", "summary": "NMPA 三类创新通道注册路径可行性(已终审批准)。", }, "user_requirements": { "work_dir": "data/dev/workspaces/bizdev/user_requirements_1788923266008791000", "title": "用户需求表", "stage": "V0.5", "state": "reviewing", "summary": "用户需求表 UR(真实 MiniMax 产出,审核中)。", }, "prelim_design": { "work_dir": "data/dev/workspaces/bizdev/prelim_design_1788923504095801000", "title": "系统初步设计方案", "stage": "V0.5", "state": "approving", "summary": "系统初步设计方案(真实 MiniMax 产出,待批准)。", }, "cdo_quote": { "work_dir": "data/dev/workspaces/bizdev/cdo_quote_1788924027615053000", "title": "CDO商务报价单", "stage": "V1.0", "state": "drafting", "summary": "CDO 商务报价与工时拆解(真实 MiniMax 产出)。", }, } def sid(uid: int) -> str: return f"sess-neuropulse-u{uid}" def read_md(rel: str) -> str: p = ROOT / rel / "output.md" if not p.exists(): return f"# 缺失产物\n\n路径不存在: `{rel}/output.md`" return p.read_text(encoding="utf-8") def load_db_messages(): cmd = [ "docker", "exec", "shuimu-context-pg-dev", "psql", "-U", "app", "-d", "agent_system_dev", "-c", r"\copy (SELECT session_id, role, content, COALESCE(intent,''), created_at FROM chat_messages WHERE session_id LIKE 'sess_full_p5201%' ORDER BY id) TO STDOUT WITH CSV", ] raw = subprocess.check_output(cmd, text=True) rows = list(csv.reader(raw.splitlines())) # map backend session -> UI session mapped = [] for sess, role, content, intent, created in rows: uid = 1 if sess.endswith("_u2"): uid = 2 elif sess.endswith("_u3"): uid = 3 mapped.append( { "session_id": sid(uid), "role": role, "content": content, "intent": intent, "created_at": created, } ) return mapped def load_early_dialogues_from_results(): path = Path("/tmp/full_lifecycle_p5201_results.json") if not path.exists(): return [] data = json.loads(path.read_text(encoding="utf-8")) out = [] for d in data.get("dialogues") or []: uid = d.get("user_id") or 1 name = d.get("user") or "张工" # skip empty timeout user_msg = d.get("input") or "" reply = d.get("reply") or "" intent = d.get("intent") or "" if not user_msg: continue out.append({"uid": uid, "name": name, "role": "user", "content": user_msg, "intent": ""}) if reply: out.append( { "uid": uid, "name": "水木东方协同参谋", "role": "assistant", "content": reply, "intent": intent, } ) elif "生成《用户初始需求收集表》" in user_msg: out.append( { "uid": uid, "name": "水木东方协同参谋", "role": "assistant", "content": ( "已为您启动《用户初始需求收集表》编制任务。智能体小队已完成立项画像整理," "正式稿已写入交付物大盘(doc-neuropulse-intake)。您可在右侧工作流文档中打开查阅," "并按编制人流程认领后送审。" ), "intent": "TRIGGER_TASK", } ) # reminders (full from DB preferred; keep order markers) for key in ( "author_after_reject", "reviewer_pending", "approver_pending", "reviewer_after_resubmit", "approver_after_pass", ): item = (data.get("reminders") or {}).get(key) if not isinstance(item, dict) or not item.get("input"): continue # DB has fuller replies for these; skip adding truncated duplicates here return out def enrich_assistant_from_reply_files(messages): """用 dialogue/reply.md 全文替换截断的同类答复。""" replies = [] for p in (ROOT / "data/dev/workspaces/dialogue").glob("*/reply.md"): text = p.read_text(encoding="utf-8").strip() if text: replies.append(text) def better(old: str) -> str: if not old: return old head = old[:80].replace("\n", " ") for r in replies: if r.startswith(old[:40]) or old[:40] in r[:120] or head[:30] in r[:200]: if len(r) > len(old): return r return old for m in messages: if m.get("role") == "assistant": m["content"] = better(m["content"]) return messages def build_messages(): early = load_early_dialogues_from_results() db = load_db_messages() # 先装 early(张工主线),再装 DB(避免重复时以 DB 全文覆盖同类) by_session: dict[str, list] = {sid(1): [], sid(2): [], sid(3): []} idx = 0 def push(uid, role, content, intent="", author=None, ts=None): nonlocal idx idx += 1 session = sid(uid) hhmm = ts or f"10:{10 + idx:02d}" if "T" in str(hhmm): # 2026-09-09 11:28:52.xxx+08 try: hhmm = str(hhmm)[11:16] except Exception: hhmm = f"11:{idx:02d}" msg = { "id": f"m-np-{idx}", "sessionId": session, "role": role, "content": content, "timestamp": hhmm, } if role == "user": msg["authorName"] = {1: "张工", 2: "李主管", 3: "王总监"}.get(uid, "用户") else: msg["authorName"] = author or "水木东方协同参谋" if intent: msg["intentTag"] = intent by_session[session].append(msg) # system banner for each for uid, uname in [(1, "张工"), (2, "李主管"), (3, "王总监")]: push( uid, "system", f"水木东方协同参谋小队已就位。当前项目【{PROJECT_NAME}】进入【V0.5 方案成型与法规对齐】。" f"您正以「{uname}」身份与智能体私聊,其他成员对话不可见。", ts="09:50", ) for e in early: push(e["uid"], e["role"], e["content"], e.get("intent") or "", e.get("name"), ts=None) # DB messages (reminders / later turns) — append if not duplicate of last user text existing_user_texts = set() for sess_msgs in by_session.values(): for m in sess_msgs: if m["role"] == "user": existing_user_texts.add(m["content"][:40]) for row in db: uid = 1 if row["session_id"].endswith("u2"): uid = 2 elif row["session_id"].endswith("u3"): uid = 3 if row["role"] == "user" and row["content"][:40] in existing_user_texts: continue push(uid, row["role"], row["content"], row.get("intent") or "", ts=row.get("created_at")) # flatten + enrich all_msgs = [] for s in (sid(1), sid(2), sid(3)): all_msgs.extend(by_session[s]) all_msgs = enrich_assistant_from_reply_files(all_msgs) return all_msgs, {s: len(by_session[s]) for s in by_session} def main(): messages, counts = build_messages() documents = {} deliverables = [] for key, meta in DOC_MAP.items(): doc_id = f"doc-neuropulse-{key}" md = read_md(meta["work_dir"]) documents[doc_id] = { "docId": doc_id, "projectId": PROJECT_ID, "taskKey": key, "title": meta["title"], "stage": meta["stage"], "category": "workflow", "creatorId": 1, "creatorName": "张工", "version": "V1.0" if meta["state"] == "approved" else "V0.9", "lifecycleState": meta["state"], "isTemplateOnly": False, "isClaimed": True, "author": "张工", "reviewer": "李主管", "approver": "王总监", "updatedAt": "2026-09-09 11:30", "summary": meta["summary"], "markdown": md, } deliverables.append( { "docId": doc_id, "projectId": PROJECT_ID, "taskKey": key, "title": meta["title"], "stage": meta["stage"], "category": "workflow", "creatorId": 1, "creatorName": "张工", "isFixedWorkflow": True, "isTemplateOnly": False, "lifecycleState": meta["state"], "isClaimed": True, "author": "张工", "reviewer": "李主管", "approver": "王总监", "updatedAt": "2026-09-09 11:30", "summary": meta["summary"], } ) sessions = [] for uid, uname in [(1, "张工"), (2, "李主管"), (3, "王总监")]: sessions.append( { "sessionId": sid(uid), "userId": uid, "projectId": PROJECT_ID, "title": PROJECT_NAME, "deviceType": DEVICE_TYPE, "stage": "V0.5", "stageLabel": "V0.5 方案成型与法规对齐", "messageCount": counts.get(sid(uid), 0), "updatedAt": "刚刚", } ) seed = { "generatedAt": datetime.now().isoformat(timespec="seconds"), "projectId": PROJECT_ID, "projectName": PROJECT_NAME, "activeSessionId": sid(1), "sessions": sessions, "messages": messages, "deliverables": deliverables, "documents": documents, "permissions": { "1": {d["docId"]: "author" for d in deliverables}, "2": {d["docId"]: "reviewer" for d in deliverables}, "3": {d["docId"]: "approver" for d in deliverables}, }, } OUT.parent.mkdir(parents=True, exist_ok=True) OUT.write_text(json.dumps(seed, ensure_ascii=False), encoding="utf-8") print(f"Wrote {OUT}") print(f"sessions={len(sessions)} messages={len(messages)} docs={len(documents)}") for k, v in documents.items(): print(f" {k}: markdown={len(v['markdown'])} state={v['lifecycleState']}") if __name__ == "__main__": main()