# ============================================================ # 锐扬 APS 演示全流程 E2E 驱动(开发/诊断用) # 解析文件夹 → 导入试排 → MRP 分解 → 下达 → 柔性排产 → 验证订单页数据 # ============================================================ from __future__ import annotations import json import os import sys import urllib.request from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] BASE = (os.environ.get("APS_DEMO_API_URL") or "http://127.0.0.1:8000").rstrip("/") WORKDIR = str(Path( os.environ.get("RUIYANG_DEMO_DIR") or (REPO_ROOT / "demo-data" / "锐扬APS演示数据") ).resolve()) PROJECT_NAME = "锐扬演示-E2E" 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: """POST /api/chat(SSE),聚合 token 文本 / intent / blocks。""" 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(session_id: str, confirm_id: str) -> dict: return req("POST", "/api/actions/confirm", {"sessionId": session_id, "confirmId": confirm_id, "approve": True}) def show(title: str, payload: dict, keys: list[str]) -> None: print(f"\n--- {title} ---") for k in keys: v = payload.get(k) if isinstance(v, list): print(f" {k}: {len(v)} 条") else: print(f" {k}: {v}") def main() -> None: # 1) 建项目 + 会话 proj = req("POST", "/api/projects", { "name": PROJECT_NAME, "scopeLabel": "锐扬 APS 文件夹演示", "workDir": WORKDIR, }) pid = proj["project"]["id"] print(f"[1] 项目已建: {pid} workDir={WORKDIR}") sess = req("POST", "/api/sessions", {"projectId": pid, "title": "锐扬全流程演示"}) sid = sess["session"]["id"] print(f"[1] 会话已建: {sid}") # 2) 激活工作区(切换世界);服务端只接受选择字段与消息体 ws = req("PUT", "/api/workspace", { "activeProjectId": pid, "activeSessionId": sid, "messages": {}, }) print(f"[2] 工作区已激活 worldKey={ws.get('worldKey')}") # 3) 解析文件夹 r = chat(sid, pid, "分析一下数据文件") print(f"\n[3] folder.analyze intents={r['intents']}\n{r['text'][:1200]}") if r["errors"]: print(" ERRORS:", r["errors"]) # 4) 导入目录数据并试排(P2 → 确认卡) r = chat(sid, pid, "根据这个文件夹的数据导入并试排") print(f"\n[4] folder.schedule intents={r['intents']}\n{r['text'][:800]}") for b in r["blocks"]: cid = (b.get("props") or {}).get("confirmId") if cid: res = confirm(sid, cid) print(f" 确认 {cid[:12]}… → {json.dumps(res, ensure_ascii=False)[:400]}") # 5) MRP 分解 r = chat(sid, pid, "执行MRP分解") print(f"\n[5] order.decompose intents={r['intents']}\n{r['text'][:800]}") if r["errors"]: print(" ERRORS:", r["errors"]) mrp = req("GET", "/api/mrp") show("MRP 分解后 /api/mrp", mrp, ["make", "purchaseOrders", "outsourceOrders"]) # 6) 下达 MRP 建议单(P2 → 确认卡) r = chat(sid, pid, "下达MRP建议单") print(f"\n[6] mrp.release intents={r['intents']}\n{r['text'][:600]}") for b in r["blocks"]: cid = (b.get("props") or {}).get("confirmId") if cid: res = confirm(sid, cid) print(f" 确认 {cid[:12]}… → {json.dumps(res, ensure_ascii=False)[:400]}") # 7) 柔性排产 r = chat(sid, pid, "跑一版柔性排产") print(f"\n[7] flex.schedule intents={r['intents']}\n{r['text'][:800]}") for b in r["blocks"]: cid = (b.get("props") or {}).get("confirmId") if cid: res = confirm(sid, cid) print(f" 确认 {cid[:12]}… → {json.dumps(res, ensure_ascii=False)[:400]}") if r["errors"]: print(" ERRORS:", r["errors"]) # 8) 验证订单页各 Tab 数据 od = req("GET", "/api/orders") show("订单页 /api/orders", od, ["orders", "productionOrders", "purchaseOrders", "outsourceOrders", "make", "hasSchedule"]) g = req("GET", "/api/flex/gantt") rows = g.get("rows") or g.get("lines") or [] print(f"\n[8] /api/flex/gantt keys={list(g.keys())[:8]} rows={len(rows)}") print(f"\nDONE project={pid} session={sid}") if __name__ == "__main__": sys.exit(main())