# ============================================================ # 锐扬排产演示 · 一键重置 + 数据灌注(演示汇报用) # 自动删除旧的「锐扬排产演示」项目 → 重建空项目 → 走完整对话流程: # 解析文件夹 → 导入并试排 → MRP 分解 → 下达 → 柔性排产 → 发布 → 验证 # 对话过程实时写入会话消息(中间面板有完整自然语言交互记录) # 目标后端:http://127.0.0.1:8000(当前运行中的演示应用) # 用法:python demo-data\fill_ruiyang_demo.py # ============================================================ from __future__ import annotations import json import os import sys import urllib.request from pathlib import Path BASE = (os.environ.get("APS_DEMO_API_URL") or "http://127.0.0.1:8000").rstrip("/") PROJECT_NAME = "锐扬排产演示" SCOPE_LABEL = "锐扬 APS 文件夹全流程演示" WORKDIR = str(Path( os.environ.get("RUIYANG_DEMO_DIR") or (Path(__file__).resolve().parent / "锐扬APS演示数据") ).resolve()) PROJECT_ID = "" # 运行时由 reset_project() 确定 def req(method: str, path: str, body: dict | None = None, timeout: int = 300) -> dict: data = json.dumps(body).encode("utf-8") if body is not None else None r = urllib.request.Request( BASE + path, data=data, method=method, headers={"Content-Type": "application/json"}, ) with urllib.request.urlopen(r, timeout=timeout) as resp: return json.loads(resp.read().decode("utf-8")) def chat(session_id: str, project_id: str, text: str) -> dict: body = json.dumps({"sessionId": session_id, "projectId": project_id, "text": text, "history": []}).encode("utf-8") r = urllib.request.Request(BASE + "/api/chat", data=body, method="POST", headers={"Content-Type": "application/json"}) out = {"text": "", "intents": [], "blocks": [], "errors": []} with urllib.request.urlopen(r, timeout=600) as resp: for raw in resp: line = raw.decode("utf-8", "ignore").strip() if not line.startswith("data:"): continue try: ev = json.loads(line[5:].strip()) except json.JSONDecodeError: continue t = ev.get("type") if t == "token": out["text"] += ev.get("text") or "" elif t == "intent": out["intents"].append((ev.get("intent") or {}).get("intent")) elif t == "block": out["blocks"].append(ev.get("block") or {}) elif t == "error": out["errors"].append(ev.get("message")) return out def confirm_all(session_id: str, blocks: list[dict]) -> list[str]: """批准所有 P2 确认卡,返回确认结果文案(用于写入会话记录)。""" results = [] for b in blocks: cid = (b.get("props") or {}).get("confirmId") if cid: res = req("POST", "/api/actions/confirm", {"sessionId": session_id, "confirmId": cid, "approve": True}) print(f" 确认卡 {cid[:12]}… → {json.dumps(res, ensure_ascii=False)[:300]}") if res.get("message"): results.append(res["message"]) return results class Recorder: """把对话过程实时写入会话消息,让中间面板有自然语言交互记录。""" def __init__(self, session_id: str) -> None: self.sid = session_id self.messages: list[dict] = [] def turn(self, user_text: str, r: dict, confirms: list[str] | None = None) -> None: self.messages.append({"role": "user", "text": user_text}) # 确认卡片块不落库(避免界面上出现已过期的确认按钮),确认结果以文本形式追加 blocks = [b for b in r["blocks"] if not (b.get("props") or {}).get("confirmId")] text = r["text"] for c in confirms or []: text += f"\n\n✅ {c}" agent: dict = {"role": "agent", "text": text, "streaming": False} if r["intents"]: agent["intent"] = {"intent": r["intents"][-1], "params": {}, "source": "RULE_FAST"} if blocks: agent["blocks"] = blocks self.messages.append(agent) self.flush() def flush(self) -> None: req("PUT", f"/api/sessions/{self.sid}/messages", {"messages": self.messages}) def step(title: str, r: dict, n: int = 700) -> None: print(f"\n=== {title} intents={r['intents']} ===") print(r["text"][:n]) if r["errors"]: print(" !! ERRORS:", r["errors"]) def reset_project() -> str: """删除全部同名旧项目并重建空项目,返回新项目 ID。""" global PROJECT_ID ws = req("GET", "/api/workspace") olds = [p for p in (ws.get("projects") or []) if p.get("name") == PROJECT_NAME] for p in olds: res = req("DELETE", f"/api/projects/{p['id']}") print(f"[0] 删除旧项目 {p['id']}:{res.get('message')}") proj = req("POST", "/api/projects", { "name": PROJECT_NAME, "scopeLabel": SCOPE_LABEL, "workDir": WORKDIR, }) PROJECT_ID = proj["project"]["id"] print(f"[0] 新项目 {PROJECT_ID} workDir={WORKDIR}") return PROJECT_ID def main() -> None: # 1) 重置项目(删旧建新)→ 新会话 → 激活工作区 reset_project() sess = req("POST", "/api/sessions", {"projectId": PROJECT_ID, "title": "锐扬全流程演示"}) sid = sess["session"]["id"] ws = req("PUT", "/api/workspace", {"activeProjectId": PROJECT_ID, "activeSessionId": sid, "messages": {}}) print(f"[1] 会话 {sid} 已激活 worldKey={ws.get('worldKey')}") rec = Recorder(sid) # 2) 解析文件夹 r = chat(sid, PROJECT_ID, "分析一下数据文件") step("解析文件夹", r, 1000) rec.turn("分析一下数据文件", r) # 3) 导入并试排(P2 确认卡) r = chat(sid, PROJECT_ID, "根据这个文件夹的数据导入并试排") step("导入并试排", r) rec.turn("根据这个文件夹的数据导入并试排", r, confirm_all(sid, r["blocks"])) # 4) MRP 分解 r = chat(sid, PROJECT_ID, "执行MRP分解") step("MRP分解", r) rec.turn("执行MRP分解", r) mrp = req("GET", "/api/mrp") print(f" /api/mrp: make={len(mrp.get('make') or [])} purchase={len(mrp.get('purchaseOrders') or [])} outsource={len(mrp.get('outsourceOrders') or [])}") # 5) 下达 MRP 建议单(P2 确认卡) r = chat(sid, PROJECT_ID, "下达MRP建议单") step("下达MRP建议单", r) rec.turn("下达MRP建议单", r, confirm_all(sid, r["blocks"])) # 6) 柔性排产 r = chat(sid, PROJECT_ID, "跑一版柔性排产") step("柔性排产", r) rec.turn("跑一版柔性排产", r, confirm_all(sid, r["blocks"])) # 7) 发布排产版本(P2 确认卡) r = chat(sid, PROJECT_ID, "发布当前排产版本") step("发布排产版本", r) rec.turn("发布当前排产版本", r, confirm_all(sid, r["blocks"])) # 7) 验证 od = req("GET", "/api/orders") print("\n=== 验证 /api/orders ===") for k in ["orders", "productionOrders", "purchaseOrders", "outsourceOrders", "make"]: v = od.get(k) if isinstance(v, list): print(f" {k}: {len(v)} 条") print(f" hasSchedule: {od.get('hasSchedule')}") s = req("GET", "/api/world/summary") print(f"\n/api/world/summary: {json.dumps(s, ensure_ascii=False)[:400]}") g = req("GET", "/api/flex/gantt") rows = g.get("rows") or g.get("lines") or [] print(f"/api/flex/gantt: keys={list(g.keys())[:8]} 行数={len(rows)}") print(f"\nDONE project={PROJECT_ID} session={sid}") if __name__ == "__main__": sys.exit(main())