627 lines
24 KiB
Python
627 lines
24 KiB
Python
|
|
"""
|
||
|
|
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()
|