nakas Claude commited on
Commit
bd3ca51
·
1 Parent(s): c098588

Remove all fallback data - fail fast if DWD ICON unavailable

Browse files

- Remove fetch_fallback_data() and process_fallback_data() functions
- Replace all fallback calls with proper error exceptions
- Update error handling to fail cleanly with informative messages
- Remove synthetic data generation and fallback processing
- Simplify plot creation by removing fallback data handling
- Application now works only with real DWD ICON data or fails

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +26 -182
app.py CHANGED
@@ -226,8 +226,7 @@ def fetch_dwd_icon_data(lat, lon):
226
  print(f"Fetching real DWD ICON data for {lat:.3f}°N, {lon:.3f}°E")
227
 
228
  if not GRIB_AVAILABLE:
229
- print("Warning: GRIB2 libraries not available, using fallback data")
230
- return fetch_fallback_data(lat, lon)
231
 
232
  # Get latest model run
233
  run_date = get_latest_dwd_run()
@@ -250,16 +249,14 @@ def fetch_dwd_icon_data(lat, lon):
250
  clat_file, clon_file = download_dwd_coordinate_files(run_date)
251
 
252
  if not clat_file or not clon_file:
253
- print("Failed to download coordinate files, using fallback")
254
- return fetch_fallback_data(lat, lon)
255
 
256
  # Parse coordinate files
257
  clat_ds = parse_grib_file(clat_file)
258
  clon_ds = parse_grib_file(clon_file)
259
 
260
  if clat_ds is None or clon_ds is None:
261
- print("Failed to parse coordinate files, using fallback")
262
- return fetch_fallback_data(lat, lon)
263
 
264
  # Get coordinate arrays
265
  # DWD coordinate files may use different variable names
@@ -287,7 +284,7 @@ def fetch_dwd_icon_data(lat, lon):
287
  print(f"Error extracting coordinate arrays: {e}")
288
  print(f"CLAT dataset: {clat_ds}")
289
  print(f"CLON dataset: {clon_ds}")
290
- return fetch_fallback_data(lat, lon)
291
 
292
  # Find nearest grid point
293
  nearest_idx = find_nearest_grid_point(lat, lon, grid_lats, grid_lons)
@@ -353,76 +350,8 @@ def fetch_dwd_icon_data(lat, lon):
353
  print(f"Error fetching real DWD ICON data: {e}")
354
  import traceback
355
  traceback.print_exc()
356
- return fetch_fallback_data(lat, lon)
357
 
358
- def fetch_fallback_data(lat, lon):
359
- """
360
- Generate realistic fallback data when real DWD data is unavailable
361
- """
362
- print("Using fallback synthetic data")
363
-
364
- current_time = datetime.now(timezone.utc)
365
- forecast_hours = []
366
-
367
- # Generate forecast times - 4 days with 6-hour intervals
368
- for i in range(0, 97, 6): # Every 6 hours for 4 days
369
- forecast_time = current_time + timedelta(hours=i)
370
- forecast_hours.append(forecast_time)
371
-
372
- # Create realistic weather patterns based on location and season
373
- import math
374
- base_temp = 15 + 10 * math.sin((current_time.timetuple().tm_yday - 80) * 2 * math.pi / 365)
375
-
376
- simulated_data = {
377
- "location": {"lat": lat, "lon": lon, "name": f"Location {lat:.2f}°N, {lon:.2f}°E"},
378
- "current": {
379
- "temp_c": base_temp,
380
- "humidity": 65,
381
- "wind_kph": 15,
382
- "pressure_mb": 1013,
383
- "cloud": 40,
384
- "vis_km": 10
385
- },
386
- "forecast": {
387
- "forecastday": []
388
- }
389
- }
390
-
391
- # Generate daily data
392
- current_day = None
393
- day_data = None
394
-
395
- for i, forecast_time in enumerate(forecast_hours):
396
- if forecast_time.date() != current_day:
397
- if day_data:
398
- simulated_data["forecast"]["forecastday"].append(day_data)
399
-
400
- current_day = forecast_time.date()
401
- day_data = {
402
- "date": current_day.strftime("%Y-%m-%d"),
403
- "hour": []
404
- }
405
-
406
- # Generate hourly data
407
- hour_temp = base_temp + 5 * math.sin(forecast_time.hour * math.pi / 12) + 2 * math.sin(i * 0.1)
408
- hour_data = {
409
- "time": forecast_time.strftime("%Y-%m-%d %H:%M"),
410
- "temp_c": hour_temp,
411
- "humidity": int(60 + 20 * math.cos(forecast_time.hour * math.pi / 12 + i * 0.1)),
412
- "wind_kph": 10 + 8 * math.sin(forecast_time.hour * math.pi / 8 + i * 0.05),
413
- "wind_dir": int((forecast_time.hour * 15 + i * 5) % 360),
414
- "pressure_mb": 1013 + 5 * math.sin(forecast_time.hour * math.pi / 12),
415
- "precip_mm": max(0, math.sin(forecast_time.hour * math.pi / 6 + i * 0.2) * 0.5),
416
- "cloud": int(30 + 40 * math.sin(forecast_time.hour * math.pi / 10 + i * 0.15)),
417
- "vis_km": 10 + 5 * math.cos(forecast_time.hour * math.pi / 12),
418
- "gust_kph": 15 + 10 * math.sin(forecast_time.hour * math.pi / 6 + i * 0.1)
419
- }
420
- day_data["hour"].append(hour_data)
421
-
422
- if day_data:
423
- simulated_data["forecast"]["forecastday"].append(day_data)
424
-
425
- return simulated_data
426
 
