""" Causal Engine Understands cause-and-effect relationships, not just correlations """ from typing import Dict, List, Optional, Tuple from dataclasses import dataclass from datetime import datetime @dataclass class CausalRelationship: """A causal relationship between signals""" cause: str effect: str strength: float # 0-1 mechanism: str # Explanation of how cause leads to effect evidence: List[Dict] confidence: float def to_dict(self) -> Dict: return { "cause": self.cause, "effect": self.effect, "strength": round(self.strength, 2), "mechanism": self.mechanism, "evidence_count": len(self.evidence), "confidence": round(self.confidence, 2) } class CausalEngine: """Identifies causal relationships in urban systems""" def __init__(self): self.causal_graph = self._build_causal_graph() def _build_causal_graph(self) -> Dict: """Build knowledge graph of causal relationships""" return { # Lighting → Safety "lighting": { "safety_index": { "strength": 0.71, "mechanism": "Better lighting increases visibility, deters criminal activity, and improves pedestrian confidence", "evidence": [ {"study": "Chicago Alley Lighting", "effect": "7% crime reduction"}, {"study": "NYC Street Lighting", "effect": "36% reduction in outdoor nighttime index crimes"} ] }, "night_activity": { "strength": 0.65, "mechanism": "Improved lighting extends usable hours for commercial and social activities", "evidence": [] } }, # Trees → Temperature "tree_coverage": { "heat_stress": { "strength": 0.82, "mechanism": "Trees provide shade and evapotranspiration, reducing surface and air temperature", "evidence": [ {"study": "Urban Heat Island", "effect": "2-8°C cooling effect"}, {"study": "Barcelona Tree Strategy", "effect": "4°C reduction in pedestrian areas"} ] }, "walkability": { "strength": 0.58, "mechanism": "Shaded walkways encourage walking and extend comfortable walking hours", "evidence": [] }, "property_value": { "strength": 0.45, "mechanism": "Tree-lined streets are associated with higher property values and desirability", "evidence": [ {"study": "Portland Trees", "effect": "$1.35B total property value increase"} ] } }, # Pedestrian Flow → Retail "pedestrian_flow": { "retail_revenue": { "strength": 0.78, "mechanism": "More pedestrians = more potential customers = higher sales", "evidence": [ {"study": "High Street Retail", "effect": "1% footfall increase = 1.3% sales increase"} ] }, "safety_index": { "strength": 0.52, "mechanism": "Eyes on the street effect - more people watching increases natural surveillance", "evidence": [ {"study": "Jane Jacobs", "effect": "Natural surveillance reduces crime"} ] } }, # Maintenance → Everything "maintenance_quality": { "property_value": { "strength": 0.63, "mechanism": "Well-maintained areas signal investment and care, attracting residents and businesses", "evidence": [] }, "safety_index": { "strength": 0.55, "mechanism": "Broken windows theory - visible neglect signals low enforcement and invites more disorder", "evidence": [ {"study": "Broken Windows", "effect": "Maintenance prevents escalation of disorder"} ] }, "tourism": { "strength": 0.48, "mechanism": "Tourists avoid areas that appear neglected or unsafe", "evidence": [] } }, # Public Transit → Accessibility "transit_access": { "property_value": { "strength": 0.67, "mechanism": "Transit access reduces commute costs and increases location desirability", "evidence": [ {"study": "TOD Value", "effect": "10-20% property value premium near transit"} ] }, "walkability": { "strength": 0.44, "mechanism": "Transit hubs create walkable destinations and mixed-use development", "evidence": [] } } } def explain_causality(self, cause: str, effect: str) -> Optional[Dict]: """ Explain why A causes B Returns: Causal explanation or None if no relationship known """ if cause in self.causal_graph and effect in self.causal_graph[cause]: relationship = self.causal_graph[cause][effect] return { "cause": cause, "effect": effect, "strength": relationship["strength"], "mechanism": relationship["mechanism"], "evidence": relationship["evidence"], "interpretation": self._interpret_strength(relationship["strength"]) } return None def find_causes(self, effect: str) -> List[Dict]: """Find all known causes of an effect""" causes = [] for cause, effects in self.causal_graph.items(): if effect in effects: relationship = effects[effect] causes.append({ "cause": cause, "strength": relationship["strength"], "mechanism": relationship["mechanism"][:100] + "..." }) # Sort by strength causes.sort(key=lambda x: x["strength"], reverse=True) return causes def find_effects(self, cause: str) -> List[Dict]: """Find all known effects of a cause""" if cause not in self.causal_graph: return [] effects = [] for effect, relationship in self.causal_graph[cause].items(): effects.append({ "effect": effect, "strength": relationship["strength"], "mechanism": relationship["mechanism"][:100] + "..." }) # Sort by strength effects.sort(key=lambda x: x["strength"], reverse=True) return effects def calculate_attribution( self, effect: str, signal_values: Dict[str, float] ) -> Dict: """ Calculate how much each cause contributes to an effect Example: "Safety Index = 45. What causes this?" → Lighting: 31% of variation → Maintenance: 22% of variation → Pedestrian flow: 18% of variation """ causes = self.find_causes(effect) if not causes: return { "effect": effect, "status": "unknown", "message": f"No causal model for {effect}" } # Calculate attribution attributions = [] total_strength = sum(c["strength"] for c in causes) for cause in causes: cause_name = cause["cause"] current_value = signal_values.get(cause_name, 50) # Attribution = strength * (deviation from optimal) deviation = abs(50 - current_value) / 50 # 0 = optimal, 1 = worst attribution = cause["strength"] / total_strength * (1 - deviation) attributions.append({ "cause": cause_name, "attribution_percent": round(attribution * 100, 1), "current_value": current_value, "potential_improvement": round(deviation * 100, 1), "mechanism": cause["mechanism"] }) # Sort by attribution attributions.sort(key=lambda x: x["attribution_percent"], reverse=True) return { "effect": effect, "current_value": signal_values.get(effect, 50), "total_attribution": round(sum(a["attribution_percent"] for a in attributions), 1), "attributions": attributions, "top_driver": attributions[0]["cause"] if attributions else None } def recommend_interventions( self, target: str, current_value: float, target_value: float ) -> List[Dict]: """ Recommend interventions to achieve a target Example: "Improve safety from 45 to 70" → Install lighting (expected improvement: +15) → Increase maintenance (expected improvement: +8) → Activate pedestrian flow (expected improvement: +5) """ causes = self.find_causes(target) interventions = [] for cause in causes: cause_name = cause["cause"] strength = cause["strength"] # Expected improvement gap = target_value - current_value expected_improvement = gap * strength interventions.append({ "intervention": f"Improve {cause_name}", "target_cause": cause_name, "expected_improvement": round(expected_improvement, 1), "confidence": round(strength, 2), "mechanism": cause["mechanism"], "priority": "high" if expected_improvement > gap * 0.3 else "medium" }) # Sort by expected improvement interventions.sort(key=lambda x: x["expected_improvement"], reverse=True) return interventions def _interpret_strength(self, strength: float) -> str: """Interpret causal strength""" if strength >= 0.7: return "Strong causal relationship" elif strength >= 0.5: return "Moderate causal relationship" elif strength >= 0.3: return "Weak causal relationship" else: return "Very weak causal relationship" # Example usage def example_causal_analysis(): """Example: Causal analysis""" engine = CausalEngine() # Explain causality print("=== Causal Explanation ===") explanation = engine.explain_causality("lighting", "safety_index") if explanation: print(f"{explanation['cause']} → {explanation['effect']}") print(f"Strength: {explanation['strength']} ({explanation['interpretation']})") print(f"Mechanism: {explanation['mechanism']}") print("Evidence:") for ev in explanation['evidence']: print(f" - {ev['study']}: {ev['effect']}") # Find causes of safety print("\n=== Causes of Safety Index ===") causes = engine.find_causes("safety_index") for cause in causes: print(f" {cause['cause']}: {cause['strength']}") # Attribution analysis print("\n=== Attribution Analysis ===") signal_values = { "lighting": 35, "maintenance_quality": 40, "pedestrian_flow": 60, "safety_index": 45 } attribution = engine.calculate_attribution("safety_index", signal_values) print(f"Safety Index = {attribution['current_value']}") print("Attributions:") for attr in attribution['attributions']: print(f" {attr['cause']}: {attr['attribution_percent']}%") print(f" Current: {attr['current_value']}, Potential: {attr['potential_improvement']}%") # Recommend interventions print("\n=== Recommended Interventions ===") interventions = engine.recommend_interventions("safety_index", 45, 70) for intervention in interventions: print(f" {intervention['intervention']}") print(f" Expected improvement: +{intervention['expected_improvement']}") print(f" Priority: {intervention['priority']}") return engine if __name__ == '__main__': example_causal_analysis()