bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
309 lines
11 KiB
Python
309 lines
11 KiB
Python
"""
|
|
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()
|