""" Mobile API Adapter — Optimizes IOM data for quiXzoom mobile app Mobile-first: iPhone-first, always. One hand, one thumb, three seconds. """ from typing import Dict, List, Optional from datetime import datetime class MobileAdapter: """Adapts IOM data for mobile consumption""" def __init__(self): self.max_payload_size = 50 * 1024 # 50KB max per response def adapt_observation(self, observation: Dict) -> Dict: """ Adapt observation for mobile app Strip unnecessary fields, optimize images """ return { "id": observation.get("id"), "goid": observation.get("goid"), "type": self._get_mobile_type(observation.get("goid", "")), "condition": observation.get("overall_condition", 3), "condition_label": self._get_condition_label(observation.get("overall_condition", 3)), "location": { "lat": observation.get("latitude"), "lng": observation.get("longitude"), "address": observation.get("address", "Unknown") }, "thumbnail": observation.get("thumbnail_url"), "findings_count": len(observation.get("findings", [])), "timestamp": observation.get("timestamp"), "synced": True } def adapt_rgi_for_mobile(self, rgi_result: Dict) -> Dict: """ Adapt RGI for mobile display Simplified, visual, actionable """ return { "location": rgi_result.get("location"), "rgi_score": round(rgi_result.get("overall_rgi", 0)), "rgi_color": self._get_rgi_color(rgi_result.get("overall_rgi", 0)), "level": rgi_result.get("contradiction_level"), "alert": rgi_result.get("overall_rgi", 0) > 50, # Simplified subscores for mobile "dimensions": [ { "name": "Physical", "score": round(rgi_result.get("subscores", {}).get("physical_reality_gap", {}).get("score", 0)), "icon": "building" }, { "name": "Safety", "score": round(rgi_result.get("subscores", {}).get("safety_reality_gap", {}).get("score", 0)), "icon": "shield" }, { "name": "Economic", "score": round(rgi_result.get("subscores", {}).get("economic_reality_gap", {}).get("score", 0)), "icon": "dollar" } ], # Top contradiction for mobile alert "top_contradiction": self._get_top_contradiction(rgi_result), # Actionable insight "insight": self._generate_insight(rgi_result) } def adapt_rcs_for_mobile(self, rcs_result: Dict) -> Dict: """ Adapt RCS for mobile display Alert-style, immediate """ return { "location": rcs_result.get("location"), "rcs_score": round(rcs_result.get("rcs_score", 0)), "alert_level": self._get_alert_level(rcs_result.get("rcs_score", 0)), "alert_color": self._get_alert_color(rcs_result.get("rcs_score", 0)), # Strongest contradiction "strongest": self._get_strongest_contradiction(rcs_result), # Count summary "summary": { "total": rcs_result.get("contradiction_count", 0), "critical": rcs_result.get("critical_count", 0), "major": rcs_result.get("major_count", 0) } } def adapt_uli_for_mobile(self, uli_result: Dict) -> Dict: """ Adapt ULI for mobile Visual layer representation """ layers = uli_result.get("layers", {}) return { "location": uli_result.get("location"), "uli_score": round(uli_result.get("uli", 0)), "reality_count": uli_result.get("reality_count", 0), # Visual layer bars "layers": [ { "name": "Formal", "score": round(layers.get("formal", {}).get("score", 0)), "color": "#4A90E2", "dominant": layers.get("formal", {}).get("dominant", False) }, { "name": "Functional", "score": round(layers.get("functional", {}).get("score", 0)), "color": "#F5A623", "dominant": layers.get("functional", {}).get("dominant", False) }, { "name": "Informal", "score": round(layers.get("informal", {}).get("score", 0)), "color": "#D0021B", "dominant": layers.get("informal", {}).get("dominant", False) } ], # Dominant layer "dominant_layer": uli_result.get("dominant_layer", "unknown"), # Interpretation "interpretation": uli_result.get("interpretation", "") } def adapt_dashboard_summary(self, data: Dict) -> Dict: """ Adapt dashboard data for mobile summary view Cards-style, swipeable """ return { "cards": [ { "type": "rgi", "title": "Reality Gap", "score": round(data.get("rgi", 0)), "trend": data.get("rgi_trend", "stable"), "color": self._get_rgi_color(data.get("rgi", 0)) }, { "type": "rcs", "title": "Contradictions", "score": round(data.get("rcs", 0)), "count": data.get("contradiction_count", 0), "color": self._get_alert_color(data.get("rcs", 0)) }, { "type": "uli", "title": "Urban Layers", "score": round(data.get("uli", 0)), "realities": data.get("reality_count", 0), "color": "#9013FE" } ], "last_updated": datetime.utcnow().isoformat() } def _get_mobile_type(self, goid: str) -> str: """Get mobile-friendly type from GOID""" parts = goid.split("-") if len(parts) >= 4: type_map = { "WIN": "Window", "ROD": "Road", "SGN": "Sign", "LIG": "Light", "CHA": "Charger", "PAV": "Pavement", "FEN": "Fence", "PIP": "Pipe", "CAB": "Cabinet", "ANT": "Antenna" } return type_map.get(parts[3], parts[3]) return "Unknown" def _get_condition_label(self, condition: int) -> str: """Get human-readable condition label""" labels = { 1: "Excellent", 2: "Good", 3: "Fair", 4: "Poor", 5: "Critical" } return labels.get(condition, "Unknown") def _get_rgi_color(self, score: float) -> str: """Get color for RGI score""" if score < 20: return "#4CAF50" # Green elif score < 40: return "#8BC34A" # Light green elif score < 60: return "#FFC107" # Yellow elif score < 80: return "#FF9800" # Orange else: return "#F44336" # Red def _get_alert_level(self, score: float) -> str: """Get alert level""" if score < 10: return "low" elif score < 30: return "medium" elif score < 50: return "high" else: return "critical" def _get_alert_color(self, score: float) -> str: """Get alert color""" if score < 10: return "#4CAF50" elif score < 30: return "#FFC107" elif score < 50: return "#FF9800" else: return "#F44336" def _get_top_contradiction(self, rgi_result: Dict) -> Optional[Dict]: """Get top contradiction for mobile""" contradictions = rgi_result.get("contradictions", []) if contradictions: top = contradictions[0] return { "type": top.get("type"), "severity": top.get("severity"), "description": top.get("description", "")[:100] # Truncate for mobile } return None def _get_strongest_contradiction(self, rcs_result: Dict) -> Optional[Dict]: """Get strongest contradiction for mobile""" strongest = rcs_result.get("strongest_contradictions", []) if strongest: top = strongest[0] return { "type": top.get("type"), "severity": top.get("severity"), "description": top.get("description", "")[:80] } return None def _generate_insight(self, rgi_result: Dict) -> str: """Generate actionable insight""" score = rgi_result.get("overall_rgi", 0) if score < 20: return "Low gap. Systems functioning as intended." elif score < 40: return "Moderate gap. Some systems need attention." elif score < 60: return "Significant gap. Multiple systems underperforming." elif score < 80: return "Major gap. Critical intervention needed." else: return "Critical gap. System failure likely." # Example usage def example_mobile_adaptation(): """Example: Adapt data for mobile""" adapter = MobileAdapter() # Example RGI result rgi = { "location": "Bangkok Silom", "overall_rgi": 38.77, "contradiction_level": "medium", "subscores": { "physical_reality_gap": {"score": 63.33}, "operational_reality_gap": {"score": 16.67}, "safety_reality_gap": {"score": 0.0}, "institutional_reality_gap": {"score": 70.0}, "economic_reality_gap": {"score": 40.0}, "maintenance_reality_gap": {"score": 25.0}, "human_experience_gap": {"score": 20.0} }, "contradictions": [ {"type": "planned_vs_constructed", "severity": "high", "description": "Planned 3 buildings but observed 5"} ] } mobile_rgi = adapter.adapt_rgi_for_mobile(rgi) print("=== Mobile RGI ===") print(f"Score: {mobile_rgi['rgi_score']}") print(f"Color: {mobile_rgi['rgi_color']}") print(f"Alert: {mobile_rgi['alert']}") print(f"Insight: {mobile_rgi['insight']}") # Example ULI uli = { "location": "Bangkok Silom", "uli": 41.16, "reality_count": 3, "dominant_layer": "functional", "interpretation": "3 realities coexist", "layers": { "formal": {"score": 75.0, "dominant": False}, "functional": {"score": 82.5, "dominant": True}, "informal": {"score": 45.0, "dominant": False} } } mobile_uli = adapter.adapt_uli_for_mobile(uli) print("\n=== Mobile ULI ===") print(f"Score: {mobile_uli['uli_score']}") print(f"Realities: {mobile_uli['reality_count']}") print(f"Dominant: {mobile_uli['dominant_layer']}") return mobile_rgi, mobile_uli if __name__ == '__main__': example_mobile_adaptation()