""" Prediction Engine (PRI - Predictive Reality Index) Forecasts what will happen in 30, 90, and 365 days """ from typing import Dict, List, Optional, Tuple from dataclasses import dataclass from datetime import datetime, timedelta import json @dataclass class Prediction: """A single prediction""" target: str current_value: float predicted_value: float confidence: float horizon_days: int drivers: List[Dict] scenario: Optional[str] = None @property def change_percent(self) -> float: return round(((self.predicted_value - self.current_value) / max(self.current_value, 1)) * 100, 2) def to_dict(self) -> Dict: return { "target": self.target, "current_value": round(self.current_value, 2), "predicted_value": round(self.predicted_value, 2), "change_percent": self.change_percent, "confidence": round(self.confidence, 2), "horizon_days": self.horizon_days, "drivers": self.drivers, "scenario": self.scenario } class PredictionEngine: """Predicts future states from reality signals""" def __init__(self): self.models = self._load_prediction_models() def _load_prediction_models(self) -> Dict: """Load prediction models for different signals""" return { "vacancy_rate": { "drivers": ["retail_density", "pedestrian_flow", "rent_burden", "maintenance_quality"], "trend_factor": 0.02, # 2% monthly trend "seasonality": True }, "crime_risk": { "drivers": ["lighting", "pedestrian_flow", "maintenance_quality", "informal_economy"], "trend_factor": 0.015, "seasonality": False }, "family_friendly": { "drivers": ["playground", "greenery", "safety_index", "schools", "noise_level"], "trend_factor": 0.01, "seasonality": False }, "property_value": { "drivers": ["walkability", "transit_access", "greenery", "safety_index", "retail_density"], "trend_factor": 0.03, "seasonality": True } } def predict( self, signal_history: List[Dict], target: str, horizon_days: int = 90, scenario: Optional[Dict] = None ) -> Prediction: """ Predict future value of a signal Args: signal_history: Historical signal values target: Signal type to predict horizon_days: Prediction horizon (30, 90, 365) scenario: Optional scenario (e.g., "add 40 trees") Returns: Prediction result """ model = self.models.get(target, {}) # Get current value current_value = self._get_current_value(signal_history, target) # Calculate trend trend = self._calculate_trend(signal_history, target) # Apply scenario effects scenario_effect = 0 if scenario: scenario_effect = self._calculate_scenario_effect(scenario, target) # Predict months = horizon_days / 30 predicted_value = current_value + (trend * months) + scenario_effect # Calculate confidence based on data quality confidence = self._calculate_confidence(signal_history, target) # Identify drivers drivers = self._identify_drivers(signal_history, target, model) return Prediction( target=target, current_value=current_value, predicted_value=predicted_value, confidence=confidence, horizon_days=horizon_days, drivers=drivers, scenario=scenario.get("name") if scenario else None ) def predict_multiple( self, signal_history: List[Dict], targets: List[str], horizon_days: int = 90 ) -> Dict: """Predict multiple targets""" predictions = {} for target in targets: pred = self.predict(signal_history, target, horizon_days) predictions[target] = pred.to_dict() return { "horizon_days": horizon_days, "predictions": predictions, "timestamp": datetime.utcnow().isoformat() } def scenario_analysis( self, signal_history: List[Dict], target: str, scenarios: List[Dict] ) -> Dict: """ Compare multiple scenarios Example scenarios: - {"name": "add_40_trees", "changes": {"tree_coverage": 40}} - {"name": "improve_lighting", "changes": {"lighting": 20}} """ results = [] # Baseline prediction baseline = self.predict(signal_history, target, 365) for scenario in scenarios: pred = self.predict(signal_history, target, 365, scenario) improvement = pred.predicted_value - baseline.predicted_value results.append({ "scenario": scenario["name"], "predicted_value": round(pred.predicted_value, 2), "improvement": round(improvement, 2), "improvement_percent": round((improvement / max(baseline.predicted_value, 1)) * 100, 2), "cost_estimate": scenario.get("cost_estimate", "unknown"), "roi": self._calculate_roi(improvement, scenario.get("cost_estimate")) }) # Sort by improvement results.sort(key=lambda x: x["improvement"], reverse=True) return { "target": target, "baseline": baseline.to_dict(), "scenarios": results, "best_scenario": results[0]["scenario"] if results else None } def _get_current_value(self, signal_history: List[Dict], target: str) -> float: """Get current value from history""" matching = [s for s in signal_history if s.get("signal_type") == target] if matching: return matching[-1].get("value", 50) return 50 # Default def _calculate_trend(self, signal_history: List[Dict], target: str) -> float: """Calculate trend from historical data""" matching = [s for s in signal_history if s.get("signal_type") == target] if len(matching) < 2: return 0 # No trend # Simple linear trend values = [s.get("value", 50) for s in matching] return (values[-1] - values[0]) / len(values) def _calculate_scenario_effect(self, scenario: Dict, target: str) -> float: """Calculate effect of a scenario""" changes = scenario.get("changes", {}) # Simple heuristic: each unit change affects target by 0.5 total_effect = 0 for signal_type, change in changes.items(): total_effect += change * 0.5 return total_effect def _calculate_confidence(self, signal_history: List[Dict], target: str) -> float: """Calculate prediction confidence""" matching = [s for s in signal_history if s.get("signal_type") == target] # More data = higher confidence data_confidence = min(0.9, len(matching) / 10) # Recent data = higher confidence if matching: latest = matching[-1].get("timestamp", "") # Check if data is recent (within 30 days) # Simplified: assume recent recency_confidence = 0.8 else: recency_confidence = 0.3 return (data_confidence + recency_confidence) / 2 def _identify_drivers(self, signal_history: List[Dict], target: str, model: Dict) -> List[Dict]: """Identify key drivers for prediction""" drivers = [] for driver_type in model.get("drivers", []): driver_signals = [s for s in signal_history if s.get("signal_type") == driver_type] if driver_signals: avg_value = sum(s.get("value", 50) for s in driver_signals) / len(driver_signals) drivers.append({ "signal_type": driver_type, "current_value": round(avg_value, 2), "impact": "high" if avg_value < 30 or avg_value > 70 else "medium" }) return drivers def _calculate_roi(self, improvement: float, cost_estimate: Optional[float]) -> Optional[float]: """Calculate ROI for a scenario""" if cost_estimate and cost_estimate > 0: return round(improvement / cost_estimate, 2) return None # Example usage def example_predictions(): """Example: Predict future states""" engine = PredictionEngine() # Historical signals signal_history = [ {"signal_type": "vacancy_rate", "value": 25, "timestamp": "2026-01-01"}, {"signal_type": "vacancy_rate", "value": 28, "timestamp": "2026-02-01"}, {"signal_type": "vacancy_rate", "value": 32, "timestamp": "2026-03-01"}, {"signal_type": "retail_density", "value": 45, "timestamp": "2026-03-01"}, {"signal_type": "pedestrian_flow", "value": 60, "timestamp": "2026-03-01"}, {"signal_type": "rent_burden", "value": 75, "timestamp": "2026-03-01"}, {"signal_type": "maintenance_quality", "value": 40, "timestamp": "2026-03-01"}, ] # Predict vacancy rate in 90 days pred = engine.predict(signal_history, "vacancy_rate", 90) print("=== Prediction: Vacancy Rate ===") print(f"Current: {pred.current_value}%") print(f"Predicted (90 days): {pred.predicted_value}%") print(f"Change: {pred.change_percent}%") print(f"Confidence: {pred.confidence}") print("\nKey Drivers:") for driver in pred.drivers: print(f" {driver['signal_type']}: {driver['current_value']} ({driver['impact']} impact)") # Scenario analysis print("\n=== Scenario Analysis ===") scenarios = [ {"name": "add_40_trees", "changes": {"tree_coverage": 40}, "cost_estimate": 50000}, {"name": "improve_lighting", "changes": {"lighting": 30}, "cost_estimate": 30000}, {"name": "reduce_rent", "changes": {"rent_burden": -20}, "cost_estimate": 0} ] result = engine.scenario_analysis(signal_history, "vacancy_rate", scenarios) print(f"Baseline (365 days): {result['baseline']['predicted_value']}%") print("\nScenarios:") for scenario in result['scenarios']: print(f" {scenario['scenario']}:") print(f" Predicted: {scenario['predicted_value']}%") print(f" Improvement: {scenario['improvement_percent']}%") if scenario['roi']: print(f" ROI: {scenario['roi']}") print(f"\nBest scenario: {result['best_scenario']}") return pred if __name__ == '__main__': example_predictions()