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