Files
boc/iom/sync/mobile_adapter.py
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- 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
2026-06-29 16:24:48 +00:00

334 lines
12 KiB
Python

"""
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()