aps-agent/demo-data/fill_ruiyang_demo.py

188 lines
7.6 KiB
Python
Raw Normal View History

# ============================================================
# 锐扬排产演示 · 一键重置 + 数据灌注(演示汇报用)
# 自动删除旧的「锐扬排产演示」项目 → 重建空项目 → 走完整对话流程:
# 解析文件夹 → 导入并试排 → 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())