aps-agent/demo-data/fill_ruiyang_demo.py

188 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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