Spaces:
Running
Running
| import gradio as gr | |
| from datetime import datetime | |
| import sys | |
| import threading | |
| from agents.orchestrator import ClimateRiskOrchestrator | |
| from tools.mapping_utils import ( | |
| COUNTRIES_AND_CITIES, | |
| US_STATES, | |
| get_coordinates_from_dropdown, | |
| create_risk_map, | |
| get_city_suggestions, | |
| ) | |
| # === LogCatcher === | |
| class LogCatcher: | |
| def __init__(self): | |
| self.buffer = "" | |
| self.lock = threading.Lock() | |
| self._stdout = sys.stdout | |
| self._stderr = sys.stderr | |
| def write(self, msg): | |
| with self.lock: | |
| self.buffer += msg | |
| self._stdout.write(msg) | |
| def flush(self): | |
| pass | |
| def get_logs(self): | |
| with self.lock: | |
| return self.buffer | |
| def clear(self): | |
| with self.lock: | |
| self.buffer = "" | |
| def redirect(self): | |
| sys.stdout = self | |
| sys.stderr = self | |
| def restore(self): | |
| sys.stdout = self._stdout | |
| sys.stderr = self._stderr | |
| def isatty(self): | |
| return False | |
| def fileno(self): | |
| return self._stdout.fileno() | |
| logcatcher = LogCatcher() | |
| logcatcher.redirect() | |
| class ClimateRiskUI: | |
| """User interface for the climate risk system with dropdown and map functionality.""" | |
| def __init__(self, model): | |
| self.orchestrator = ClimateRiskOrchestrator(model) | |
| self.theme = gr.themes.Soft( | |
| primary_hue="blue", secondary_hue="gray", neutral_hue="slate" | |
| ) | |
| def update_business_visibility(self, profile_type): | |
| show_business = profile_type == "Business Owner" | |
| return gr.Dropdown(visible=show_business) | |
| def analyze_with_dropdown( | |
| self, | |
| country, | |
| city, | |
| state, | |
| profile_type, | |
| business_type, | |
| vulnerable_groups, | |
| ): | |
| logcatcher.clear() | |
| if not country or not city: | |
| return ( | |
| "Please select both country and city.", | |
| "", | |
| "", | |
| ) | |
| coords_result, validation_message = get_coordinates_from_dropdown(country, city, state) | |
| if coords_result is None: | |
| return validation_message, "", "" | |
| lat, lon = coords_result | |
| state_info = f", {state}" if state else "" | |
| location_full = f"{city}{state_info}, {country}" | |
| base_query = f"Perform a comprehensive climate risk assessment for {location_full}." | |
| profile_context = "" | |
| if profile_type.lower() == "business owner": | |
| business_detail = f" as a {business_type}" if business_type else "" | |
| profile_context = ( | |
| f" Focus on business continuity risks{business_detail}, including supply chain vulnerabilities, operational disruptions, infrastructure threats, customer safety, inventory protection, and revenue continuity. Consider industry-specific vulnerabilities and regulatory compliance requirements." | |
| ) | |
| elif profile_type.lower() == "farmer/agriculture": | |
| profile_context = " Emphasize agricultural risks including crop threats, soil conditions, water availability, extreme weather impacts on farming operations, and seasonal climate patterns." | |
| elif profile_type.lower() == "emergency manager": | |
| profile_context = " Prioritize emergency management perspectives including evacuation planning, critical infrastructure vulnerabilities, community preparedness needs, and multi-hazard scenarios." | |
| else: | |
| profile_context = " Focus on residential safety, household preparedness, health impacts, and community-level risks." | |
| vulnerable_context = "" | |
| if vulnerable_groups: | |
| groups_text = ", ".join(vulnerable_groups) | |
| vulnerable_context = f" Pay special attention to impacts on vulnerable populations: {groups_text}." | |
| analysis_requirements = ( | |
| " Analyze earthquake, wildfire, flood, and extreme weather risks. Provide specific risk levels (0-100 scale), contributing factors, time horizons, and confidence levels. Include recent data and current conditions." | |
| ) | |
| user_query = base_query + profile_context + vulnerable_context + analysis_requirements | |
| user_profile = { | |
| "type": profile_type.lower(), | |
| "business_type": business_type if profile_type.lower() == "business owner" else None, | |
| "vulnerable_groups": vulnerable_groups or [], | |
| } | |
| print(f"[{datetime.now()}] Analyse : {user_query}") | |
| result = self.orchestrator.analyze_and_recommend(user_query, user_profile) | |
| if "error" in result: | |
| print(f"[ERROR] {result['error']}") | |
| return f"Error: {result['error']}", "", "" | |
| risk_summary = self._format_risk_analysis(result["risk_analysis"]) | |
| recommendations_text = self._format_recommendations(result["recommendations"], profile_type) | |
| enhanced_map = create_risk_map(lat, lon, city, country, result["risk_analysis"]) | |
| return risk_summary, recommendations_text, enhanced_map | |
| def update_map_from_location(self, country, city, state=None): | |
| if not country or not city: | |
| return "Please select both country and city.", "" | |
| coords_result, validation_message = get_coordinates_from_dropdown(country, city, state) | |
| if coords_result is None: | |
| return validation_message, "" | |
| lat, lon = coords_result | |
| risk_map = create_risk_map(lat, lon, city, country) | |
| return validation_message, risk_map | |
| def update_cities(self, country): | |
| suggestions = get_city_suggestions(country) | |
| show_state = country == "United States" | |
| country_centers = { | |
| "France": (48.8566, 2.3522), | |
| "United States": (39.8283, -98.5795), | |
| "United Kingdom": (51.5074, -0.1278), | |
| "Germany": (52.5200, 13.4050), | |
| "Japan": (35.6762, 139.6503), | |
| "Canada": (45.4215, -75.7040), | |
| "Australia": (-35.2809, 149.1300), | |
| "Italy": (41.9028, 12.4964), | |
| "Spain": (40.4168, -3.7038), | |
| "China": (39.9042, 116.4074), | |
| "India": (28.6139, 77.2090), | |
| "Brazil": (-15.7975, -47.8919), | |
| } | |
| lat, lon = country_centers.get(country, (48.8566, 2.3522)) | |
| basic_map = create_risk_map(lat, lon, f"Select a city in {country}", country) | |
| return suggestions, gr.Dropdown(visible=show_state), basic_map | |
| def analyze_user_input( | |
| self, | |
| user_query: str, | |
| profile_type: str, | |
| business_type: str, | |
| vulnerable_groups: list = None, | |
| ): | |
| logcatcher.clear() | |
| if not user_query.strip(): | |
| return ( | |
| "Please enter your climate risk question or location.", | |
| "", | |
| "<div style='text-align: center; padding: 50px; background-color: #f0f0f0; border-radius: 10px;'>Map will appear here after analysis.</div>", | |
| ) | |
| user_profile = { | |
| "type": profile_type.lower(), | |
| "business_type": business_type if profile_type.lower() == "business owner" else None, | |
| "vulnerable_groups": vulnerable_groups or [], | |
| } | |
| print(f"[{datetime.now()}] Analyse: {user_query}") | |
| result = self.orchestrator.analyze_and_recommend(user_query, user_profile) | |
| if "error" in result: | |
| print(f"[ERROR] {result['error']}") | |
| return f"Error: {result['error']}", "", "" | |
| risk_summary = self._format_risk_analysis(result["risk_analysis"]) | |
| recommendations_text = self._format_recommendations(result["recommendations"], profile_type) | |
| location = result["risk_analysis"].get("location", {}) | |
| lat = location.get("lat", 0) | |
| lon = location.get("lon", 0) | |
| city = location.get("city", "Unknown") | |
| country = location.get("country", "Unknown") | |
| enhanced_map = create_risk_map(lat, lon, city, country, result["risk_analysis"]) | |
| return risk_summary, recommendations_text, enhanced_map | |
| def _format_risk_analysis(self, risk_analysis: dict) -> str: | |
| if not risk_analysis or "error" in risk_analysis: | |
| return "Risk analysis not available or failed." | |
| formatted = f"# 🌍 Climate Risk Analysis\n\n" | |
| location = risk_analysis.get("location", {}) | |
| if location: | |
| formatted += f"**Location:** {location.get('city', 'Unknown')}, {location.get('country', '')}\n" | |
| formatted += f"**Coordinates:** {location.get('lat', 0):.4f}°N, {location.get('lon', 0):.4f}°E\n\n" | |
| formatted += f"**Analysis Date:** {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n" | |
| overall = risk_analysis.get("overall_assessment", "No overall assessment available.") | |
| formatted += f"## 📊 Overall Assessment\n{overall}\n\n" | |
| risks = risk_analysis.get("risk_analysis", {}) | |
| if risks: | |
| formatted += "## 🎯 Individual Risk Assessment\n\n" | |
| for risk_name, risk_data in risks.items(): | |
| if isinstance(risk_data, dict): | |
| risk_level = risk_data.get("risk_level", 0) | |
| if risk_level > 80: | |
| emoji = "🔴" | |
| level_text = "VERY HIGH" | |
| elif risk_level > 60: | |
| emoji = "🟠" | |
| level_text = "HIGH" | |
| elif risk_level > 40: | |
| emoji = "🟡" | |
| level_text = "MODERATE" | |
| elif risk_level > 20: | |
| emoji = "🟢" | |
| level_text = "LOW" | |
| else: | |
| emoji = "⚪" | |
| level_text = "MINIMAL" | |
| formatted += f"### {emoji} {risk_name.title()} Risk\n" | |
| formatted += f"**Risk Level:** {level_text} ({risk_level}/100)\n" | |
| formatted += f"**Time Horizon:** {risk_data.get('time_horizon', 'Unknown')}\n" | |
| formatted += f"**Confidence:** {risk_data.get('confidence', 'Unknown')}\n\n" | |
| if risk_data.get("key_insights"): | |
| formatted += f"**Analysis:** {risk_data['key_insights']}\n\n" | |
| factors = risk_data.get("contributing_factors", []) | |
| if factors: | |
| formatted += f"**Key Factors:** {', '.join(factors)}\n\n" | |
| return formatted | |
| def _format_recommendations(self, recommendations: dict, profile_type: str) -> str: | |
| if not recommendations: | |
| return "No recommendations available." | |
| formatted = f"# 🎯 Personalized Recommendations for {profile_type} **[survivalist mode]**\n\n" | |
| if "emergency" in recommendations: | |
| formatted += "## 🚨 Emergency Preparedness\n" | |
| for rec in recommendations["emergency"]: | |
| formatted += f"- {rec}\n" | |
| formatted += "\n" | |
| if "household" in recommendations: | |
| formatted += "## 🏠 Household Adaptations\n" | |
| for rec in recommendations["household"]: | |
| formatted += f"- {rec}\n" | |
| formatted += "\n" | |
| if "business" in recommendations: | |
| formatted += "## 🏢 Business Continuity\n" | |
| for rec in recommendations["business"]: | |
| formatted += f"- {rec}\n" | |
| formatted += "\n" | |
| if "financial" in recommendations: | |
| formatted += "## 💰 Financial Planning\n" | |
| for rec in recommendations["financial"]: | |
| formatted += f"- {rec}\n" | |
| formatted += "\n" | |
| formatted += "---\n" | |
| formatted += "*Recommendations generated by AI agents based on current risk analysis and your profile.*" | |
| return formatted | |
| def create_interface(self): | |
| def get_logs(): | |
| return logcatcher.get_logs() | |
| with gr.Blocks( | |
| theme=self.theme, title="🛰️ Sentinel One – Climate Risk Evaluation MultiAgents" | |
| ) as app: | |
| gr.Markdown( | |
| """ | |
| # 🛰️ Sentinel One – Climate Risk Evaluation MultiAgents | |
| <div style='background: linear-gradient(90deg, #f6f8fa 0%, #e2eafc 100%); border-radius: 10px; padding: 16px 18px; font-size: 16px; margin-bottom: 10px;'> | |
| <b>🤖 What does Sentinel One do?</b> | |
| <br><br> | |
| Sentinel One's AI agents instantly analyze climate risks <b>( | |
| 🌪️ Weather, | |
| 🌊 Flood, | |
| 🌍 Earthquake, | |
| 🔥 Wildfire, | |
| 🌫️ Air quality, | |
| 📈 Climate trends, | |
| ☀️ Solar radiation, | |
| 🌊 Marine forecast | |
| )</b> for any location, providing you with clear, actionable recommendations. | |
| <br><br> | |
| <i>Analysis is fully automated, always up to date, and based on leading data sources: OpenStreetMap 🗺️, Open-Meteo 🌦️, USGS 🌎, NASA FIRMS 🔥.</i> | |
| <br><br> | |
| <b>How to use Sentinel One?</b><br> | |
| Use the <b>quick location selection</b> (dropdowns and map) 🌍, or ask complex, personalized questions in <b>natural language</b> 💬. | |
| </div> | |
| """ | |
| ) | |
| with gr.Tabs(): | |
| with gr.TabItem("📍 Quick Location Selection"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| country_dropdown = gr.Dropdown( | |
| choices=list(COUNTRIES_AND_CITIES.keys()), | |
| label="Select Country", | |
| value="France", | |
| interactive=True, | |
| ) | |
| city_input = gr.Textbox( | |
| label="Enter City Name", | |
| placeholder="e.g., Bordeaux, Lyon, Marseille, ...", | |
| value="Lorient", | |
| interactive=True, | |
| info="Enter any city name in the selected country", | |
| ) | |
| state_dropdown = gr.Dropdown( | |
| choices=US_STATES, | |
| label="Select State (US only)", | |
| value="California", | |
| visible=False, | |
| interactive=True, | |
| info="Select state for US locations", | |
| ) | |
| city_suggestions = gr.Markdown( | |
| get_city_suggestions("France"), visible=True | |
| ) | |
| with gr.Column(): | |
| profile_dropdown = gr.Dropdown( | |
| choices=[ | |
| "General Public", | |
| "Business Owner", | |
| "Farmer/Agriculture", | |
| "Emergency Manager", | |
| ], | |
| label="Your Profile", | |
| value="General Public", | |
| ) | |
| vulnerable_groups = gr.CheckboxGroup( | |
| choices=[ | |
| "Elderly", | |
| "Children", | |
| "Chronic Health Conditions", | |
| "Pregnant", | |
| ], | |
| label="Vulnerable Groups in Household", | |
| ) | |
| business_type_dropdown = gr.Dropdown( | |
| choices=[ | |
| "Restaurant/Food Service", | |
| "Retail Store", | |
| "Manufacturing", | |
| "Construction", | |
| "Healthcare Facility", | |
| "Educational Institution", | |
| "Technology/Software", | |
| "Transportation/Logistics", | |
| "Tourism/Hospitality", | |
| "Financial Services", | |
| "Real Estate", | |
| "Agriculture/Farming", | |
| "Energy/Utilities", | |
| "Entertainment/Events", | |
| "Professional Services", | |
| "Small Office", | |
| "Warehouse/Distribution", | |
| "Other", | |
| ], | |
| label="Business Type", | |
| value="Retail Store", | |
| visible=False, | |
| interactive=True, | |
| info="Select your business type for specialized recommendations", | |
| ) | |
| with gr.Row(): | |
| analyze_location_btn = gr.Button( | |
| "🔍 Analyze This Location", variant="primary", size="lg" | |
| ) | |
| with gr.Row(): | |
| gr.HTML(""" | |
| <div style="display: flex; align-items: center; gap: 10px;"> | |
| <h3 style="margin: 0;">🛰️ Agentic Logs</h3> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| logs_box = gr.Textbox( | |
| value=logcatcher.get_logs(), | |
| label="Logs", | |
| lines=17, | |
| max_lines=25, | |
| interactive=False, | |
| elem_id="terminal_logs", | |
| show_copy_button=True, | |
| container=False, | |
| ) | |
| logs_timer = gr.Timer(0.5) | |
| logs_timer.tick(get_logs, None, logs_box) | |
| with gr.Row(): | |
| location_map = gr.HTML( | |
| create_risk_map(47.7486, -3.3667, "Lorient", "France"), | |
| label="Interactive Risk Map", | |
| ) | |
| with gr.Row(): | |
| location_status = gr.Markdown("", visible=True) | |
| # Résumé d'analyse dans un cadre custom (CSS) | |
| with gr.Row(): | |
| dropdown_risk_summary = gr.Markdown( | |
| "Select a location above to begin analysis.", | |
| label="Risk Assessment Summary", | |
| elem_id="risk_summary_box", | |
| ) | |
| # Recommandations dans un cadre custom (CSS) | |
| with gr.Row(): | |
| dropdown_recommendations = gr.Markdown( | |
| "Recommendations will appear here after analysis.", | |
| label="AI-Generated Recommendations", | |
| elem_id="recommendations_box", | |
| ) | |
| with gr.TabItem("💬 Natural Language Query"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| user_query = gr.Textbox( | |
| label="Your Climate Risk Question", | |
| placeholder="Will New York get flooded tomorrow if we don't win the Hackaton ?", | |
| lines=3, | |
| info="Be as specific as possible about location, timeframe, and what you're concerned about.", | |
| ) | |
| gr.Markdown( | |
| """ | |
| **Examples:** | |
| - "What are the wildfire risks in Los Angeles this week?" | |
| - "I live in Lorient (Bretagne), can I run outside this evening ?" | |
| - "I'm planning to move to Miami, what climate risks should I be aware of?" | |
| - "How should my farm in Iowa prepare for climate change?" | |
| - "What emergency preparations should my business in Tokyo make for earthquakes?" | |
| """ | |
| ) | |
| with gr.Column(scale=1): | |
| nl_profile_type = gr.Dropdown( | |
| choices=[ | |
| "General Public", | |
| "Business Owner", | |
| "Farmer/Agriculture", | |
| "Emergency Manager", | |
| ], | |
| label="Your Profile", | |
| value="General Public", | |
| ) | |
| nl_business_type_dropdown = gr.Dropdown( | |
| choices=[ | |
| "Restaurant/Food Service", | |
| "Retail Store", | |
| "Manufacturing", | |
| "Construction", | |
| "Healthcare Facility", | |
| "Educational Institution", | |
| "Technology/Software", | |
| "Transportation/Logistics", | |
| "Tourism/Hospitality", | |
| "Financial Services", | |
| "Real Estate", | |
| "Agriculture/Farming", | |
| "Energy/Utilities", | |
| "Entertainment/Events", | |
| "Professional Services", | |
| "Small Office", | |
| "Warehouse/Distribution", | |
| "Other", | |
| ], | |
| label="Business Type", | |
| value="Retail Store", | |
| visible=False, | |
| interactive=True, | |
| info="Select your business type for specialized recommendations", | |
| ) | |
| nl_vulnerable_groups = gr.CheckboxGroup( | |
| choices=[ | |
| "Elderly", | |
| "Children", | |
| "Chronic Health Conditions", | |
| "Pregnant", | |
| ], | |
| label="Vulnerable Groups in Household", | |
| ) | |
| analyze_btn = gr.Button( | |
| "🔍 Analyze Query & Get Recommendations", | |
| variant="primary", | |
| size="lg", | |
| ) | |
| with gr.Row(): | |
| gr.HTML(""" | |
| <div style="display: flex; align-items: center; gap: 10px;"> | |
| <h3 style="margin: 0;">🛰️ Agentic Logs</h3> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| nl_logs_box = gr.Textbox( | |
| value=logcatcher.get_logs(), | |
| label="Logs", | |
| lines=17, | |
| max_lines=25, | |
| interactive=False, | |
| elem_id="nl_terminal_logs", | |
| show_copy_button=True, | |
| container=False, | |
| ) | |
| nl_logs_timer = gr.Timer(0.5) | |
| nl_logs_timer.tick(get_logs, None, nl_logs_box) | |
| with gr.Row(): | |
| nl_location_map = gr.HTML( | |
| "<div style='text-align: center; padding: 50px; background-color: #f0f0f0; border-radius: 10px;'>Map will appear here after analysis.</div>", | |
| label="Interactive Risk Map", | |
| ) | |
| # Résultats d'analyse en langage naturel dans un cadre custom (CSS) | |
| with gr.Row(): | |
| risk_analysis_output = gr.Markdown( | |
| "Enter your question above to get started.", | |
| label="Risk Analysis", | |
| elem_id="nl_risk_box", | |
| ) | |
| # Recommandations NL dans un cadre custom (CSS) | |
| with gr.Row(): | |
| recommendations_output = gr.Markdown( | |
| "Personalized recommendations will appear here.", | |
| label="AI-Generated Recommendations", | |
| elem_id="nl_rec_box", | |
| ) | |
| # CSS pour les cadres custom | |
| gr.HTML(""" | |
| <style> | |
| #risk_summary_box, #recommendations_box, #nl_risk_box, #nl_rec_box { | |
| border: 2px solid #007aff; | |
| border-radius: 13px; | |
| background: #fafdff; | |
| box-shadow: 0 2px 12px rgba(80,140,255,0.08); | |
| padding: 20px 15px; | |
| margin-top: 10px; | |
| margin-bottom: 18px; | |
| } | |
| #terminal_logs textarea, #nl_terminal_logs textarea { | |
| background-color: #181a1b !important; | |
| color: #00ff66 !important; | |
| font-family: 'Fira Mono', 'Consolas', monospace !important; | |
| font-size: 15px; | |
| border-radius: 9px !important; | |
| border: 2px solid #31343a !important; | |
| box-shadow: 0 2px 6px rgba(0,0,0,0.19); | |
| padding: 12px 10px !important; | |
| min-height: 320px !important; | |
| max-height: 420px !important; | |
| letter-spacing: 0.5px; | |
| line-height: 1.5; | |
| overflow-y: auto !important; | |
| resize: vertical !important; | |
| scrollbar-width: thin; | |
| scrollbar-color: #6cf97c #282c34; | |
| } | |
| #terminal_logs, #nl_terminal_logs { | |
| width: 100% !important; | |
| } | |
| </style> | |
| """) | |
| profile_dropdown.change( | |
| fn=self.update_business_visibility, | |
| inputs=[profile_dropdown], | |
| outputs=[business_type_dropdown], | |
| ) | |
| nl_profile_type.change( | |
| fn=self.update_business_visibility, | |
| inputs=[nl_profile_type], | |
| outputs=[nl_business_type_dropdown], | |
| ) | |
| country_dropdown.change( | |
| fn=self.update_cities, | |
| inputs=[country_dropdown], | |
| outputs=[city_suggestions, state_dropdown, location_map], | |
| ) | |
| city_input.change( | |
| fn=self.update_map_from_location, | |
| inputs=[country_dropdown, city_input, state_dropdown], | |
| outputs=[location_status, location_map], | |
| ) | |
| analyze_location_btn.click( | |
| fn=self.analyze_with_dropdown, | |
| inputs=[ | |
| country_dropdown, | |
| city_input, | |
| state_dropdown, | |
| profile_dropdown, | |
| business_type_dropdown, | |
| vulnerable_groups, | |
| ], | |
| outputs=[dropdown_risk_summary, dropdown_recommendations, location_map], | |
| show_progress="full", | |
| ) | |
| analyze_btn.click( | |
| fn=self.analyze_user_input, | |
| inputs=[ | |
| user_query, | |
| nl_profile_type, | |
| nl_business_type_dropdown, | |
| nl_vulnerable_groups, | |
| ], | |
| outputs=[ | |
| risk_analysis_output, | |
| recommendations_output, | |
| nl_location_map, | |
| ], | |
| show_progress="full", | |
| ) | |
| return app |