148 lines
5.7 KiB
Python
148 lines
5.7 KiB
Python
# ============================================================
|
||
# 锐扬 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())
|