362 lines
12 KiB
Python
362 lines
12 KiB
Python
#!/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()
|