CCockrum commited on
Commit
b141a96
Β·
verified Β·
1 Parent(s): 4143b21

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +223 -166
app.py CHANGED
@@ -17,8 +17,8 @@ class EnhancedOceanClimateAgent:
17
  self.anomaly_threshold = 2.0
18
  self.critical_temp_change = 1.5
19
 
20
- #https://api.tidesandcurrents.noaa.gov/api/prod/datagetter
21
- self.noaa_base_url = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter?date=latest&product=water_level&time_zone=gmt&units=english&format=xml"
22
  self.noaa_stations_url = "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations.json"
23
 
24
  # Popular NOAA stations for different regions
@@ -33,47 +33,58 @@ class EnhancedOceanClimateAgent:
33
  "Charleston, SC": "8665530"
34
  }
35
 
36
- def get_noaa_data(self, station_id, product, start_date, end_date, units="metric"):
37
- """Fetch data from NOAA API"""
 
 
 
 
38
  params = {
 
39
  'product': product,
 
 
 
40
  'application': 'OceanClimateAgent',
41
- 'begin_date': start_date.strftime('%Y%m%d'),
42
- 'end_date': end_date.strftime('%Y%m%d'),
43
- 'station': station_id,
44
  'time_zone': 'gmt',
45
  'units': units,
46
  'format': 'json'
47
  }
48
-
49
  try:
50
- print(f"πŸ“‘ Requesting {product} data for station {station_id}")
51
- print("πŸ• Date range:", start_date.strftime('%Y-%m-%d'), "to", end_date.strftime('%Y-%m-%d'))
52
-
53
- # Print full API URL for testing
54
- full_url = f"{self.noaa_base_url}?{urlencode(params)}"
55
- print(f"🌐 NOAA API URL: {full_url}")
56
-
57
  response = requests.get(self.noaa_base_url, params=params, timeout=30)
58
- print(f"πŸ” Status code: {response.status_code}")
59
-
60
- if response.status_code == 200:
61
- data = response.json()
62
- if 'data' in data:
63
- print(f"βœ… Data received: {len(data['data'])} records for {product}")
64
- return pd.DataFrame(data['data'])
65
- elif 'error' in data:
66
- print(f"❌ NOAA error: {data['error'].get('message')}")
67
- else:
68
- print(f"❌ Unknown response structure: {data}")
 
 
69
  else:
70
- print(f"❌ API HTTP error {response.status_code}: {response.text}")
71
-
72
- except Exception as e:
 
 
 
 
73
  print(f"❌ Request failed for {product}: {str(e)}")
74
-
75
- return None
76
-
 
 
 
 
77
 
78
  def get_comprehensive_station_data(self, station_name, days_back=30):
79
  """Get comprehensive data from a NOAA station"""
@@ -81,35 +92,40 @@ class EnhancedOceanClimateAgent:
81
  if not station_id:
82
  return None, "Station not found"
83
 
84
- # Ensure end_date is not in the future (NOAA does not support future data)
85
- today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
86
- end_date = min(datetime.utcnow(), today)
87
  start_date = end_date - timedelta(days=days_back)
88
-
89
 
90
- # Available NOAA products
91
- products_to_fetch = {
92
- 'water_level': 'water_level',
93
- 'water_temperature': 'water_temperature',
94
- 'air_temperature': 'air_temperature',
95
- 'wind': 'wind',
96
- 'air_pressure': 'air_pressure',
97
- 'salinity': 'salinity',
98
- 'currents': 'currents'
99
- }
 
100
 
101
  all_data = {}
102
  success_count = 0
103
 
104
- for product_name, product_code in products_to_fetch.items():
 
105
  data = self.get_noaa_data(station_id, product_code, start_date, end_date)
 
106
  if data is not None and not data.empty:
107
  all_data[product_name] = data
108
  success_count += 1
 
 
 
109
 
110
  if success_count == 0:
111
- return None, "No data available for this station and time period"
112
 
 
113
  return all_data, f"Successfully retrieved {success_count}/{len(products_to_fetch)} data types"
114
 
115
  def process_noaa_data(self, raw_data):
@@ -117,47 +133,66 @@ class EnhancedOceanClimateAgent:
117
  if not raw_data:
