aps-agent/server/aps_domain/guidance.py

153 lines
5.9 KiB
Python
Raw Normal View History

# ============================================================
# 主动引导(moduleId: domain-guidance, 可重生 ✅)
# AG-07:按世界状态给出下一步建议(空态 / 冲突高 / 知识未命中)
# ============================================================
from __future__ import annotations
from typing import Any
World = dict[str, Any]
def build_guidance(world: World, *, context: str | None = None) -> dict[str, Any]:
"""根据世界状态产出信号与可点击下一步建议。
context: 可选强化场景(knowledge_miss / high_conflict / empty_schedule)
"""
signals: list[dict[str, Any]] = []
suggestions: list[dict[str, Any]] = []
versions = world.get("scheduleVersions") or []
flex_versions = world.get("flexScheduleVersions") or []
conflicts = [c for c in (world.get("conflicts") or []) if c.get("status") != "RESOLVED"]
flex_conflicts = [c for c in (world.get("flexConflicts") or []) if c.get("status") != "RESOLVED"]
open_conflicts = len(conflicts) + len(flex_conflicts)
latest = versions[-1] if versions else None
latest_flex = flex_versions[-1] if flex_versions else None
draft = latest and latest.get("status") == "DRAFT"
flex_orders = world.get("flexOrders") or []
# ---- 信号检测 ----
if not versions:
signals.append({
"id": "no_version", "severity": "high",
"title": "尚无固定轨排产版本",
"reason": "甘特为空,需先试排生成草稿。",
})
suggestions.append({
"id": "run_delivery", "label": "试排一版交期优先",
"command": "试排一版交期优先", "priority": 10,
"reason": "无版本时空态首选",
})
suggestions.append({
"id": "scenario_compare", "label": "多策略方案对比",
"command": "多策略方案对比", "priority": 8,
"reason": "不确定策略时先沙盒对比",
})
conflict_n = open_conflicts
if latest:
conflict_n = max(conflict_n, int(latest.get("conflictCount") or 0))
if latest_flex:
conflict_n = max(conflict_n, int(latest_flex.get("conflictCount") or 0))
if conflict_n >= 3 or context == "high_conflict":
signals.append({
"id": "high_conflict", "severity": "high" if conflict_n >= 5 else "medium",
"title": f"冲突偏高(约 {conflict_n} 项)",
"reason": "建议先看冲突中心,或换策略对比后再发布。",
})
suggestions.append({
"id": "view_conflicts", "label": "查看冲突",
"command": "查看冲突", "priority": 20,
"reason": "冲突高时优先定位",
})
suggestions.append({
"id": "compare_strategies", "label": "多策略方案对比",
"command": "多策略方案对比", "priority": 18,
"reason": "用沙盒评估缓解冲突的策略",
})
if draft and latest:
signals.append({
"id": "draft_ready", "severity": "low",
"title": f"草稿 {latest.get('versionNo')} 待发布",
"reason": "确认无重大冲突后可发布为执行基准。",
})
suggestions.append({
"id": "publish", "label": "发布当前排产版本",
"command": "发布当前排产版本", "priority": 6,
"reason": "草稿就绪",
})
if flex_orders and not flex_versions:
signals.append({
"id": "no_flex", "severity": "medium",
"title": "有柔性订单但未排产",
"reason": "能力池/虚拟产线需跑柔性排产。",
})
suggestions.append({
"id": "flex_run", "label": "跑一版柔性排产",
"command": "跑一版柔性排产", "priority": 12,
"reason": "柔性轨空态",
})
if context == "knowledge_miss":
signals.append({
"id": "knowledge_miss", "severity": "medium",
"title": "知识未命中",
"reason": "可换说法,或浏览已有知识资产。",
})
suggestions.append({
"id": "kb_list", "label": "查看知识库清单",
"command": "知识库", "priority": 25,
"reason": "未命中时的下一步",
})
suggestions.append({
"id": "kb_machining", "label": "机加工工艺怎么生成",
"command": "机加工工艺怎么生成", "priority": 24,
"reason": "机械加工工艺路线总则",
})
suggestions.append({
"id": "kb_sop", "label": "问主数据录入顺序",
"command": "主数据录入顺序有什么规定", "priority": 22,
"reason": "常见 SOP 样例",
})
# 去重(按 command 保留最高 priority)
by_cmd: dict[str, dict[str, Any]] = {}
for s in suggestions:
prev = by_cmd.get(s["command"])
if prev is None or s["priority"] > prev["priority"]:
by_cmd[s["command"]] = s
ranked = sorted(by_cmd.values(), key=lambda x: -x["priority"])[:5]
if not ranked:
ranked = [{
"id": "help", "label": "看看能做什么",
"command": "帮助", "priority": 1,
"reason": "默认兜底",
}]
hint = "根据当前状态,建议下一步:"
if signals:
hint = signals[0]["reason"]
return {
"signals": signals,
"suggestions": ranked,
"hint": hint,
"context": context,
"stats": {
"scheduleVersions": len(versions),
"flexScheduleVersions": len(flex_versions),
"openConflicts": open_conflicts,
"conflictScore": conflict_n,
},
}
def guidance_ui_block(world: World, *, context: str | None = None) -> dict[str, Any]:
"""构造 guidance UI 块 props(由调用方包进 UIBlock)。"""
return build_guidance(world, context=context)