""" Philippines Disaster Risk Assessment MCP Server An MCP (Model Context Protocol) server that provides comprehensive disaster risk assessment data for any location in the Philippines. This server interfaces with the GeoRisk Philippines API to deliver real-time hazard information including: - Seismic hazards (active faults, ground shaking, liquefaction, tsunami) - Volcanic hazards (active volcanoes, pyroclastic flows, ashfall, lahar) - Hydrometeorological hazards (floods, landslides, storm surge, severe winds) - Critical infrastructure proximity (schools, hospitals, road networks) Features: - Intelligent caching (1-hour TTL) to reduce API load - Input validation for geographic coordinates - Secure authentication with GeoRisk API - User-friendly Gradio web interface Usage: - Call with latitude and longitude coordinates (-90 to 90, -180 to 180) - Returns formatted hazard assessment reports with risk levels and recommendations """ import gradio as gr from src.ph_disaster_risk_mcp.api_client import GeoRiskClient from src.ph_disaster_risk_mcp.auth import AuthManager from src.ph_disaster_risk_mcp.cache import CacheManager from src.ph_disaster_risk_mcp.formatter import format_hazard_data, format_error from src.ph_disaster_risk_mcp.exceptions import GeoRiskError # Initialize global instances for authentication, caching, and API client auth_manager = AuthManager() cache_manager = CacheManager(ttl_seconds=3600) # 1-hour cache TTL api_client = GeoRiskClient(auth_manager=auth_manager) def get_risk_data(latitude: float, longitude: float) -> dict: """ Get comprehensive disaster risk assessment for any location in the Philippines. Provides detailed hazard information from the GeoRisk Philippines API including: - Seismic hazards (active faults, ground shaking, liquefaction, tsunami) - Volcanic hazards (active volcanoes, pyroclastic flows, ashfall, lahar) - Hydrometeorological hazards (floods, landslides, storm surge, severe winds) - Critical infrastructure proximity (schools, hospitals, road networks) Returns structured JSON data with automatic risk categorization, summary statistics, and overall risk level assessment (CRITICAL/HIGH/MODERATE/LOW). Args: latitude: Latitude coordinate (-90 to 90 degrees) longitude: Longitude coordinate (-180 to 180 degrees) Returns: dict: Structured disaster risk assessment containing: - success: Boolean indicating success/failure - summary: Risk overview with total_hazards_assessed, high_risk_count, moderate_risk_count, critical_hazards list, overall_risk_level - location: Coordinate and location name information - hazards: Categorized by type (seismic, volcanic, hydrometeorological) - facilities: Organized by category (schools, hospitals, roads) - metadata: Cache status, timestamp, source, and TTL info Example: get_risk_data(14.5995, 120.9842) # Manila, Philippines Returns: {"success": true, "summary": {...}, "location": {...}, ...} """ try: # Check cache for existing response from_cache = False cached_response = cache_manager.get(latitude, longitude) if cached_response is not None: # Cache hit - use cached data response = cached_response from_cache = True else: # Cache miss - make API call response = api_client.get_hazard_assessment(latitude, longitude) # Store response in cache cache_manager.set(latitude, longitude, response) # Extract and structure the data structured_data = _structure_response(response, latitude, longitude, from_cache) return structured_data except GeoRiskError as e: # Handle all GeoRisk-related errors (validation, auth, API errors) return { "error": True, "error_type": type(e).__name__, "message": str(e), "location": {"latitude": latitude, "longitude": longitude} } except Exception as e: # Handle unexpected errors return { "error": True, "error_type": type(e).__name__, "message": str(e), "location": {"latitude": latitude, "longitude": longitude} } def _structure_response(response: dict, latitude: float, longitude: float, from_cache: bool) -> dict: from datetime import datetime # Extract data section data = response.get("data", response) if isinstance(response, dict) else response # Categorize hazards by severity high_risks = [] moderate_risks = [] low_risks = [] # Define hazard categories for structured output hazard_fields = { "seismic": [ "activeFault", "groundRupture", "groundShaking", "eil", "liquefaction", "fissure", "tsunami" ], "volcanic": [ "activeVolcano", "potentiallyActiveVolcano", "inactiveVolcano", "ballisticProjectile", "baseSurge", "lahar", "lava", "pyroclasticFlow", "volcanicFissure", "volcanicTsunami", "pdz", "edz", "ashfall" ], "hydrometeorological": [ "flood", "ril", "stormSurge", "severeWinds" ] } # Process hazards and categorize by risk level categorized_hazards = {} for category, fields in hazard_fields.items(): categorized_hazards[category] = {} for field in fields: hazard = data.get(field, {}) if hazard and isinstance(hazard, dict): assessment = hazard.get("assessment", "").lower() categorized_hazards[category][field] = hazard # Categorize by risk level hazard_info = {"name": field, "assessment": hazard.get("assessment", "N/A")} if any(word in assessment for word in ["high", "High", "very high", "extreme", "critical", "within", "prone", "highly susceptible", "high susceptibility"]): high_risks.append(hazard_info) elif any(word in assessment for word in ["moderate", "moderately", "medium", "present"]): moderate_risks.append(hazard_info) elif any(word in assessment for word in ["low", "minimal", "none", "safe", "outside"]): low_risks.append(hazard_info) # Process critical facilities facilities = { "schools": { "elementary": data.get("elementarySchool", {}), "secondary": data.get("secondarySchool", {}) }, "hospitals": { "government": data.get("governmentHospital", {}), "private": data.get("privateHospital", {}) }, "roads": { "primary": data.get("primaryRoadNetwork", {}), "secondary": data.get("secondaryRoadNetwork", {}) } } # Build structured response return { "success": True, "summary": { "total_hazards_assessed": sum(len(v) for v in categorized_hazards.values()), "high_risk_count": len(high_risks), "moderate_risk_count": len(moderate_risks), "low_risk_count": len(low_risks), "critical_hazards": high_risks[:5], # Top 5 critical hazards "overall_risk_level": _calculate_overall_risk(len(high_risks), len(moderate_risks)) }, "location": { "latitude": latitude, "longitude": longitude, "name": data.get("location", {}).get("name", "Unknown Location") }, "hazards": categorized_hazards, "facilities": facilities, "metadata": { "from_cache": from_cache, "timestamp": datetime.utcnow().isoformat() + "Z", "source": "GeoRisk Philippines API", "cache_ttl_seconds": 3600 } } def _calculate_overall_risk(high_count: int, moderate_count: int) -> str: if high_count >= 3: return "CRITICAL" elif high_count >= 1: return "HIGH" elif moderate_count >= 3: return "MODERATE" elif moderate_count >= 1: return "LOW-MODERATE" else: return "LOW" def get_risk_data_html(data: dict) -> str: """ Format disaster risk data as HTML for web display. Takes structured JSON data from get_risk_data() and converts it to a beautiful HTML display with color-coded risk summary banner and detailed hazard information organized by category. This function is useful for: - Gradio web UI display - Embedding in web pages - Custom HTML rendering needs For MCP/API usage, use get_risk_data() instead which returns structured JSON. Args: data: Structured risk assessment dict from get_risk_data() Returns: str: HTML formatted string with risk assessment display including: - Color-coded summary banner (red/yellow/green based on risk level) - Overall risk level and hazard counts - Detailed seismic, volcanic, and hydrometeorological hazards - Critical facilities information Example: json_data = get_risk_data(14.5995, 120.9842) html = get_risk_data_html(json_data) """ # Handle errors if data.get("error"): return format_error_display(data) # Use the original formatter by reconstructing the format it expects # This maintains backward compatibility with the existing formatter reconstructed_data = { "data": { "location": {"name": data["location"]["name"]}, "coordinates": { "latitude": data["location"]["latitude"], "longitude": data["location"]["longitude"] } } } # Add all hazards back for category in data["hazards"].values(): for field_name, field_data in category.items(): reconstructed_data["data"][field_name] = field_data # Add facilities back reconstructed_data["data"]["elementarySchool"] = data["facilities"]["schools"]["elementary"] reconstructed_data["data"]["secondarySchool"] = data["facilities"]["schools"]["secondary"] reconstructed_data["data"]["governmentHospital"] = data["facilities"]["hospitals"]["government"] reconstructed_data["data"]["privateHospital"] = data["facilities"]["hospitals"]["private"] reconstructed_data["data"]["primaryRoadNetwork"] = data["facilities"]["roads"]["primary"] reconstructed_data["data"]["secondaryRoadNetwork"] = data["facilities"]["roads"]["secondary"] # Add summary banner at the top summary_html = f"""
{data['summary']['high_risk_count']} high-risk hazards | {data['summary']['moderate_risk_count']} moderate-risk hazards | {data['summary']['total_hazards_assessed']} total hazards assessed
{('🚨 Critical: ' + ', '.join([h['name'] for h in data['summary']['critical_hazards'][:3]]) + '
') if data['summary']['critical_hazards'] else ''}{error_data.get('message', 'An error occurred')}