aps-agent/server/aps_domain/guidance.py

153 lines
5.9 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.

# ============================================================
# 主动引导(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)