153 lines
5.9 KiB
Python
153 lines
5.9 KiB
Python
# ============================================================
|
||
# 主动引导(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)
|