Files
boc/iom/decision_support/outcome_engine.py
T

627 lines
24 KiB
Python
Raw Normal View History

"""
Outcome Engine
Self-learning system where every recommendation becomes an experiment
Closed feedback loop: Observation → Analysis → Recommendation → Action → New Observation → Change → AI learns
"""
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
import json
class OutcomeStatus(str, Enum):
"""Status of an intervention outcome"""
SUCCESS = "success"
PARTIAL = "partial"
UNDERPERFORMED = "underperformed"
FAILED = "failed"
INCONCLUSIVE = "inconclusive"
@dataclass
class InterventionOutcome:
"""Outcome of an intervention"""
intervention_id: str
recommendation_id: str
location_id: str
action_type: str
baseline_state: Dict[str, float]
post_state: Dict[str, float]
expected_change: Dict[str, float]
actual_change: Dict[str, float]
variance: Dict[str, float]
status: OutcomeStatus
confidence: float
timestamp: str
learnings: List[str]
def to_dict(self) -> Dict:
return {
"intervention_id": self.intervention_id,
"recommendation_id": self.recommendation_id,
"location_id": self.location_id,
"action_type": self.action_type,
"expected_change": {k: round(v, 2) for k, v in self.expected_change.items()},
"actual_change": {k: round(v, 2) for k, v in self.actual_change.items()},
"variance": {k: round(v, 2) for k, v in self.variance.items()},
"status": self.status.value,
"confidence": round(self.confidence, 2),
"timestamp": self.timestamp,
"learnings": self.learnings
}
class EvidenceEngine:
"""Tracks evidence for each recommendation type"""
def __init__(self):
self.evidence_db: Dict[str, Dict] = {}
def record_outcome(self, outcome: InterventionOutcome):
"""Record outcome and update evidence"""
action_type = outcome.action_type
if action_type not in self.evidence_db:
self.evidence_db[action_type] = {
"action_type": action_type,
"total_tests": 0,
"successes": 0,
"partials": 0,
"failures": 0,
"average_effect": {},
"confidence": 0,
"history": []
}
evidence = self.evidence_db[action_type]
evidence["total_tests"] += 1
evidence["history"].append(outcome.to_dict())
# Update success counts
if outcome.status == OutcomeStatus.SUCCESS:
evidence["successes"] += 1
elif outcome.status == OutcomeStatus.PARTIAL:
evidence["partials"] += 1
else:
evidence["failures"] += 1
# Update average effects
for metric, change in outcome.actual_change.items():
if metric not in evidence["average_effect"]:
evidence["average_effect"][metric] = []
evidence["average_effect"][metric].append(change)
# Calculate confidence
success_rate = (evidence["successes"] + evidence["partials"] * 0.5) / evidence["total_tests"]
evidence["confidence"] = min(0.99, success_rate * (1 - 1 / evidence["total_tests"]))
# Keep only last 100 outcomes
evidence["history"] = evidence["history"][-100:]
def get_evidence(self, action_type: str) -> Dict:
"""Get evidence for an action type"""
evidence = self.evidence_db.get(action_type, {
"action_type": action_type,
"total_tests": 0,
"confidence": 0,
"message": "No evidence yet"
})
# Calculate averages
avg_effects = {}
for metric, values in evidence.get("average_effect", {}).items():
if values:
avg_effects[metric] = round(sum(values) / len(values), 2)
return {
"action_type": action_type,
"total_tests": evidence["total_tests"],
"successes": evidence.get("successes", 0),
"partials": evidence.get("partials", 0),
"failures": evidence.get("failures", 0),
"success_rate": round(evidence.get("successes", 0) / max(evidence["total_tests"], 1) * 100, 1),
"average_effects": avg_effects,
"confidence": round(evidence.get("confidence", 0), 2),
"reliability": self._classify_reliability(evidence["total_tests"], evidence.get("confidence", 0))
}
def _classify_reliability(self, n_tests: int, confidence: float) -> str:
"""Classify reliability of evidence"""
if n_tests < 10:
return "insufficient_data"
elif confidence < 0.5:
return "unreliable"
elif confidence < 0.7:
return "moderate"
elif confidence < 0.9:
return "reliable"
else:
return "highly_reliable"
def compare_actions(self, action_types: List[str]) -> List[Dict]:
"""Compare evidence for multiple actions"""
comparisons = []
for action_type in action_types:
evidence = self.get_evidence(action_type)
comparisons.append(evidence)
# Sort by confidence
comparisons.sort(key=lambda x: x["confidence"], reverse=True)
return comparisons
class BenchmarkEngine:
"""Benchmarks performance across locations and interventions"""
def __init__(self):
self.outcomes: List[InterventionOutcome] = []
self.location_performance: Dict[str, List[Dict]] = {}
def add_outcome(self, outcome: InterventionOutcome):
"""Add outcome to benchmark database"""
self.outcomes.append(outcome)
# Track by location
loc = outcome.location_id
if loc not in self.location_performance:
self.location_performance[loc] = []
self.location_performance[loc].append(outcome.to_dict())
def benchmark_locations(self, metric: str = "safety_index") -> List[Dict]:
"""Benchmark locations by improvement in metric"""
results = []
for location_id, outcomes in self.location_performance.items():
if not outcomes:
continue
# Calculate average improvement
improvements = []
for outcome in outcomes:
actual = outcome.get("actual_change", {})
if metric in actual:
improvements.append(actual[metric])
if improvements:
avg_improvement = sum(improvements) / len(improvements)
results.append({
"location_id": location_id,
"interventions_count": len(outcomes),
"average_improvement": round(avg_improvement, 2),
"best_improvement": round(max(improvements), 2),
"success_rate": round(
sum(1 for o in outcomes if o.get("status") in ["success", "partial"]) / len(outcomes) * 100, 1
)
})
# Sort by improvement
results.sort(key=lambda x: x["average_improvement"], reverse=True)
return results
def best_practices(self, area_type: str = "all") -> List[Dict]:
"""Identify best practices across all interventions"""
# Group by action type
by_action = {}
for outcome in self.outcomes:
action = outcome.action_type
if action not in by_action:
by_action[action] = []
by_action[action].append(outcome)
practices = []
for action, outcomes in by_action.items():
if len(outcomes) < 3: # Need minimum data
continue
success_count = sum(1 for o in outcomes if o.status in [OutcomeStatus.SUCCESS, OutcomeStatus.PARTIAL])
avg_variance = sum(
sum(o.variance.values()) / max(len(o.variance), 1)
for o in outcomes
) / len(outcomes)
practices.append({
"action_type": action,
"tested_count": len(outcomes),
"success_rate": round(success_count / len(outcomes) * 100, 1),
"average_variance": round(avg_variance, 2),
"effectiveness": "high" if success_count / len(outcomes) > 0.7 else "medium" if success_count / len(outcomes) > 0.4 else "low"
})
# Sort by success rate
practices.sort(key=lambda x: x["success_rate"], reverse=True)
return practices
class LearningGraph:
"""Learning graph: Signal → Decision → Action → Result → Learning"""
def __init__(self):
self.learnings: List[Dict] = []
self.signal_learnings: Dict[str, List[Dict]] = {}
def add_learning(self, outcome: InterventionOutcome):
"""Extract learning from outcome"""
learning = {
"timestamp": outcome.timestamp,
"action_type": outcome.action_type,
"location_id": outcome.location_id,
"expected": outcome.expected_change,
"actual": outcome.actual_change,
"variance": outcome.variance,
"status": outcome.status.value,
"learnings": outcome.learnings
}
self.learnings.append(learning)
# Index by affected signals
for signal in outcome.actual_change.keys():
if signal not in self.signal_learnings:
self.signal_learnings[signal] = []
self.signal_learnings[signal].append(learning)
def get_learnings_for_signal(self, signal_type: str) -> List[Dict]:
"""Get all learnings related to a signal type"""
return self.signal_learnings.get(signal_type, [])
def get_learnings_for_action(self, action_type: str) -> List[Dict]:
"""Get all learnings for an action type"""
return [l for l in self.learnings if l["action_type"] == action_type]
def get_insights(self) -> List[Dict]:
"""Generate insights from accumulated learnings"""
insights = []
# Find patterns in successful interventions
successful = [l for l in self.learnings if l["status"] in ["success", "partial"]]
if successful:
# Most effective actions
action_success = {}
for learning in successful:
action = learning["action_type"]
if action not in action_success:
action_success[action] = []
action_success[action].append(learning)
for action, learnings in action_success.items():
avg_improvement = sum(
sum(l["actual"].values()) / max(len(l["actual"]), 1)
for l in learnings
) / len(learnings)
insights.append({
"type": "effective_action",
"action": action,
"evidence_count": len(learnings),
"average_improvement": round(avg_improvement, 2),
"insight": f"{action} has shown consistent positive results ({len(learnings)} interventions)"
})
# Find common failure patterns
failed = [l for l in self.learnings if l["status"] in ["underperformed", "failed"]]
if len(failed) > 5:
insights.append({
"type": "failure_pattern",
"count": len(failed),
"insight": f"{len(failed)} interventions underperformed. Consider reviewing implementation quality."
})
return insights
class InterventionLibrary:
"""Global library of interventions with proven results"""
def __init__(self):
self.interventions: Dict[str, Dict] = {}
def register_intervention_type(
self,
intervention_type: str,
description: str,
typical_cost_usd: float,
typical_timeline_months: int,
expected_effects: Dict[str, float],
required_resources: List[str]
):
"""Register an intervention type"""
self.interventions[intervention_type] = {
"type": intervention_type,
"description": description,
"typical_cost_usd": typical_cost_usd,
"typical_timeline_months": typical_timeline_months,
"expected_effects": expected_effects,
"required_resources": required_resources,
"evidence": {
"tested_count": 0,
"success_rate": 0,
"average_actual_effects": {}
}
}
def update_evidence(self, intervention_type: str, outcome: InterventionOutcome):
"""Update evidence with new outcome"""
if intervention_type not in self.interventions:
return
intervention = self.interventions[intervention_type]
evidence = intervention["evidence"]
evidence["tested_count"] += 1
# Update success rate
if outcome.status in [OutcomeStatus.SUCCESS, OutcomeStatus.PARTIAL]:
current_success = evidence["success_rate"] * (evidence["tested_count"] - 1)
evidence["success_rate"] = (current_success + 1) / evidence["tested_count"]
# Update average actual effects
for metric, change in outcome.actual_change.items():
if metric not in evidence["average_actual_effects"]:
evidence["average_actual_effects"][metric] = []
# Ensure it's a list (not a float from previous averaging)
if not isinstance(evidence["average_actual_effects"][metric], list):
evidence["average_actual_effects"][metric] = []
evidence["average_actual_effects"][metric].append(change)
# Calculate averages (store raw values separately, show averages)
for metric, values in evidence["average_actual_effects"].items():
if isinstance(values, list) and values:
evidence[f"_avg_{metric}"] = round(sum(values) / len(values), 2)
def get_intervention(self, intervention_type: str) -> Optional[Dict]:
"""Get intervention details with evidence"""
return self.interventions.get(intervention_type)
def search_interventions(
self,
target_effect: Optional[str] = None,
max_cost: Optional[float] = None,
min_success_rate: Optional[float] = None
) -> List[Dict]:
"""Search interventions by criteria"""
results = []
for intervention in self.interventions.values():
# Filter by target effect
if target_effect and target_effect not in intervention["expected_effects"]:
continue
# Filter by cost
if max_cost and intervention["typical_cost_usd"] > max_cost:
continue
# Filter by success rate
success_rate = intervention["evidence"]["success_rate"]
if min_success_rate and success_rate < min_success_rate:
continue
results.append(intervention)
# Sort by success rate
results.sort(key=lambda x: x["evidence"]["success_rate"], reverse=True)
return results
class OutcomeEngine:
"""Main outcome engine coordinating all components"""
def __init__(self):
self.evidence = EvidenceEngine()
self.benchmark = BenchmarkEngine()
self.learning = LearningGraph()
self.library = InterventionLibrary()
# Register common interventions
self._register_common_interventions()
def _register_common_interventions(self):
"""Register common intervention types"""
self.library.register_intervention_type(
"replace_lighting",
"Replace street lighting with LED",
800000,
4,
{"safety_index": 15, "night_activity": 20, "energy_efficiency": 25},
["electrician", "materials", "permits"]
)
self.library.register_intervention_type(
"repair_sidewalks",
"Repair damaged sidewalks",
400000,
3,
{"walkability": 20, "safety_index": 10, "accessibility": 15},
["construction_crew", "materials"]
)
self.library.register_intervention_type(
"plant_trees",
"Plant trees along streets",
200000,
12,
{"shade_index": 25, "heat_stress": -15, "walkability": 10},
["landscaping_crew", "saplings", "irrigation"]
)
self.library.register_intervention_type(
"remove_graffiti",
"Remove graffiti and paint walls",
50000,
1,
{"visual_maintenance": 30, "safety_index": 8},
["painting_crew", "paint"]
)
def process_outcome(self, outcome: InterventionOutcome):
"""Process a new outcome through all systems"""
# Update evidence
self.evidence.record_outcome(outcome)
# Update benchmarks
self.benchmark.add_outcome(outcome)
# Add to learning graph
self.learning.add_learning(outcome)
# Update intervention library
self.library.update_evidence(outcome.action_type, outcome)
return {
"status": "processed",
"intervention_id": outcome.intervention_id,
"evidence_updated": True,
"learnings_extracted": len(outcome.learnings)
}
def get_recommendation_with_evidence(self, action_type: str) -> Dict:
"""Get recommendation backed by evidence"""
intervention = self.library.get_intervention(action_type)
evidence = self.evidence.get_evidence(action_type)
return {
"intervention": intervention,
"evidence": evidence,
"confidence": evidence["confidence"],
"recommendation": self._generate_recommendation(intervention, evidence)
}
def _generate_recommendation(self, intervention: Dict, evidence: Dict) -> str:
"""Generate human-readable recommendation"""
if evidence["total_tests"] == 0:
return f"{intervention['description']} - No evidence yet"
if evidence["reliability"] == "highly_reliable":
return f"{intervention['description']} - Highly recommended ({evidence['success_rate']}% success rate, {evidence['total_tests']} tests)"
elif evidence["reliability"] == "reliable":
return f"{intervention['description']} - Recommended ({evidence['success_rate']}% success rate)"
elif evidence["reliability"] == "moderate":
return f"{intervention['description']} - Consider with caution ({evidence['success_rate']}% success rate)"
else:
return f"{intervention['description']} - Insufficient evidence"
def get_system_stats(self) -> Dict:
"""Get overall system statistics"""
return {
"total_outcomes": len(self.benchmark.outcomes),
"total_learnings": len(self.learning.learnings),
"intervention_types": len(self.library.interventions),
"evidence_entries": len(self.evidence.evidence_db),
"insights": self.learning.get_insights()
}
# Example usage
def example_outcome_engine():
"""Example: Outcome Engine in action"""
engine = OutcomeEngine()
print("=== Outcome Engine Demo ===")
print(f"Registered interventions: {len(engine.library.interventions)}")
# Simulate outcomes
outcomes = [
InterventionOutcome(
intervention_id="INT-001",
recommendation_id="REC-001",
location_id="LOC-001",
action_type="replace_lighting",
baseline_state={"safety_index": 45, "night_activity": 30},
post_state={"safety_index": 62, "night_activity": 55},
expected_change={"safety_index": 15, "night_activity": 20},
actual_change={"safety_index": 17, "night_activity": 25},
variance={"safety_index": 2, "night_activity": 5},
status=OutcomeStatus.SUCCESS,
confidence=0.85,
timestamp=datetime.utcnow().isoformat(),
learnings=["LED lighting significantly improved safety", "Color temperature 4000K optimal"]
),
InterventionOutcome(
intervention_id="INT-002",
recommendation_id="REC-002",
location_id="LOC-002",
action_type="replace_lighting",
baseline_state={"safety_index": 40, "night_activity": 25},
post_state={"safety_index": 52, "night_activity": 38},
expected_change={"safety_index": 15, "night_activity": 20},
actual_change={"safety_index": 12, "night_activity": 13},
variance={"safety_index": -3, "night_activity": -7},
status=OutcomeStatus.PARTIAL,
confidence=0.75,
timestamp=datetime.utcnow().isoformat(),
learnings=["Partial success due to uneven coverage"]
),
InterventionOutcome(
intervention_id="INT-003",
recommendation_id="REC-003",
location_id="LOC-003",
action_type="plant_trees",
baseline_state={"shade_index": 20, "heat_stress": 80},
post_state={"shade_index": 50, "heat_stress": 60},
expected_change={"shade_index": 25, "heat_stress": -15},
actual_change={"shade_index": 30, "heat_stress": -20},
variance={"shade_index": 5, "heat_stress": -5},
status=OutcomeStatus.SUCCESS,
confidence=0.9,
timestamp=datetime.utcnow().isoformat(),
learnings=["Tree species selection critical", "Native species performed best"]
)
]
# Process outcomes
print("\n=== Processing Outcomes ===")
for outcome in outcomes:
result = engine.process_outcome(outcome)
print(f"Processed {outcome.intervention_id}: {outcome.status.value}")
# Get evidence
print("\n=== Evidence for Lighting Replacement ===")
evidence = engine.evidence.get_evidence("replace_lighting")
print(f"Tests: {evidence['total_tests']}")
print(f"Success rate: {evidence['success_rate']}%")
print(f"Confidence: {evidence['confidence']}")
print(f"Reliability: {evidence['reliability']}")
# Get recommendation with evidence
print("\n=== Recommendation with Evidence ===")
rec = engine.get_recommendation_with_evidence("replace_lighting")
print(f"Recommendation: {rec['recommendation']}")
# Benchmark locations
print("\n=== Location Benchmarks ===")
benchmarks = engine.benchmark.benchmark_locations("safety_index")
for b in benchmarks:
print(f" {b['location_id']}: {b['average_improvement']:.1f} avg improvement")
# Best practices
print("\n=== Best Practices ===")
practices = engine.benchmark.best_practices()
for p in practices:
print(f" {p['action_type']}: {p['success_rate']}% success ({p['tested_count']} tests)")
# Insights
print("\n=== AI Insights ===")
insights = engine.learning.get_insights()
for insight in insights:
print(f" [{insight['type']}] {insight['insight']}")
# System stats
print("\n=== System Stats ===")
stats = engine.get_system_stats()
print(f"Total outcomes: {stats['total_outcomes']}")
print(f"Total learnings: {stats['total_learnings']}")
print(f"Insights generated: {len(stats['insights'])}")
return engine
if __name__ == '__main__':
example_outcome_engine()