| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- """场景级注册表 纯函数 (① 体检 workflow 升格; 批量装配 SOP §5)。
- 消费 configs/scenario_registry.yaml。答: 给定商业场合(scenario) →
- 出哪些报告章 (chapter_subset) · 必产哪些交付域 (required_domains) ·
- 什么审级 (review_tier, 走 SOP §6.1 RV-1) · 什么裁决基调 (flavor)。
- 编号 namespace (SOP §4.-1): 域=交付九域 D1-D9; 章=报告7章; 审级 RV-1。
- 纯函数 (coverage 扫描只读文件, 无写副作用)。机器位 CLI = scripts/run_scenario.py。
- """
- try:
- from app_common.app_common_guanlan.api import install_root as _install_root
- except ImportError: # 理论不可达;包结构异常时回退到按位置上跳
- from pathlib import Path as _P
- def _install_root(_f): return _P(_f).resolve().parents[4]
- from src import paths as P
- import os
- from pathlib import Path
- import yaml
- REPO = _install_root(__file__)
- REGISTRY = P.config("scenario_registry.yaml") # 配置唯一取用口 (src/paths.py)
- # 交付九域 → 证据关键词 (扫 outputs/<farm>/sop/ 文件名判该域是否已产)。SOP §4.-1 crosswalk。
- DOMAIN_EVIDENCE = {
- "D1": ["风资源", "风况", "wind_resource"],
- "D2": ["功率", "power", "mast", "偏航", "yaw", "变桨", "pitch", "回桨", "气动", "aero", "达成"],
- "D3": ["温度", "temp", "振动", "vib", "轴承", "低速轴", "主轴", "1P", "探头"],
- "D4": ["故障", "停机", "fault", "pareto", "帕累托", "事件"],
- "D5": ["可利用率", "可靠", "mtbf", "avail", "复发"],
- "D6": ["安全", "前兆", "safety", "安全链"],
- "D7": ["运维", "备件", "检修", "承包商"],
- "D8": ["寿命", "rul", "延寿", "役龄"],
- "D9": ["方案", "隐患清单", "隐患排查", "治理", "体检报告", "报告"],
- # 前置/横切 (非交付域, 但 workflow 前置门必查)
- "限电": ["限电", "curtail", "弃风"],
- "损失": ["损失", "电量", "waterfall", "瀑布"],
- }
- VALID_DOMAINS = {f"D{i}" for i in range(1, 10)}
- TIER_ORDER = {"lite": 0, "双模型": 1, "AdvCI": 2}
- def load_registry(path=REGISTRY):
- with open(path, encoding="utf-8") as f:
- return yaml.safe_load(f)
- def resolve(scenario_id, reg=None):
- """按 id 或 alias 找场景 dict; 未知 → KeyError。"""
- reg = reg or load_registry()
- for s in reg["scenarios"]:
- if s["id"] == scenario_id or scenario_id in (s.get("aliases") or []):
- return s
- raise KeyError(f"未知场景 {scenario_id!r} (合法: {[s['id'] for s in reg['scenarios']]})")
- def required_domains(scenario_id, reg=None):
- s = resolve(scenario_id, reg)
- return list(s.get("required_domains") or s.get("domain_weight", {}).get("主攻", []))
- def chapter_subset(scenario_id, reg=None):
- return list(resolve(scenario_id, reg).get("chapter_subset") or [])
- def review_tier(*, has_economic, has_confirmed, systemic, destination):
- """SOP §6.1 RV-1 审级门 (唯一正本逻辑; 场景 default 仅 doc 提示, 实际 tier 由此算)。
- destination ∈ {内部样板, 业主实名, 上云公开}。返回 (tier, 理由)。"""
- if destination == "上云公开" or has_economic or has_confirmed:
- why = []
- if destination == "上云公开": why.append("上云/公开")
- if has_economic: why.append("含经济数字¥")
- if has_confirmed: why.append("确诊异常")
- return "AdvCI", "RV-1: " + "∨".join(why) + " → AdvCI-PR (独立 C/D/R subagent)"
- if systemic:
- return "双模型", "RV-1: 跨机系统性(≥3台同机制) → 双模型审 (Gemini+DeepSeek)"
- return "lite", "RV-1: 健康清场/无确诊无¥ → lite (1 独立 subagent, 禁主 Claude 自审)"
- def check_coverage(farm, scenario_id, reg=None, sop_dir=None):
- """扫 outputs/<farm>/sop/ 文件名, 判每个 required_domain 是否已产。
- 返回 {domain: {"covered": bool, "evidence": [文件名...]}} + 前置门(限电/损失)。
- coverage = §0.8 required_outputs / 九域矩阵'缺一中止'的机器化 (缺 = 显式声明, 非静默跳过)。"""
- sop_dir = Path(sop_dir) if sop_dir else P.sop(farm)
- files = [p.name for p in sop_dir.iterdir()] if sop_dir.is_dir() else []
- def hits(keys):
- return [fn for fn in files if any(k.lower() in fn.lower() for k in keys)]
- req = required_domains(scenario_id, reg)
- out = {}
- for d in req + ["限电", "损失"]:
- ev = hits(DOMAIN_EVIDENCE.get(d, []))
- out[d] = {"covered": bool(ev), "evidence": ev}
- return out
|