118
  return None
119
 
120
- # Process water level data (primary dataset)
 
 
121
  if 'water_level' in raw_data:
122
  df = raw_data['water_level'].copy()
123
  df['datetime'] = pd.to_datetime(df['t'])
124
  df['water_level'] = pd.to_numeric(df['v'], errors='coerce')
125
-
126
- # Add other parameters when available
127
- if 'water_temperature' in raw_data:
128
- temp_df = raw_data['water_temperature'].copy()
129
- temp_df['datetime'] = pd.to_datetime(temp_df['t'])
130
- temp_df['water_temp'] = pd.to_numeric(temp_df['v'], errors='coerce')
131
- df = df.merge(temp_df[['datetime', 'water_temp']], on='datetime', how='left')
132
-
133
- if 'air_temperature' in raw_data:
134
- air_temp_df = raw_data['air_temperature'].copy()
135
- air_temp_df['datetime'] = pd.to_datetime(air_temp_df['t'])
136
- air_temp_df['air_temp'] = pd.to_numeric(air_temp_df['v'], errors='coerce')
137
- df = df.merge(air_temp_df[['datetime', 'air_temp']], on='datetime', how='left')
138
-
139
- if 'wind' in raw_data:
140
- wind_df = raw_data['wind'].copy()
141
- wind_df['datetime'] = pd.to_datetime(wind_df['t'])
142
- wind_df['wind_speed'] = pd.to_numeric(wind_df['s'], errors='coerce')
143
- wind_df['wind_direction'] = pd.to_numeric(wind_df['d'], errors='coerce')
144
- df = df.merge(wind_df[['datetime', 'wind_speed', 'wind_direction']], on='datetime', how='left')
145
-
146
- if 'air_pressure' in raw_data:
147
- pressure_df = raw_data['air_pressure'].copy()
148
- pressure_df['datetime'] = pd.to_datetime(pressure_df['t'])
149
- pressure_df['air_pressure'] = pd.to_numeric(pressure_df['v'], errors='coerce')
150
- df = df.merge(pressure_df[['datetime', 'air_pressure']], on='datetime', how='left')
151
-
152
- if 'salinity' in raw_data:
153
- salinity_df = raw_data['salinity'].copy()
154
- salinity_df['datetime'] = pd.to_datetime(salinity_df['t'])
155
- salinity_df['salinity'] = pd.to_numeric(salinity_df['v'], errors='coerce')
156
- df = df.merge(salinity_df[['datetime', 'salinity']], on='datetime', how='left')
157
-
158
- return df
159
 
160
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  def detect_anomalies(self, data, column, window=24): # 24 hours for hourly data
163
  """Detect anomalies using rolling statistics"""
@@ -212,15 +247,16 @@ class EnhancedOceanClimateAgent:
212
  alerts.append(f"Significant water level change: {wl_trend*24:.1f}cm/day at {station_name}")
213
 
214
  # Temperature analysis
215
- if 'water_temp' in data.columns:
216
- temp_trend = self.calculate_trends(data, 'water_temp')
217
- analysis['water_temp_trend'] = temp_trend * 24 # per day
218
-
219
- if temp_trend * 24 > 0.5: # >0.5Β°C per day
220
- alerts.append(f"Rapid water temperature rise: {temp_trend*24:.2f}Β°C/day at {station_name}")
 
221
 
222
  # Anomaly detection
223
- for col in ['water_level', 'water_temp', 'wind_speed']:
224
  if col in data.columns:
225
  anomalies, z_scores = self.detect_anomalies(data, col)
226
  anomaly_pct = (anomalies.sum() / len(data)) * 100
