config.py
Language Python1.9 KB58 lines
"""partner-thresholds.md config and the arming guard."""from __future__ import annotationsimport reimport sysimport yamlfrom .paths import CONFIG_MD# Any value of this literal in the active mode means the pipeline is not armed.SENTINEL = "TBD"# The two classify outcomes that are not a partner tier.NON_PARTNER = ("Not a partner client", "UNCLASSIFIED: thresholds not set")def load_config() -> dict:"""Extract and parse the first ```yaml fenced block from partner-thresholds.md."""if not CONFIG_MD.exists():sys.exit(f"Config not found: {CONFIG_MD}")text = CONFIG_MD.read_text(encoding="utf-8")m = re.search(r"```yaml\s*\n(.*?)\n```", text, re.DOTALL)if not m:sys.exit("No ```yaml block found in partner-thresholds.md")return yaml.safe_load(m.group(1)) or {}def _has_sentinel(node) -> bool:if isinstance(node, str):return node.strip() == SENTINELif isinstance(node, dict):return any(_has_sentinel(v) for v in node.values())if isinstance(node, list):return any(_has_sentinel(v) for v in node)return Falsedef is_armed(cfg: dict) -> tuple[bool, list[str]]:"""A run is armed only when the active mode has no TBDs and min_orders is set."""reasons = []mode = cfg.get("mode", "ltv_bands")active = cfg.get(mode)if _has_sentinel(active):reasons.append(f"'{mode}' block still has TBD placeholder(s)")if cfg.get("min_orders_to_qualify") == SENTINEL:reasons.append("min_orders_to_qualify is TBD")return (len(reasons) == 0, reasons)def guard_or_exit():"""The write stages refuse to run until the thresholds are set."""cfg = load_config()armed, reasons = is_armed(cfg)if not armed:sys.exit("REFUSED: thresholds not set (" + "; ".join(reasons) +"). Fill partner-thresholds.md before write stages.")return cfg