anasfsd123 commited on
Commit
4ea315a
Β·
verified Β·
1 Parent(s): 50e5022

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +778 -0
app.py ADDED
@@ -0,0 +1,778 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ from datetime import datetime, timedelta
4
+ import pandas as pd
5
+ import plotly.express as px
6
+ import plotly.graph_objects as go
7
+ from plotly.subplots import make_subplots
8
+ import numpy as np
9
+ import os
10
+ import folium
11
+ from streamlit_folium import st_folium
12
+ import json
13
+ import time
14
+ from geopy.geocoders import Nominatim
15
+ from geopy.distance import geodesic
16
+ import warnings
17
+ warnings.filterwarnings('ignore')
18
+
19
+ # Secure API key handling
20
+ def get_groq_api_key():
21
+ """Securely get GROQ API key from environment variables or Streamlit secrets"""
22
+ # Try to get from Streamlit secrets first
23
+ try:
24
+ return st.secrets["GROQ_API_KEY"]
25
+ except:
26
+ # Fallback to environment variable
27
+ api_key = os.getenv("GROQ_API_KEY")
28
+ if not api_key:
29
+ st.error("πŸ” GROQ API key not found. Please configure it in Streamlit secrets or environment variables.")
30
+ st.info("""
31
+ **To configure the API key:**
32
+ 1. **For Hugging Face Spaces**: Add `GROQ_API_KEY` in your Space settings under 'Repository secrets'
33
+ 2. **For local development**: Set environment variable `GROQ_API_KEY=your_key_here`
34
+ 3. **For Streamlit Cloud**: Add to secrets.toml file
35
+ """)
36
+ return None
37
+ return api_key
38
+
39
+ # Color schemes for different magnitude levels
40
+ MAGNITUDE_COLORS = {
41
+ 'Low': '#00ff00', # Green
42
+ 'Moderate': '#ffff00', # Yellow
43
+ 'High': '#ff8000', # Orange
44
+ 'Severe': '#ff0000', # Red
45
+ 'Extreme': '#800000' # Dark Red
46
+ }
47
+
48
+ # Risk assessment thresholds
49
+ RISK_THRESHOLDS = {
50
+ 'low': {'count': 5, 'max_magnitude': 3.0},
51
+ 'moderate': {'count': 10, 'max_magnitude': 4.5},
52
+ 'high': {'count': 20, 'max_magnitude': 5.5},
53
+ 'severe': {'count': 30, 'max_magnitude': 6.5},
54
+ 'extreme': {'count': 50, 'max_magnitude': 7.0}
55
+ }
56
+
57
+ # Emergency protocols
58
+ EMERGENCY_PROTOCOLS = {
59
+ 'low': "Monitor situation. No immediate action required.",
60
+ 'moderate': "Stay alert. Review emergency plans.",
61
+ 'high': "Prepare emergency kit. Stay informed.",
62
+ 'severe': "Follow evacuation orders if issued. Seek shelter.",
63
+ 'extreme': "IMMEDIATE EVACUATION. Follow emergency services."
64
+ }
65
+
66
+ def get_groq_summary(prompt, context=""):
67
+ """Enhanced Groq LLM function with secure API key handling"""
68
+ api_key = get_groq_api_key()
69
+ if not api_key:
70
+ return "AI Analysis unavailable - API key not configured"
71
+
72
+ try:
73
+ # Import Groq only when needed to avoid errors if not installed
74
+ from groq import Groq
75
+
76
+ client = Groq(api_key=api_key)
77
+ full_prompt = f"{context}\n\n{prompt}" if context else prompt
78
+ response = client.chat.completions.create(
79
+ model="llama-3.3-70b-versatile",
80
+ messages=[
81
+ {"role": "system", "content": "You are an expert seismologist, emergency response specialist, and public safety advisor. Provide detailed, accurate, and actionable information."},
82
+ {"role": "user", "content": full_prompt}
83
+ ],
84
+ max_tokens=2048,
85
+ temperature=0.7,
86
+ top_p=0.9,
87
+ presence_penalty=0.1,
88
+ frequency_penalty=0.1
89
+ )
90
+ return response.choices[0].message.content
91
+ except ImportError:
92
+ return "AI Analysis unavailable - Groq library not installed"
93
+ except Exception as e:
94
+ return f"AI Analysis Error: {str(e)}"
95
+
96
+ def fetch_earthquakes(min_magnitude=2.5, hours=24, region_bbox=None, detailed=True):
97
+ """Fetch earthquake data with enhanced error handling and data processing"""
98
+ try:
99
+ endtime = datetime.utcnow()
100
+ starttime = endtime - timedelta(hours=hours)
101
+
102
+ url = "https://earthquake.usgs.gov/fdsnws/event/1/query"
103
+ params = {
104
+ "format": "geojson",
105
+ "starttime": starttime.strftime('%Y-%m-%dT%H:%M:%S'),
106
+ "endtime": endtime.strftime('%Y-%m-%dT%H:%M:%S'),
107
+ "minmagnitude": min_magnitude,
108
+ "orderby": "time",
109
+ "limit": 500 if detailed else 200
110
+ }
111
+
112
+ if region_bbox:
113
+ params.update({
114
+ "minlatitude": region_bbox[1],
115
+ "maxlatitude": region_bbox[3],
116
+ "minlongitude": region_bbox[0],
117
+ "maxlongitude": region_bbox[2],
118
+ })
119
+
120
+ response = requests.get(url, params=params, timeout=30)
121
+ response.raise_for_status()
122
+ data = response.json()
123
+
124
+ features = data.get('features', [])
125
+ earthquakes = []
126
+
127
+ for f in features:
128
+ prop = f['properties']
129
+ geom = f['geometry']
130
+
131
+ earthquake = {
132
+ 'time': datetime.utcfromtimestamp(prop['time']/1000),
133
+ 'place': prop['place'],
134
+ 'magnitude': prop['mag'],
135
+ 'longitude': geom['coordinates'][0],
136
+ 'latitude': geom['coordinates'][1],
137
+ 'depth': geom['coordinates'][2],
138
+ 'url': prop['url'],
139
+ 'type': prop.get('type', 'earthquake'),
140
+ 'status': prop.get('status', 'automatic'),
141
+ 'tsunami': prop.get('tsunami', 0),
142
+ 'felt': prop.get('felt', 0),
143
+ 'cdi': prop.get('cdi', 0),
144
+ 'mmi': prop.get('mmi', 0),
145
+ 'alert': prop.get('alert', ''),
146
+ 'sig': prop.get('sig', 0)
147
+ }
148
+
149
+ earthquake['risk_level'] = calculate_risk_level(earthquake['magnitude'])
150
+ earthquake['time_ago'] = calculate_time_ago(earthquake['time'])
151
+
152
+ earthquakes.append(earthquake)
153
+
154
+ df = pd.DataFrame(earthquakes)
155
+
156
+ if not df.empty:
157
+ df['magnitude_category'] = df['magnitude'].apply(categorize_magnitude)
158
+ df['depth_category'] = df['depth'].apply(categorize_depth)
159
+ df['hour_of_day'] = df['time'].dt.hour
160
+ df['day_of_week'] = df['time'].dt.day_name()
161
+
162
+ return df
163
+
164
+ except requests.exceptions.RequestException as e:
165
+ st.error(f"Network error: {e}")
166
+ return pd.DataFrame()
167
+ except Exception as e:
168
+ st.error(f"Data processing error: {e}")
169
+ return pd.DataFrame()
170
+
171
+ def calculate_risk_level(magnitude):
172
+ """Calculate risk level based on magnitude"""
173
+ if magnitude >= 7.0:
174
+ return 'Extreme'
175
+ elif magnitude >= 6.0:
176
+ return 'Severe'
177
+ elif magnitude >= 5.0:
178
+ return 'High'
179
+ elif magnitude >= 4.0:
180
+ return 'Moderate'
181
+ else:
182
+ return 'Low'
183
+
184
+ def categorize_magnitude(magnitude):
185
+ """Categorize magnitude for analysis"""
186
+ if magnitude >= 7.0:
187
+ return 'Major (β‰₯7.0)'
188
+ elif magnitude >= 6.0:
189
+ return 'Strong (6.0-6.9)'
190
+ elif magnitude >= 5.0:
191
+ return 'Moderate (5.0-5.9)'
192
+ elif magnitude >= 4.0:
193
+ return 'Light (4.0-4.9)'
194
+ else:
195
+ return 'Minor (<4.0)'
196
+
197
+ def categorize_depth(depth):
198
+ """Categorize depth for analysis"""
199
+ if depth < 70:
200
+ return 'Shallow (<70km)'
201
+ elif depth < 300:
202
+ return 'Intermediate (70-300km)'
203
+ else:
204
+ return 'Deep (>300km)'
205
+
206
+ def calculate_time_ago(time):
207
+ """Calculate time ago in human readable format"""
208
+ now = datetime.utcnow()
209
+ diff = now - time
210
+
211
+ if diff.days > 0:
212
+ return f"{diff.days} day(s) ago"
213
+ elif diff.seconds >= 3600:
214
+ hours = diff.seconds // 3600
215
+ return f"{hours} hour(s) ago"
216
+ elif diff.seconds >= 60:
217
+ minutes = diff.seconds // 60
218
+ return f"{minutes} minute(s) ago"
219
+ else:
220
+ return "Just now"
221
+
222
+ def analyze_seismic_patterns(df):
223
+ """Analyze seismic patterns and trends"""
224
+ if df.empty:
225
+ return {}
226
+
227
+ analysis = {}
228
+
229
+ try:
230
+ # Only calculate distributions if we have data
231
+ if len(df) > 0:
232
+ analysis['hourly_distribution'] = df['hour_of_day'].value_counts().sort_index()
233
+ analysis['daily_distribution'] = df['day_of_week'].value_counts()
234
+
235
+ # Magnitude statistics - only if we have magnitude data
236
+ if 'magnitude' in df.columns and len(df) > 0:
237
+ analysis['magnitude_stats'] = {
238
+ 'mean': df['magnitude'].mean(),
239
+ 'median': df['magnitude'].median(),
240
+ 'std': df['magnitude'].std(),
241
+ 'max': df['magnitude'].max(),
242
+ 'min': df['magnitude'].min()
243
+ }
244
+
245
+ # Depth statistics - only if we have depth data
246
+ if 'depth' in df.columns and len(df) > 0:
247
+ analysis['depth_stats'] = {
248
+ 'mean': df['depth'].mean(),
249
+ 'median': df['depth'].median(),
250
+ 'std': df['depth'].std()
251
+ }
252
+
253
+ # Risk distribution - only if we have risk level data
254
+ if 'risk_level' in df.columns and len(df) > 0:
255
+ analysis['risk_distribution'] = df['risk_level'].value_counts()
256
+
257
+ # Geographic center - only if we have multiple data points
258
+ if len(df) > 1 and 'latitude' in df.columns and 'longitude' in df.columns:
259
+ analysis['geographic_center'] = {
260
+ 'lat': df['latitude'].mean(),
261
+ 'lon': df['longitude'].mean()
262
+ }
263
+
264
+ except Exception as e:
265
+ st.warning(f"Error in pattern analysis: {str(e)}")
266
+ return {}
267
+
268
+ return analysis
269
+
270
+ def calculate_overall_risk(df):
271
+ """Calculate overall risk assessment"""
272
+ if df.empty:
273
+ return 'low', "No recent seismic activity"
274
+
275
+ count = len(df)
276
+ max_magnitude = df['magnitude'].max()
277
+
278
+ risk_score = 0
279
+
280
+ if count >= RISK_THRESHOLDS['extreme']['count']:
281
+ risk_score += 40
282
+ elif count >= RISK_THRESHOLDS['severe']['count']:
283
+ risk_score += 30
284
+ elif count >= RISK_THRESHOLDS['high']['count']:
285
+ risk_score += 20
286
+ elif count >= RISK_THRESHOLDS['moderate']['count']:
287
+ risk_score += 10
288
+
289
+ if max_magnitude >= RISK_THRESHOLDS['extreme']['max_magnitude']:
290
+ risk_score += 40
291
+ elif max_magnitude >= RISK_THRESHOLDS['severe']['max_magnitude']:
292
+ risk_score += 30
293
+ elif max_magnitude >= RISK_THRESHOLDS['high']['max_magnitude']:
294
+ risk_score += 20
295
+ elif max_magnitude >= RISK_THRESHOLDS['moderate']['max_magnitude']:
296
+ risk_score += 10
297
+
298
+ if risk_score >= 60:
299
+ risk_level = 'extreme'
300
+ elif risk_score >= 40:
301
+ risk_level = 'severe'
302
+ elif risk_score >= 25:
303
+ risk_level = 'high'
304
+ elif risk_score >= 10:
305
+ risk_level = 'moderate'
306
+ else:
307
+ risk_level = 'low'
308
+
309
+ return risk_level, f"Risk Score: {risk_score}/80"
310
+
311
+ def create_advanced_map(df, region_bbox=None):
312
+ """Create an advanced interactive map"""
313
+ if df.empty:
314
+ return None
315
+
316
+ center_lat = df['latitude'].mean()
317
+ center_lon = df['longitude'].mean()
318
+
319
+ m = folium.Map(
320
+ location=[center_lat, center_lon],
321
+ zoom_start=6,
322
+ tiles='OpenStreetMap'
323
+ )
324
+
325
+ for idx, row in df.iterrows():
326
+ if row['magnitude'] >= 6.0:
327
+ color = 'red'
328
+ radius = 15
329
+ elif row['magnitude'] >= 5.0:
330
+ color = 'orange'
331
+ radius = 12
332
+ elif row['magnitude'] >= 4.0:
333
+ color = 'yellow'
334
+ radius = 10
335
+ else:
336
+ color = 'green'
337
+ radius = 8
338
+
339
+ popup_content = f"""
340
+ <b>Magnitude {row['magnitude']}</b><br>
341
+ Location: {row['place']}<br>
342
+ Time: {row['time'].strftime('%Y-%m-%d %H:%M:%S')}<br>
343
+ Depth: {row['depth']:.1f} km<br>
344
+ <a href="{row['url']}" target="_blank">USGS Details</a>
345
+ """
346
+
347
+ folium.CircleMarker(
348
+ location=[row['latitude'], row['longitude']],
349
+ radius=radius,
350
+ popup=popup_content,
351
+ color=color,
352
+ fill=True,
353
+ fillOpacity=0.7
354
+ ).add_to(m)
355
+
356
+ if region_bbox:
357
+ folium.Rectangle(
358
+ bounds=[[region_bbox[1], region_bbox[0]], [region_bbox[3], region_bbox[2]]],
359
+ color='blue',
360
+ weight=2,
361
+ fillOpacity=0.1
362
+ ).add_to(m)
363
+
364
+ return m
365
+
366
+ def create_comprehensive_charts(df, analysis):
367
+ """Create comprehensive visualization charts"""
368
+ if df.empty:
369
+ return []
370
+
371
+ charts = []
372
+
373
+ # Magnitude over time with trend - with error handling
374
+ fig1 = go.Figure()
375
+ fig1.add_trace(go.Scatter(
376
+ x=df['time'], y=df['magnitude'],
377
+ mode='markers',
378
+ marker=dict(
379
+ size=df['magnitude'] * 2,
380
+ color=df['magnitude'],
381
+ colorscale='Reds',
382
+ showscale=True
383
+ ),
384
+ name='Earthquakes'
385
+ ))
386
+
387
+ # Only add trend line if we have enough data points (at least 2)
388
+ if len(df) >= 2:
389
+ try:
390
+ z = np.polyfit(range(len(df)), df['magnitude'], 1)
391
+ p = np.poly1d(z)
392
+ fig1.add_trace(go.Scatter(
393
+ x=df['time'], y=p(range(len(df))),
394
+ mode='lines',
395
+ name='Trend',
396
+ line=dict(color='blue', dash='dash')
397
+ ))
398
+ except (np.linalg.LinAlgError, ValueError) as e:
399
+ # If polynomial fitting fails, just show the scatter plot without trend
400
+ st.warning(f"Trend analysis unavailable: {str(e)}")
401
+
402
+ fig1.update_layout(
403
+ title='Earthquake Magnitude Over Time with Trend',
404
+ xaxis_title='Time',
405
+ yaxis_title='Magnitude',
406
+ height=400
407
+ )
408
+ charts.append(fig1)
409
+
410
+ # Magnitude distribution histogram - only if we have data
411
+ if len(df) > 0:
412
+ fig2 = px.histogram(
413
+ df, x='magnitude', nbins=min(20, len(df)), # Limit bins to data size
414
+ title='Magnitude Distribution',
415
+ labels={'magnitude': 'Magnitude', 'count': 'Frequency'}
416
+ )
417
+ fig2.update_layout(height=400)
418
+ charts.append(fig2)
419
+
420
+ # Depth vs Magnitude scatter - only if we have data
421
+ if len(df) > 0:
422
+ fig3 = px.scatter(
423
+ df, x='depth', y='magnitude', color='magnitude',
424
+ title='Depth vs Magnitude Relationship',
425
+ labels={'depth': 'Depth (km)', 'magnitude': 'Magnitude'}
426
+ )
427
+ fig3.update_layout(height=400)
428
+ charts.append(fig3)
429
+
430
+ # Hourly distribution - only if we have the data
431
+ if 'hourly_distribution' in analysis and len(analysis['hourly_distribution']) > 0:
432
+ fig4 = px.bar(
433
+ x=analysis['hourly_distribution'].index,
434
+ y=analysis['hourly_distribution'].values,
435
+ title='Earthquake Activity by Hour of Day',
436
+ labels={'x': 'Hour', 'y': 'Count'}
437
+ )
438
+ fig4.update_layout(height=400)
439
+ charts.append(fig4)
440
+
441
+ # Risk level distribution - only if we have the data
442
+ if 'risk_distribution' in analysis and len(analysis['risk_distribution']) > 0:
443
+ fig5 = px.pie(
444
+ values=analysis['risk_distribution'].values,
445
+ names=analysis['risk_distribution'].index,
446
+ title='Risk Level Distribution'
447
+ )
448
+ fig5.update_layout(height=400)
449
+ charts.append(fig5)
450
+
451
+ return charts
452
+
453
+ def main():
454
+ st.set_page_config(
455
+ page_title="🌍 QuakeGuard AI",
456
+ page_icon="🌍",
457
+ layout="wide",
458
+ initial_sidebar_state="expanded"
459
+ )
460
+
461
+ st.markdown("""
462
+ <style>
463
+ .main-header {
464
+ font-size: 3rem;
465
+ font-weight: bold;
466
+ text-align: center;
467
+ color: #1f77b4;
468
+ margin-bottom: 2rem;
469
+ }
470
+ .risk-high { color: #ff4444; font-weight: bold; }
471
+ .risk-moderate { color: #ffaa00; font-weight: bold; }
472
+ .risk-low { color: #44aa44; font-weight: bold; }
473
+ .metric-card {
474
+ background-color: #f0f2f6;
475
+ padding: 1rem;
476
+ border-radius: 0.5rem;
477
+ border-left: 4px solid #1f77b4;
478
+ color: #222 !important;
479
+ }
480
+ </style>
481
+ """, unsafe_allow_html=True)
482
+
483
+ st.markdown('<h1 class="main-header">🌍 Advanced Earthquake Warning System</h1>', unsafe_allow_html=True)
484
+ st.markdown("### Real-time seismic monitoring with AI-powered risk assessment and emergency protocols")
485
+
486
+ # Check if API key is configured
487
+ if not get_groq_api_key():
488
+ st.stop()
489
+
490
+ st.sidebar.header("βš™οΈ Configuration")
491
+
492
+ region = st.sidebar.text_input(
493
+ "🌍 Region (optional)",
494
+ placeholder="e.g., California, Pakistan, Japan"
495
+ )
496
+
497
+ col1, col2 = st.sidebar.columns(2)
498
+ with col1:
499
+ min_magnitude = st.slider("πŸ“ Min Magnitude", 1.0, 7.0, 2.5, 0.1)
500
+ with col2:
501
+ hours = st.slider("⏰ Hours", 1, 168, 24)
502
+
503
+ with st.sidebar.expander("πŸ”§ Advanced Options"):
504
+ show_detailed_analysis = st.checkbox("Detailed Analysis", value=True)
505
+ show_ai_summary = st.checkbox("AI Summary", value=True)
506
+ show_emergency_protocols = st.checkbox("Emergency Protocols", value=True)
507
+
508
+ region_bboxes = {
509
+ "California": [-125, 32, -114, 42],
510
+ "Pakistan": [60, 23, 77, 37],
511
+ "Japan": [129, 31, 146, 45],
512
+ "Chile": [-75, -56, -66, -17],
513
+ "Turkey": [25, 36, 45, 43],
514
+ "Indonesia": [95, -11, 141, 6],
515
+ "India": [68, 6, 97, 37],
516
+ "Mexico": [-118, 14, -86, 33],
517
+ "USA": [-125, 24, -66, 49],
518
+ "World": [-180, -90, 180, 90]
519
+ }
520
+
521
+ region_bbox = region_bboxes.get(region.strip().title()) if region else None
522
+
523
+ if st.button("πŸ”„ Refresh Data", type="primary"):
524
+ st.rerun()
525
+
526
+ with st.spinner("🌐 Fetching earthquake data..."):
527
+ df = fetch_earthquakes(min_magnitude, hours, region_bbox, show_detailed_analysis)
528
+
529
+ if df.empty:
530
+ st.warning("⚠️ No recent earthquakes found matching your criteria.")
531
+ st.info("πŸ’‘ Try reducing the minimum magnitude or increasing the time range.")
532
+
533
+ # Show a simple message when no data is available
534
+ st.markdown("""
535
+ <div class="metric-card">
536
+ <h3>🚨 Current Risk Level: <span class="risk-low">LOW</span></h3>
537
+ <p><strong>Risk Score:</strong> 0/80</p>
538
+ <p><strong>Emergency Protocol:</strong> Monitor situation. No immediate action required.</p>
539
+ </div>
540
+ """, unsafe_allow_html=True)
541
+
542
+ # Show tabs with appropriate messages
543
+ tab1, tab2, tab3, tab4, tab5 = st.tabs(["πŸ—ΊοΈ Map", "πŸ“Š Analytics", "πŸ“‹ Data", "πŸ€– AI Analysis", "🚨 Emergency"])
544
+
545
+ with tab1:
546
+ st.subheader("🌍 Interactive Earthquake Map")
547
+ st.info("No earthquake data available for map visualization")
548
+
549
+ with tab2:
550
+ st.subheader("πŸ“Š Advanced Analytics")
551
+ st.info("No earthquake data available for analysis")
552
+
553
+ with tab3:
554
+ st.subheader("πŸ“‹ Earthquake Data")
555
+ st.info("No earthquake data available")
556
+
557
+ with tab4:
558
+ st.subheader("πŸ€– AI-Powered Analysis")
559
+ if show_ai_summary:
560
+ st.info("No earthquake data available for AI analysis")
561
+ else:
562
+ st.info("Enable AI Summary in Advanced Options to see AI analysis.")
563
+
564
+ with tab5:
565
+ st.subheader("🚨 Emergency Information")
566
+ if show_emergency_protocols:
567
+ st.markdown("""
568
+ ### 🚨 Emergency Response Protocols
569
+
570
+ **Immediate Actions During Earthquake:**
571
+ - Drop, Cover, and Hold On
572
+ - Stay indoors if you're inside
573
+ - Move to open area if you're outside
574
+ - Stay away from windows, mirrors, and heavy objects
575
+
576
+ **After Earthquake:**
577
+ - Check for injuries and provide first aid
578
+ - Check for gas leaks and electrical damage
579
+ - Listen to emergency broadcasts
580
+ - Be prepared for aftershocks
581
+
582
+ **Emergency Contacts:**
583
+ - Emergency Services: 911 (US) / 112 (EU) / 999 (UK)
584
+ - USGS Earthquake Information: https://earthquake.usgs.gov
585
+ - Local Emergency Management: Check your local government website
586
+ """)
587
+
588
+ st.markdown("""
589
+ ### πŸ“Š Current Emergency Status
590
+ - **Risk Level**: LOW
591
+ - **Recommended Action**: Monitor situation. No immediate action required.
592
+ - **Monitoring Required**: No
593
+ """)
594
+ else:
595
+ st.info("Enable Emergency Protocols in Advanced Options to see emergency information.")
596
+ else:
597
+ st.success(f"βœ… Found {len(df)} earthquakes in the last {hours} hours")
598
+ st.write(f"πŸ• Last updated: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC")
599
+
600
+ risk_level, risk_score = calculate_overall_risk(df)
601
+
602
+ st.markdown(f"""
603
+ <div class="metric-card">
604
+ <h3>🚨 Current Risk Level: <span class="risk-{risk_level}">{risk_level.upper()}</span></h3>
605
+ <p><strong>Risk Score:</strong> {risk_score}</p>
606
+ <p><strong>Emergency Protocol:</strong> {EMERGENCY_PROTOCOLS[risk_level]}</p>
607
+ </div>
608
+ """, unsafe_allow_html=True)
609
+
610
+ col1, col2, col3, col4 = st.columns(4)
611
+ with col1:
612
+ st.metric("Total Earthquakes", len(df))
613
+ with col2:
614
+ st.metric("Max Magnitude", f"{df['magnitude'].max():.1f}")
615
+ with col3:
616
+ st.metric("Avg Magnitude", f"{df['magnitude'].mean():.2f}")
617
+ with col4:
618
+ st.metric("Avg Depth", f"{df['depth'].mean():.1f} km")
619
+
620
+ tab1, tab2, tab3, tab4, tab5 = st.tabs(["πŸ—ΊοΈ Map", "πŸ“Š Analytics", "πŸ“‹ Data", "πŸ€– AI Analysis", "🚨 Emergency"])
621
+
622
+ with tab1:
623
+ st.subheader("🌍 Interactive Earthquake Map")
624
+ if not df.empty:
625
+ try:
626
+ map_obj = create_advanced_map(df, region_bbox)
627
+ if map_obj:
628
+ st_folium(map_obj, width=800, height=500)
629
+ else:
630
+ st.info("Unable to create map visualization")
631
+ except Exception as e:
632
+ st.error(f"Error creating map: {str(e)}")
633
+ st.info("Try adjusting your search criteria")
634
+ else:
635
+ st.info("No earthquake data available for map visualization")
636
+
637
+ with tab2:
638
+ st.subheader("πŸ“Š Advanced Analytics")
639
+ if not df.empty:
640
+ try:
641
+ analysis = analyze_seismic_patterns(df)
642
+
643
+ charts = create_comprehensive_charts(df, analysis)
644
+ for i, chart in enumerate(charts):
645
+ st.plotly_chart(chart, use_container_width=True)
646
+
647
+ if analysis:
648
+ col1, col2 = st.columns(2)
649
+ with col1:
650
+ st.subheader("πŸ“ˆ Magnitude Statistics")
651
+ if 'magnitude_stats' in analysis:
652
+ stats_df = pd.DataFrame([analysis['magnitude_stats']]).T
653
+ stats_df.columns = ['Value']
654
+ st.dataframe(stats_df)
655
+ else:
656
+ st.info("Insufficient data for magnitude statistics")
657
+
658
+ with col2:
659
+ st.subheader("πŸ“Š Risk Distribution")
660
+ if 'risk_distribution' in analysis and len(analysis['risk_distribution']) > 0:
661
+ risk_df = pd.DataFrame(analysis['risk_distribution'])
662
+ risk_df.columns = ['Count']
663
+ st.dataframe(risk_df)
664
+ else:
665
+ st.info("No risk distribution data available")
666
+ except Exception as e:
667
+ st.error(f"Error in analytics: {str(e)}")
668
+ st.info("Try adjusting your search criteria or check your internet connection")
669
+ else:
670
+ st.info("No earthquake data available for analysis")
671
+
672
+ with tab3:
673
+ st.subheader("πŸ“‹ Earthquake Data")
674
+ if not df.empty:
675
+ col1, col2 = st.columns(2)
676
+ with col1:
677
+ magnitude_filter = st.multiselect(
678
+ "Filter by Magnitude Category",
679
+ options=df['magnitude_category'].unique(),
680
+ default=df['magnitude_category'].unique()
681
+ )
682
+ with col2:
683
+ risk_filter = st.multiselect(
684
+ "Filter by Risk Level",
685
+ options=df['risk_level'].unique(),
686
+ default=df['risk_level'].unique()
687
+ )
688
+
689
+ filtered_df = df[
690
+ (df['magnitude_category'].isin(magnitude_filter)) &
691
+ (df['risk_level'].isin(risk_filter))
692
+ ]
693
+
694
+ st.dataframe(
695
+ filtered_df[['time', 'place', 'magnitude', 'depth', 'risk_level', 'time_ago', 'url']],
696
+ use_container_width=True
697
+ )
698
+
699
+ csv = filtered_df.to_csv(index=False)
700
+ st.download_button(
701
+ label="πŸ“₯ Download CSV",
702
+ data=csv,
703
+ file_name=f"earthquakes_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
704
+ mime="text/csv"
705
+ )
706
+
707
+ with tab4:
708
+ st.subheader("πŸ€– AI-Powered Analysis")
709
+ if show_ai_summary and not df.empty:
710
+ with st.spinner("πŸ€– Generating AI analysis..."):
711
+ analysis = analyze_seismic_patterns(df)
712
+ risk_level, risk_score = calculate_overall_risk(df)
713
+
714
+ prompt = f"""
715
+ As an expert seismologist and emergency response specialist, provide a comprehensive analysis of the following earthquake data:
716
+
717
+ SUMMARY STATISTICS:
718
+ - Total earthquakes: {len(df)}
719
+ - Time period: {hours} hours
720
+ - Magnitude range: {df['magnitude'].min():.1f} - {df['magnitude'].max():.1f}
721
+ - Average magnitude: {df['magnitude'].mean():.2f}
722
+ - Risk level: {risk_level.upper()}
723
+ - Risk score: {risk_score}
724
+
725
+ EARTHQUAKE DATA:
726
+ {df[['time', 'place', 'magnitude', 'depth']].head(20).to_string(index=False)}
727
+
728
+ Please provide:
729
+ 1. **Risk Assessment**: Detailed evaluation of current seismic risk
730
+ 2. **Pattern Analysis**: Identification of any concerning patterns or trends
731
+ 3. **Regional Impact**: Specific implications for affected areas
732
+ 4. **Safety Recommendations**: Detailed safety advice for the public
733
+ 5. **Emergency Preparedness**: Specific actions people should take
734
+ 6. **Monitoring Recommendations**: What to watch for in coming hours/days
735
+
736
+ Be thorough, specific, and actionable in your response.
737
+ """
738
+
739
+ summary = get_groq_summary(prompt)
740
+ st.markdown(summary)
741
+ else:
742
+ st.info("Enable AI Summary in Advanced Options to see AI analysis.")
743
+
744
+ with tab5:
745
+ st.subheader("🚨 Emergency Information")
746
+ if show_emergency_protocols:
747
+ st.markdown("""
748
+ ### 🚨 Emergency Response Protocols
749
+
750
+ **Immediate Actions During Earthquake:**
751
+ - Drop, Cover, and Hold On
752
+ - Stay indoors if you're inside
753
+ - Move to open area if you're outside
754
+ - Stay away from windows, mirrors, and heavy objects
755
+
756
+ **After Earthquake:**
757
+ - Check for injuries and provide first aid
758
+ - Check for gas leaks and electrical damage
759
+ - Listen to emergency broadcasts
760
+ - Be prepared for aftershocks
761
+
762
+ **Emergency Contacts:**
763
+ - Emergency Services: 911 (US) / 112 (EU) / 999 (UK)
764
+ - USGS Earthquake Information: https://earthquake.usgs.gov
765
+ - Local Emergency Management: Check your local government website
766
+ """)
767
+
768
+ st.markdown(f"""
769
+ ### πŸ“Š Current Emergency Status
770
+ - **Risk Level**: {risk_level.upper()}
771
+ - **Recommended Action**: {EMERGENCY_PROTOCOLS[risk_level]}
772
+ - **Monitoring Required**: {'Yes' if risk_level in ['high', 'severe', 'extreme'] else 'No'}
773
+ """)
774
+ else:
775
+ st.info("Enable Emergency Protocols in Advanced Options to see emergency information.")
776
+
777
+ if __name__ == "__main__":
778
+ main()