"""A lightweight ADMM-like planner for CrisisOps MVP. This is intentionally simple: given a set of available objects (resources) and a set of requests (needs), it performs a basic allocation to maximize reliability of critical deliveries while minimizing idle trips in a toy setting. """ from __future__ import annotations import time from typing import Any, Dict, List from .core import GraphOfContracts, PlanDelta class ADMMPlanner: def __init__(self): # In a real system this would persist state; for MVP we keep in-memory. self._state: Dict[str, Dict[str, int]] = {} def reset(self) -> None: self._state.clear() def optimize(self, gof: GraphOfContracts, max_iter: int = 5) -> PlanDelta: """Run a toy optimization pass over the GoC to produce a PlanDelta. We allocate available objects to matching morphisms (requests) by a deterministic rule: dispatch-style morphisms create actions when the source and target exist, and the action payload includes a utility score that favors balanced capacity and higher-priority signals. """ actions: List[Dict[str, object]] = [] now = time.time() for mid in sorted(gof.morphisms): morph = gof.morphisms[mid] src = gof.objects.get(morph.source_id) dst = gof.objects.get(morph.target_id) if not src or not dst: continue if morph.type not in {"dispatch", "transfer"} and src.type != dst.type: continue action = { "morphism_id": mid, "action": "dispatch", "resource_id": src.id, "destination_id": dst.id, "timestamp": now, "details": { "src_type": src.type, "dst_type": dst.type, "priority": morph.signals.get("priority", "normal"), "utility_score": self._utility_score(src.attributes, dst.attributes, morph.signals), }, } actions.append(action) actions.sort(key=lambda action: (action["morphism_id"], action["resource_id"], action["destination_id"])) # Record plan delta plan = PlanDelta(timestamp=now, actions=actions) gof.plan_delta = plan return plan @staticmethod def _numeric_attribute(attributes: Dict[str, Any], keys: List[str], default: float = 0.0) -> float: for key in keys: value = attributes.get(key) if value is None: continue try: return float(value) except (TypeError, ValueError): continue return default def _utility_score( self, src_attributes: Dict[str, object], dst_attributes: Dict[str, object], signals: Dict[str, object], ) -> float: source_capacity = self._numeric_attribute(src_attributes, ["capacity", "stock", "qty", "available"], default=1.0) target_need = self._numeric_attribute(dst_attributes, ["demand", "capacity", "need", "qty"], default=1.0) balance = min(source_capacity, target_need) / max(source_capacity, target_need, 1.0) priority = str(signals.get("priority", "normal")) priority_boost = {"low": 0.9, "normal": 1.0, "high": 1.1, "critical": 1.25}.get(priority, 1.0) return round(min(1.0, balance * priority_boost), 3)