@@ -243,24 +279,31 @@ def analyze_real_ocean_data(station_name, days_back, anomaly_sensitivity, use_re
243
  agent.anomaly_threshold = anomaly_sensitivity
244
 
245
  if use_real_data:
 
246
  # Fetch real NOAA data
247
  raw_data, status_msg = agent.get_comprehensive_station_data(station_name, days_back)
248
 
249
  if raw_data is None:
250
- return None, None, None, f"Error: {status_msg}", "No alerts - data unavailable", None
 
 
251
 
252
  # Process the data
253
  data = agent.process_noaa_data(raw_data)
254
 
255
  if data is None or data.empty:
256
- return None, None, None, "No processable data available", "No alerts - data unavailable", None
 
 
257
 
258
- data_source = f"Real NOAA data from {station_name} ({status_msg})"
 
259
 
260
  else:
 
261
  # Use synthetic data for demonstration
262
  data = generate_synthetic_data(days_back)
263
- data_source = f"Synthetic demonstration data ({days_back} days)"
264
 
265
  # Generate analysis and alerts
266
  analysis, alerts = agent.generate_climate_analysis(data, station_name)
@@ -275,17 +318,9 @@ def analyze_real_ocean_data(station_name, days_back, anomaly_sensitivity, use_re
275
  alerts_text = "\n".join([f"- {alert}" for alert in alerts])
276
 
277
  # Create CSV for download
278
- import tempfile
279
-
280
- #Create CSV
281
- def save_csv_temp(data):
282
- tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode='w', newline='', encoding='utf-8')
283
- data.to_csv(tmp.name, index=False)
284
- tmp.close()
285
- return tmp.name
286
-
287
-
288
  csv_file_path = save_csv_temp(data)
 
 
289
  return fig1, fig2, fig3, analysis_text, alerts_text, csv_file_path
290
 
291
 
@@ -316,92 +351,100 @@ def generate_synthetic_data(days):
316
 
317
  def create_main_dashboard(data, agent):
318
  """Create main dashboard visualization"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  fig = make_subplots(
320
- rows=2, cols=2,
321
- subplot_titles=('Water Level', 'Water Temperature', 'Wind Speed', 'Air Pressure'),
322
  vertical_spacing=0.1
323
  )
324
 
325
- # Water Level
326
- if 'water_level' in data.columns:
 
 
 
327
  fig.add_trace(
328
- go.Scatter(x=data['datetime'], y=data['water_level'],
329
- name='Water Level', line=dict(color='blue')),
330
- row=1, col=1
331
  )
332
 
333
- # Add anomalies
334
- anomalies, _ = agent.detect_anomalies(data, 'water_level')
335
  if anomalies.any():
336
  anomaly_data = data[anomalies]
337
  fig.add_trace(
338
- go.Scatter(x=anomaly_data['datetime'], y=anomaly_data['water_level'],
339
- mode='markers', name='Anomalies',
340
  marker=dict(color='red', size=6)),
341
- row=1, col=1
342
  )
343
 
344
- # Water Temperature
345
- if 'water_temp' in data.columns:
346
- fig.add_trace(
347
- go.Scatter(x=data['datetime'], y=data['water_temp'],
348
- name='Water Temp', line=dict(color='red')),
349
- row=1, col=2
350
- )
351
-
352
- # Wind Speed
353
- if 'wind_speed' in data.columns:
354
- fig.add_trace(
355
- go.Scatter(x=data['datetime'], y=data['wind_speed'],
356
- name='Wind Speed', line=dict(color='green')),
357
- row=2, col=1
358
- )
359
-
360
- # Air Pressure
361
- if 'air_pressure' in data.columns:
362
- fig.add_trace(
363
- go.Scatter(x=data['datetime'], y=data['air_pressure'],
364
- name='Air Pressure', line=dict(color='purple')),
365
- row=2, col=2
366
- )
367
-
368
- fig.update_layout(height=600, showlegend=False, title_text="Ocean and Atmospheric Data Dashboard")
369
  return fig
370
 
371
  def create_anomaly_plots(data, agent):
372
  """Create anomaly detection plots"""
 
 
 
 
 
 
 
 
 
 
 
373
  fig = make_subplots(
374
- rows=1, cols=2,
375
- subplot_titles=('Water Level Anomalies', 'Temperature Anomalies')
376
  )
377
 
378
- # Water level anomalies
379
- if 'water_level' in data.columns:
380
- _, z_scores = agent.detect_anomalies(data, 'water_level')
381
- fig.add_trace(
382
- go.Scatter(x=data['datetime'], y=z_scores,
383
- mode='lines', name='Water Level Z-Score'),
384
- row=1, col=1
385
- )
386
- fig.add_hline(y=agent.anomaly_threshold, line_dash="dash", line_color="red", row=1, col=1)
387
 
388
- # Temperature anomalies
389
- if 'water_temp' in data.columns:
390
- _, z_scores = agent.detect_anomalies(data, 'water_temp')
391
  fig.add_trace(
392
  go.Scatter(x=data['datetime'], y=z_scores,
393
- mode='lines', name='Temperature Z-Score', line=dict(color='red')),
394
- row=1, col=2
 
395
  )
396
- fig.add_hline(y=agent.anomaly_threshold, line_dash="dash", line_color="red", row=1, col=2)
397
 
398
  fig.update_layout(height=400, showlegend=False, title_text="Anomaly Detection Analysis")
399
  return fig
400
 
401
  def create_correlation_plot(data):
402
  """Create correlation heatmap"""
403
- numeric_cols = [col for col in ['water_level', 'water_temp', 'wind_speed', 'air_pressure']
404
- if col in data.columns]
405
 
406
  if len(numeric_cols) < 2:
407
  # Return empty plot if insufficient data
@@ -423,6 +466,10 @@ def format_analysis_results(analysis, data_source):
423
  """Format analysis results for display"""
424
  result = f"### {data_source}\n\n**Key Trends:**\n"
425
 
 
 
 
 
426
  for key, value in analysis.items():
427
  if 'trend' in key:
428
  param = key.replace('_trend', '').replace('_', ' ').title()
@@ -434,6 +481,13 @@ def format_analysis_results(analysis, data_source):
434
 
435
  return result
436
 
 
 
 
 
 
 
 
437
  # Create Gradio interface
438
  with gr.Blocks(title="Enhanced Ocean Climate Monitoring AI Agent", theme=gr.themes.Ocean()) as demo:
439
  gr.Markdown("""
@@ -442,6 +496,8 @@ with gr.Blocks(title="Enhanced Ocean Climate Monitoring AI Agent", theme=gr.them
442
 
443
  This enhanced AI agent can fetch real ocean data from NOAA stations or use synthetic data for demonstration.
444
  Monitor water levels, temperature, currents, and detect climate anomalies at major coastal locations.
 
 
445
  """)
446
 
447
  with gr.Row():
@@ -453,11 +509,12 @@ with gr.Blocks(title="Enhanced Ocean Climate Monitoring AI Agent", theme=gr.them
453
  label="NOAA Station Location"
454
  )
455
  days_back = gr.Slider(
456
- minimum=7,
457
- maximum=90,
458
- value=30,
459
  step=1,
460
- label="Days of Historical Data"
 
461
  )
462
  anomaly_sensitivity = gr.Slider(
463
  minimum=1.0,
@@ -506,7 +563,7 @@ with gr.Blocks(title="Enhanced Ocean Climate Monitoring AI Agent", theme=gr.them
506
  fn=analyze_real_ocean_data,
507
  inputs=[
508
  gr.Text(value="San Francisco, CA", visible=False),
509
- gr.Number(value=30, visible=False),
510
  gr.Number(value=2.0, visible=False),
511
  gr.Checkbox(value=False, visible=False) # Start with synthetic data
512
  ],
 
17
  self.anomaly_threshold = 2.0
18
  self.critical_temp_change = 1.5
19
 
20
+ # Fixed NOAA API base URL
21
+ self.noaa_base_url = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter"
22
  self.noaa_stations_url = "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations.json"
23
 
24
  # Popular NOAA stations for different regions
 
33
  "Charleston, SC": "8665530"
34
  }
35
 
36
+ def get_noaa_data(self, station_id, product, begin_date, end_date, units="metric"):
37
+ """Fetch NOAA data for a given product and date range"""
38
+ # Format dates for NOAA API
39
+ begin_str = begin_date.strftime("%Y%m%d %H:%M")
40
+ end_str = end_date.strftime("%Y%m%d %H:%M")
41
+
42
  params = {
43
+ 'station': station_id,
44
  'product': product,
45
+ 'begin_date': begin_str,
46
+ 'end_date': end_str,
47
+ 'datum': 'MLLW', # Mean Lower Low Water
48
  'application': 'OceanClimateAgent',
 
 
 
49
  'time_zone': 'gmt',
50
  'units': units,
51
  'format': 'json'
52
  }
53
+
54
  try:
55
+ print(f"🌐 Fetching {product} data for station {station_id}")
56
+ print(f"πŸ“… Date range: {begin_str} to {end_str}")
57
+
 
 
 
 
58
  response = requests.get(self.noaa_base_url, params=params, timeout=30)
59
+
60
+ if response.status_code != 200:
61
+ print(f"❌ HTTP Error {response.status_code}: {response.text}")
62
+ return None
63
+
64
+ data = response.json()
65
+
66
+ if 'data' in data and data['data']:
67
+ print(f"βœ… Successfully fetched {len(data['data'])} records for {product}")
68
+ return pd.DataFrame(data['data'])
69
+ elif 'error' in data:
70
+ print(f"❌ NOAA API error for {product}: {data['error']['message']}")
71
+ return None
72
  else:
73
+ print(f"❌ No data returned for {product}")
74
+ return None
75
+
76
+ except requests.exceptions.Timeout:
77
+ print(f"⏰ Timeout fetching {product} data")
78
+ return None
79
+ except requests.exceptions.RequestException as e:
80
  print(f"❌ Request failed for {product}: {str(e)}")
81
+ return None
82
+ except json.JSONDecodeError as e:
83
+ print(f"❌ JSON decode error for {product}: {str(e)}")
84
+ return None
85
+ except Exception as e:
86
+ print(f"❌ Unexpected error fetching {product}: {str(e)}")
87
+ return None
88
 
89
  def get_comprehensive_station_data(self, station_name, days_back=30):
90
  """Get comprehensive data from a NOAA station"""
 
92
  if not station_id:
93
  return None, "Station not found"
94
 
95
+ # Ensure end_date is not in the future and allow for some buffer
96
+ end_date = datetime.utcnow() - timedelta(hours=2) # 2 hour buffer
 
97
  start_date = end_date - timedelta(days=days_back)
 
98
 
99
+ print(f"πŸ” Fetching data for {station_name} (ID: {station_id})")
100
+ print(f"πŸ“… Date range: {start_date} to {end_date}")
101
+
102
+ # Priority order - start with most reliable products
103
+ products_to_fetch = [
104
+ ('water_level', 'water_level'),
105
+ ('water_temperature', 'water_temperature'),
106
+ ('air_temperature', 'air_temperature'),
107
+ ('wind', 'wind'),
108
+ ('air_pressure', 'air_pressure')
109
+ ]
110
 
111
  all_data = {}
112
  success_count = 0
113
 
114
+ for product_name, product_code in products_to_fetch:
115
+ print(f"πŸ”„ Attempting to fetch {product_name}...")
116
  data = self.get_noaa_data(station_id, product_code, start_date, end_date)
117
+
118
  if data is not None and not data.empty:
119
  all_data[product_name] = data
120
  success_count += 1
121
+ print(f"βœ… {product_name}: {len(data)} records")
122
+ else:
123
+ print(f"❌ {product_name}: No data available")
124
 
125
  if success_count == 0:
126
+ return None, f"No data available for station {station_name} in the specified time period. This could be due to: station maintenance, data processing delays, or the station may not support the requested data types."
127
 
128
+ print(f"πŸŽ‰ Successfully retrieved {success_count}/{len(products_to_fetch)} data types")
129
  return all_data, f"Successfully retrieved {success_count}/{len(products_to_fetch)} data types"
130
 
131
  def process_noaa_data(self, raw_data):
 
133
  if not raw_data:
134
  return None
135
 
136
+ base_df = None
137
+
138
+ # Start with water level data if available (most common)
139
  if 'water_level' in raw_data:
140
  df = raw_data['water_level'].copy()
141
  df['datetime'] = pd.to_datetime(df['t'])
142
  df['water_level'] = pd.to_numeric(df['v'], errors='coerce')
143
+ base_df = df[['datetime', 'water_level']].copy()
144
+ print(f"πŸ“Š Base dataset: water_level with {len(base_df)} records")
145
+
146
+ # If no water level, try other datasets
147
+ if base_df is None:
148
+ for product_name in ['water_temperature', 'air_temperature', 'wind', 'air_pressure']:
149
+ if product_name in raw_data:
150
+ df = raw_data[product_name].copy()
151
+ df['datetime'] = pd.to_datetime(df['t'])
152
+ if product_name == 'wind':
153
+ df['wind_speed'] = pd.to_numeric(df['s'], errors='coerce')
154
+ base_df = df[['datetime', 'wind_speed']].copy()
155
+ else:
156
+ column_name = product_name.replace('_temperature', '_temp')
157
+ df[column_name] = pd.to_numeric(df['v'], errors='coerce')
158
+ base_df = df[['datetime', column_name]].copy()
159
+ print(f"πŸ“Š Base dataset: {product_name} with {len(base_df)} records")
160
+ break
161
+
162
+ if base_df is None:
163
+ return None
164
+
165
+ # Add other parameters when available
166
+ if 'water_temperature' in raw_data and 'water_temp' not in base_df.columns:
167
+ temp_df = raw_data['water_temperature'].copy()
168
+ temp_df['datetime'] = pd.to_datetime(temp_df['t'])
169
+ temp_df['water_temp'] = pd.to_numeric(temp_df['v'], errors='coerce')
170
+ base_df = base_df.merge(temp_df[['datetime', 'water_temp']], on='datetime', how='outer')
 
 
 
 
 
 
171
 
172
+ if 'air_temperature' in raw_data and 'air_temp' not in base_df.columns:
173
+ air_temp_df = raw_data['air_temperature'].copy()
174
+ air_temp_df['datetime'] = pd.to_datetime(air_temp_df['t'])
175
+ air_temp_df['air_temp'] = pd.to_numeric(air_temp_df['v'], errors='coerce')
176
+ base_df = base_df.merge(air_temp_df[['datetime', 'air_temp']], on='datetime', how='outer')
177
+
178
+ if 'wind' in raw_data and 'wind_speed' not in base_df.columns:
179
+ wind_df = raw_data['wind'].copy()
180
+ wind_df['datetime'] = pd.to_datetime(wind_df['t'])
181
+ wind_df['wind_speed'] = pd.to_numeric(wind_df['s'], errors='coerce')
182
+ wind_df['wind_direction'] = pd.to_numeric(wind_df['d'], errors='coerce')
183
+ base_df = base_df.merge(wind_df[['datetime', 'wind_speed', 'wind_direction']], on='datetime', how='outer')
184
+
185
+ if 'air_pressure' in raw_data and 'air_pressure' not in base_df.columns:
186
+ pressure_df = raw_data['air_pressure'].copy()
187
+ pressure_df['datetime'] = pd.to_datetime(pressure_df['t'])
188
+ pressure_df['air_pressure'] = pd.to_numeric(pressure_df['v'], errors='coerce')
189
+ base_df = base_df.merge(pressure_df[['datetime', 'air_pressure']], on='datetime', how='outer')
190
+
191
+ # Sort by datetime and remove duplicates
192
+ base_df = base_df.sort_values('datetime').drop_duplicates(subset=['datetime'])
193
+
194
+ print(f"πŸ“ˆ Final processed dataset: {len(base_df)} records with {len(base_df.columns)-1} parameters")
195
+ return base_df
196
 
197
  def detect_anomalies(self, data, column, window=24): # 24 hours for hourly data
198
  """Detect anomalies using rolling statistics"""
 
247
  alerts.append(f"Significant water level change: {wl_trend*24:.1f}cm/day at {station_name}")
248
 
249
  # Temperature analysis
250
+ for temp_col in ['water_temp', 'air_temp']:
251
+ if temp_col in data.columns:
252
+ temp_trend = self.calculate_trends(data, temp_col)
253
+ analysis[f'{temp_col}_trend'] = temp_trend * 24 # per day
254
+
255
+ if temp_trend * 24 > 0.5: # >0.5Β°C per day
256
+ alerts.append(f"Rapid {temp_col.replace('_', ' ')} rise: {temp_trend*24:.2f}Β°C/day at {station_name}")
257
 
258
  # Anomaly detection
259
+ for col in ['water_level', 'water_temp', 'air_temp', 'wind_speed']:
260
  if col in data.columns:
261
  anomalies, z_scores = self.detect_anomalies(data, col)
262
  anomaly_pct = (anomalies.sum() / len(data)) * 100
 
279
  agent.anomaly_threshold = anomaly_sensitivity
280
 
281
  if use_real_data:
282
+ print(f"πŸš€ Starting real data analysis for {station_name}")
283
  # Fetch real NOAA data
284
  raw_data, status_msg = agent.get_comprehensive_station_data(station_name, days_back)
285
 
286
  if raw_data is None:
287
+ error_msg = f"❌ Error fetching real data: {status_msg}"
288
+ print(error_msg)
289
+ return None, None, None, error_msg, "No alerts - data unavailable", None
290
 
291
  # Process the data
292
  data = agent.process_noaa_data(raw_data)
293
 
294
  if data is None or data.empty:
295
+ error_msg = "❌ No processable data available after fetching from NOAA"
296
+ print(error_msg)
297
+ return None, None, None, error_msg, "No alerts - data unavailable", None
298
 
299
+ data_source = f"βœ… Real NOAA data from {station_name} ({status_msg})"
300
+ print(f"🎯 {data_source}")
301
 
302
  else:
303
+ print("πŸ”§ Using synthetic demonstration data")
304
  # Use synthetic data for demonstration
305
  data = generate_synthetic_data(days_back)
306
+ data_source = f"πŸ”§ Synthetic demonstration data ({days_back} days)"
307
 
308
  # Generate analysis and alerts
309
  analysis, alerts = agent.generate_climate_analysis(data, station_name)
 
318
  alerts_text = "\n".join([f"- {alert}" for alert in alerts])
319
 
320
  # Create CSV for download
 
 
 
 
 
 
 
 
 
 
321
  csv_file_path = save_csv_temp(data)
322
+
323
+ print("βœ… Analysis completed successfully")
324
  return fig1, fig2, fig3, analysis_text, alerts_text, csv_file_path
325
 
326
 
 
351
 
352
  def create_main_dashboard(data, agent):
353
  """Create main dashboard visualization"""
354
+ available_plots = []
355
+ plot_data = []
356
+
357
+ # Check what data is available
358
+ if 'water_level' in data.columns and not data['water_level'].isna().all():
359
+ available_plots.append(('Water Level', 'water_level', 'blue'))
360
+ if 'water_temp' in data.columns and not data['water_temp'].isna().all():
361
+ available_plots.append(('Water Temperature', 'water_temp', 'red'))
362
+ if 'air_temp' in data.columns and not data['air_temp'].isna().all():
363
+ available_plots.append(('Air Temperature', 'air_temp', 'orange'))
364
+ if 'wind_speed' in data.columns and not data['wind_speed'].isna().all():
365
+ available_plots.append(('Wind Speed', 'wind_speed', 'green'))
366
+ if 'air_pressure' in data.columns and not data['air_pressure'].isna().all():
367
+ available_plots.append(('Air Pressure', 'air_pressure', 'purple'))
368
+
369
+ if not available_plots:
370
+ fig = go.Figure()
371
+ fig.add_annotation(text="No data available for visualization",
372
+ xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
373
+ return fig
374
+
375
+ # Create subplots based on available data
376
+ n_plots = len(available_plots)
377
+ rows = (n_plots + 1) // 2 # Ceiling division
378
+ cols = 2 if n_plots > 1 else 1
379
+
380
  fig = make_subplots(
381
+ rows=rows, cols=cols,
382
+ subplot_titles=[plot[0] for plot in available_plots],
383
  vertical_spacing=0.1
384
  )
385
 
386
+ for i, (title, column, color) in enumerate(available_plots):
387
+ row = (i // 2) + 1
388
+ col = (i % 2) + 1
389
+
390
+ # Add main data line
391
  fig.add_trace(
392
+ go.Scatter(x=data['datetime'], y=data[column],
393
+ name=title, line=dict(color=color)),
394
+ row=row, col=col
395
  )
396
 
397
+ # Add anomalies if applicable
398
+ anomalies, _ = agent.detect_anomalies(data, column)
399
  if anomalies.any():
400
  anomaly_data = data[anomalies]
401
  fig.add_trace(
402
+ go.Scatter(x=anomaly_data['datetime'], y=anomaly_data[column],
403
+ mode='markers', name=f'{title} Anomalies',
404
  marker=dict(color='red', size=6)),
405
+ row=row, col=col
406
  )
407
 
408
+ fig.update_layout(height=300*rows, showlegend=False, title_text="Ocean and Atmospheric Data Dashboard")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  return fig
410
 
411
  def create_anomaly_plots(data, agent):
412
  """Create anomaly detection plots"""
413
+ available_cols = [col for col in ['water_level', 'water_temp', 'air_temp', 'wind_speed']
414
+ if col in data.columns and not data[col].isna().all()]
415
+
416
+ if len(available_cols) == 0:
417
+ fig = go.Figure()
418
+ fig.add_annotation(text="No data available for anomaly detection",
419
+ xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
420
+ return fig
421
+
422
+ n_plots = min(len(available_cols), 2) # Maximum 2 plots
423
+
424
  fig = make_subplots(
425
+ rows=1, cols=n_plots,
426
+ subplot_titles=[f'{col.replace("_", " ").title()} Anomalies' for col in available_cols[:n_plots]]
427
  )
428
 
429
+ colors = ['blue', 'red', 'green', 'purple']
 
 
 
 
 
 
 
 
430
 
431
+ for i, col in enumerate(available_cols[:n_plots]):
432
+ _, z_scores = agent.detect_anomalies(data, col)
 
433
  fig.add_trace(
434
  go.Scatter(x=data['datetime'], y=z_scores,
435
+ mode='lines', name=f'{col} Z-Score',
436
+ line=dict(color=colors[i % len(colors)])),
437
+ row=1, col=i+1
438
  )
439
+ fig.add_hline(y=agent.anomaly_threshold, line_dash="dash", line_color="red", row=1, col=i+1)
440
 
441
  fig.update_layout(height=400, showlegend=False, title_text="Anomaly Detection Analysis")
442
  return fig
443
 
444
  def create_correlation_plot(data):
445
  """Create correlation heatmap"""
446
+ numeric_cols = [col for col in ['water_level', 'water_temp', 'air_temp', 'wind_speed', 'air_pressure']
447
+ if col in data.columns and not data[col].isna().all()]
448
 
449
  if len(numeric_cols) < 2:
450
  # Return empty plot if insufficient data
 
466
  """Format analysis results for display"""
467
  result = f"### {data_source}\n\n**Key Trends:**\n"
468
 
469
+ if not analysis:
470
+ result += "- No analysis data available\n"
471
+ return result
472
+
473
  for key, value in analysis.items():
474
  if 'trend' in key:
475
  param = key.replace('_trend', '').replace('_', ' ').title()
 
481
 
482
  return result
483
 
484
+ def save_csv_temp(data):
485
+ """Save data to temporary CSV file"""
486
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode='w', newline='', encoding='utf-8')
487
+ data.to_csv(tmp.name, index=False)
488
+ tmp.close()
489
+ return tmp.name
490
+
491
  # Create Gradio interface
492
  with gr.Blocks(title="Enhanced Ocean Climate Monitoring AI Agent", theme=gr.themes.Ocean()) as demo:
493
  gr.Markdown("""
 
496
 
497
  This enhanced AI agent can fetch real ocean data from NOAA stations or use synthetic data for demonstration.
498
  Monitor water levels, temperature, currents, and detect climate anomalies at major coastal locations.
499
+
500
+ **Note:** NOAA stations may not have all data types available. The system will use whatever data is accessible.
501
  """)
502
 
503
  with gr.Row():
 
509
  label="NOAA Station Location"
510
  )
511
  days_back = gr.Slider(
512
+ minimum=1,
513
+ maximum=30,
514
+ value=7,
515
  step=1,
516
+ label="Days of Historical Data",
517
+ info="Shorter periods are more reliable"
518
  )
519
  anomaly_sensitivity = gr.Slider(
520
  minimum=1.0,
 
563
  fn=analyze_real_ocean_data,
564
  inputs=[
565
  gr.Text(value="San Francisco, CA", visible=False),
566
+ gr.Number(value=7, visible=False),
567
  gr.Number(value=2.0, visible=False),
568
  gr.Checkbox(value=False, visible=False) # Start with synthetic data
569
  ],