427
  def get_forecast_data(lat, lon, forecast_hour="00"):
428
  """
@@ -434,13 +363,11 @@ def get_forecast_data(lat, lon, forecast_hour="00"):
434
  # Fetch data from DWD ICON model
435
  weather_data = fetch_dwd_icon_data(lat, lon)
436
 
437
- # Check if we got real DWD data or fallback data
438
  if 'weather_data' in weather_data:
439
- # Real DWD GRIB2 data
440
  return process_real_dwd_data(weather_data, lat, lon)
441
  else:
442
- # Fallback simulated data
443
- return process_fallback_data(weather_data, lat, lon)
444
 
445
  except Exception as e:
446
  import traceback
@@ -449,27 +376,8 @@ def get_forecast_data(lat, lon, forecast_hour="00"):
449
  print("Full error traceback:")
450
  print(traceback.format_exc())
451
 
452
- # Return fallback synthetic data with error note
453
- forecast_days = 4
454
- hours = np.arange(0, forecast_days * 24, 6)
455
- np.random.seed(int(lat * 100 + lon * 100))
456
-
457
- current_date = datetime.now()
458
- timestamps = [current_date + timedelta(hours=int(h)) for h in hours]
459
- temperature = 15 + 10 * np.sin(hours * np.pi / 12) + np.random.normal(0, 2, len(hours))
460
- humidity = 60 + 20 * np.sin(hours * np.pi / 24 + np.pi/4) + np.random.normal(0, 5, len(hours))
461
- wind_speed = 5 + 3 * np.sin(hours * np.pi / 18) + np.random.normal(0, 1, len(hours))
462
-
463
- return {
464
- 'timestamps': timestamps,
465
- 'temperature': temperature,
466
- 'humidity': humidity,
467
- 'wind_speed': wind_speed,
468
- 'lat': lat,
469
- 'lon': lon,
470
- 'error': error_msg,
471
- 'forecast_date': 'Fallback synthetic data'
472
- }
473
 
474
  def process_real_dwd_data(dwd_data, lat, lon):
475
  """
@@ -651,63 +559,6 @@ def process_real_dwd_data(dwd_data, lat, lon):
651
  print(f"Error processing real DWD data: {e}")
652
  raise e
653
 
