# Agent-like loop control: accept numeric or "auto"
# Keep the original (possibly "auto") value; resolution happens at run time.
self.max_loops=max_loops
def_ensure_node_exists(self,node:str)->None:
"""Ensure node present in graph structures."""
ifnodenotinself.causal_graph:
@ -269,7 +274,7 @@ class CRCALite:
Returns:
Dictionaryofstandardized(z-score)values
"""
z={}
z:Dict[str,float]={}
fork,vinstate.items():
s=self.standardization_stats.get(k)
ifsands.get("std",0.0)>0:
@ -399,7 +404,44 @@ class CRCALite:
Returns:
ListofCounterfactualScenarioobjects
"""
# Ensure stats exist for variables in factual_state (fallback behavior)\n+ self.ensure_standardization_stats(factual_state)\n+\n+ scenarios: List[CounterfactualScenario] = []\n+ z_steps = [-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]\n+\n+ for i, tv in enumerate(target_variables[:max_scenarios]):\n+ stats = self.standardization_stats.get(tv, {\"mean\": 0.0, \"std\": 1.0})\n+ cur = factual_state.get(tv, stats.get(\"mean\", 0.0))\n+\n+ # If std is zero or missing, use absolute perturbations instead\n+ if not stats or stats.get(\"std\", 0.0) <= 0:\n+ base = cur\n+ abs_steps = [-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]\n+ vals = [base + step for step in abs_steps]\n+ else:\n+ mean = stats[\"mean\"]\n+ std = stats[\"std\"]\n+ cz = (cur - mean) / std\n+ vals = [(cz + dz) * std + mean for dz in z_steps]\n+\n+ for j, v in enumerate(vals):\n+ scenarios.append(CounterfactualScenario(\n+ name=f\"scenario_{i}_{j}\",\n+ interventions={tv: float(v)},\n+ expected_outcomes=self._predict_outcomes(factual_state, {tv: float(v)}),\n+ probability=self._calculate_scenario_probability(factual_state, {tv: float(v)}),\n+ reasoning=f\"Intervention on {tv} with value {v}\"\n+ ))\n+\n+ return scenarios\n*** End Patch
# Ensure stats exist for variables in factual_state (fallback behavior)