import json from datetime import datetime from pathlib import Path from typing import Dict, List import yaml from .utils import increment, log_event, validate ROOT = Path(__file__).resolve().parents[1] ART = ROOT / "artifacts" / "decisions" HEURISTICS = { "uptime": { "action": "Roll out SRE mitigation", "bot": "sre_bot", "owner": "bob", "credits": 8, "impact": 4, "goals": ["improve_uptime"], }, "revenue": { "action": "Adjust Pricing", "bot": "pricing_bot", "owner": "alice", "credits": 10, "impact": 5, "goals": ["increase_gm_pct", "reduce_churn"], }, } def choose_actions(goals: List[str], constraints: Dict[str, int], candidates: List[Dict]) -> Dict: budget = constraints["credit_budget"] k = constraints["max_concurrent"] chosen: List[Dict] = [] spent = 0 goal_set = {goal.lower() for goal in goals} def _matches_goal(candidate: Dict) -> bool: if not goal_set: return True candidate_goals = {g.lower() for g in candidate.get("goals", [])} return bool(candidate_goals & goal_set) or not candidate_goals for cand in sorted(candidates, key=lambda c: (-c["impact"], c["action"])): if not _matches_goal(cand): continue if spent + cand["credits"] <= budget and len(chosen) < k: chosen.append(cand) spent += cand["credits"] raci = {c["bot"]: c["owner"] for c in chosen} return {"actions": chosen, "raci": raci} def plan_actions(anomalies_path: Path, goals_path: Path, constraints_path: Path) -> Path: anomalies = json.loads(anomalies_path.read_text()) goals = yaml.safe_load(goals_path.read_text()).get("goals", []) constraints = yaml.safe_load(constraints_path.read_text()) candidates: List[Dict] = [] for anom in anomalies: h = HEURISTICS.get(anom["metric"]) if h: candidates.append(h.copy()) plan = choose_actions(goals, constraints, candidates) validate(plan, "plan.schema.json") ts = datetime.utcnow().strftime("%Y%m%d%H%M%S") ART.mkdir(parents=True, exist_ok=True) out_path = ART / f"plan_{ts}.json" out_path.write_text(json.dumps(plan, indent=2)) increment("decision_plan") log_event({"type": "decision_plan", "anomalies": str(anomalies_path)}) return out_path