654
- def process_fallback_data(weather_data, lat, lon):
655
- """
656
- Process fallback simulated data into forecast format
657
- """
658
- # Extract hourly forecast data
659
- timestamps = []
660
- temperature = []
661
- humidity = []
662
- wind_speed = []
663
- wind_direction = []
664
- wind_gust = []
665
- pressure = []
666
- precipitation = []
667
- cloud_cover = []
668
- visibility = []
669
-
670
- # Process hourly data from all forecast days
671
- for day_forecast in weather_data["forecast"]["forecastday"]:
672
- for hour_data in day_forecast["hour"]:
673
- # Parse timestamp
674
- timestamp = datetime.strptime(hour_data["time"], "%Y-%m-%d %H:%M")
675
- timestamps.append(timestamp)
676
-
677
- # Extract weather variables
678
- temperature.append(hour_data["temp_c"])
679
- humidity.append(hour_data["humidity"])
680
- wind_speed.append(hour_data["wind_kph"] * 0.277778) # Convert kph to m/s
681
- wind_direction.append(hour_data["wind_dir"])
682
- wind_gust.append(hour_data["gust_kph"] * 0.277778) # Convert kph to m/s
683
- pressure.append(hour_data["pressure_mb"])
684
- precipitation.append(hour_data["precip_mm"])
685
- cloud_cover.append(hour_data["cloud"])
686
- visibility.append(hour_data["vis_km"])
687
-
688
- # Limit to reasonable forecast length (4 days = 96 hours)
689
- max_hours = min(len(timestamps), 96)
690
-
691
- result = {
692
- 'timestamps': timestamps[:max_hours],
693
- 'temperature': temperature[:max_hours],
694
- 'humidity': humidity[:max_hours],
695
- 'wind_speed': wind_speed[:max_hours],
696
- 'wind_direction': wind_direction[:max_hours],
697
- 'wind_gust': wind_gust[:max_hours],
698
- 'pressure': pressure[:max_hours],
699
- 'precipitation': precipitation[:max_hours],
700
- 'cloud_cover': cloud_cover[:max_hours],
701
- 'visibility': visibility[:max_hours],
702
- 'lat': lat,
703
- 'lon': lon,
704
- 'forecast_date': datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC'),
705
- 'data_source': 'DWD ICON Model (Simulated)',
706
- 'location_name': weather_data["location"]["name"]
707
- }
708
-
709
- print(f"Successfully processed {len(timestamps)} hours of fallback forecast data")
710
- return result
711
 
712
  def analyze_weather_events(forecast_data):
713
  """
@@ -1593,8 +1444,8 @@ def create_forecast_plot(forecast_data):
1593
  ax9 = fig.add_subplot(gs[2, 2])
1594
  ax9.axis('off')
1595
 
1596
- # Check if we have real data or fallback
1597
- data_source = "Real DWD ICON Data" if 'error' not in forecast_data else "Fallback Synthetic Data"
1598
  forecast_info = forecast_data.get('forecast_date', 'Unknown')
1599
 
1600
  # Grid point info
@@ -1638,11 +1489,7 @@ Wind: {forecast_data['wind_speed'][0]:.1f} m/s
1638
  if 'pressure' in forecast_data:
1639
  summary_text += f"Pressure: {forecast_data['pressure'][0]:.1f} hPa\n"
1640
 
1641
- # Add error info if present
1642
- if 'error' in forecast_data:
1643
- summary_text += f"\nNote: Using fallback data\nReason: {forecast_data['error'][:80]}..."
1644
-
1645
- color = 'lightgreen' if 'error' not in forecast_data else 'lightyellow'
1646
  ax9.text(0.05, 0.95, summary_text, transform=ax9.transAxes, fontsize=8,
1647
  verticalalignment='top', bbox=dict(boxstyle='round', facecolor=color, alpha=0.7))
1648
 
@@ -1655,30 +1502,27 @@ def process_map_click(lat, lon):
1655
  if lat is None or lon is None:
1656
  return "Please click on the map to select a location", None, ""
1657
 
1658
- # Get forecast data
1659
- forecast_data = get_forecast_data(lat, lon)
1660
-
1661
- # Create plot
1662
- plot = create_forecast_plot(forecast_data)
1663
-
1664
- # Create summary text
1665
- if isinstance(forecast_data, dict):
1666
- data_type = "Real DWD ICON Data" if 'error' not in forecast_data else "Fallback Data"
1667
  forecast_info = forecast_data.get('forecast_date', '')
1668
  summary = f"Forecast for location: {lat:.3f}°N, {lon:.3f}°E\n\nUsing: {data_type}\nForecast: {forecast_info}"
1669
 
1670
- if 'error' in forecast_data:
1671
- summary += f"\n\nNote: Real data unavailable - {forecast_data['error'][:150]}..."
1672
-
1673
  # Generate NOAA-style text forecast
1674
  location_name = forecast_data.get('location_name', f"{lat:.2f}°N, {lon:.2f}°E")
1675
  text_forecast = generate_forecast_text(forecast_data, location_name)
1676
 
1677
- else:
1678
- summary = forecast_data
1679
- text_forecast = "Unable to generate text forecast - data error"
1680
-
1681
- return summary, plot, text_forecast
1682
 
1683
  def create_attribution_text():
1684
  """Create proper attribution for the dataset"""
 
226
  print(f"Fetching real DWD ICON data for {lat:.3f}°N, {lon:.3f}°E")
227
 
228
  if not GRIB_AVAILABLE:
229
+ raise Exception("GRIB2 libraries not available. Install cfgrib and pygrib for DWD ICON data access.")
 
230
 
231
  # Get latest model run
232
  run_date = get_latest_dwd_run()
 
249
  clat_file, clon_file = download_dwd_coordinate_files(run_date)
250
 
251
  if not clat_file or not clon_file:
252
+ raise Exception("Failed to download coordinate files from DWD ICON server")
 
253
 
254
  # Parse coordinate files
255
  clat_ds = parse_grib_file(clat_file)
256
  clon_ds = parse_grib_file(clon_file)
257
 
258
  if clat_ds is None or clon_ds is None:
259
+ raise Exception("Failed to parse coordinate files from DWD ICON server")
 
260
 
261
  # Get coordinate arrays
262
  # DWD coordinate files may use different variable names
 
284
  print(f"Error extracting coordinate arrays: {e}")
285
  print(f"CLAT dataset: {clat_ds}")
286
  print(f"CLON dataset: {clon_ds}")
287
+ raise Exception(f"Failed to extract coordinate arrays: {e}")
288
 
289
  # Find nearest grid point
290
  nearest_idx = find_nearest_grid_point(lat, lon, grid_lats, grid_lons)
 
350
  print(f"Error fetching real DWD ICON data: {e}")
351
  import traceback
352
  traceback.print_exc()
353
+ raise Exception(f"Failed to fetch DWD ICON data: {e}")
354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
 
356
  def get_forecast_data(lat, lon, forecast_hour="00"):
357
  """
 
363
  # Fetch data from DWD ICON model
364
  weather_data = fetch_dwd_icon_data(lat, lon)
365
 
366
+ # Process real DWD GRIB2 data only
367
  if 'weather_data' in weather_data:
 
368
  return process_real_dwd_data(weather_data, lat, lon)
369
  else:
370
+ raise Exception(\"Invalid weather data format received from DWD ICON server\")
 
371
 
372
  except Exception as e:
373
  import traceback
 
376
  print("Full error traceback:")
377
  print(traceback.format_exc())
378
 
379
+ # No fallback - raise the error
380
+ raise Exception(error_msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
 
382
  def process_real_dwd_data(dwd_data, lat, lon):
383
  """
 
559
  print(f"Error processing real DWD data: {e}")
560
  raise e
561
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
 
563
  def analyze_weather_events(forecast_data):
564
  """
 
1444
  ax9 = fig.add_subplot(gs[2, 2])
1445
  ax9.axis('off')
1446
 
1447
+ # Only real DWD ICON data now
1448
+ data_source = "Real DWD ICON Data"
1449
  forecast_info = forecast_data.get('forecast_date', 'Unknown')
1450
 
1451
  # Grid point info
 
1489
  if 'pressure' in forecast_data:
1490
  summary_text += f"Pressure: {forecast_data['pressure'][0]:.1f} hPa\n"
1491
 
1492
+ color = 'lightgreen'
 
 
 
 
1493
  ax9.text(0.05, 0.95, summary_text, transform=ax9.transAxes, fontsize=8,
1494
  verticalalignment='top', bbox=dict(boxstyle='round', facecolor=color, alpha=0.7))
1495
 
 
1502
  if lat is None or lon is None:
1503
  return "Please click on the map to select a location", None, ""
1504
 
1505
+ try:
1506
+ # Get forecast data - will raise exception if it fails
1507
+ forecast_data = get_forecast_data(lat, lon)
1508
+
1509
+ # Create plot
1510
+ plot = create_forecast_plot(forecast_data)
1511
+
1512
+ # Create summary text
1513
+ data_type = "Real DWD ICON Data"
1514
  forecast_info = forecast_data.get('forecast_date', '')
1515
  summary = f"Forecast for location: {lat:.3f}°N, {lon:.3f}°E\n\nUsing: {data_type}\nForecast: {forecast_info}"
1516
 
 
 
 
1517
  # Generate NOAA-style text forecast
1518
  location_name = forecast_data.get('location_name', f"{lat:.2f}°N, {lon:.2f}°E")
1519
  text_forecast = generate_forecast_text(forecast_data, location_name)
1520
 
1521
+ return summary, plot, text_forecast
1522
+
1523
+ except Exception as e:
1524
+ error_msg = f"❌ Failed to retrieve DWD ICON data: {str(e)}"
1525
+ return error_msg, None, "Unable to generate forecast - DWD ICON data unavailable"
1526
 
1527
  def create_attribution_text():
1528
  """Create proper attribution for the dataset"""