Add: München als 3. Wetter-Location + Wetter-Detail-Modal

- München als tertiärer Standort (iris-Akzent) hinzugefügt
- Klick auf WeatherCard öffnet Detail-Modal mit:
  - 24h stündliche Prognose (horizontal scrollbar)
  - 7-Tage-Vorhersage mit Temperaturbalken
  - Wind, Feuchte, Sonnenauf/-untergang
- Backend: 7-Tage statt 3-Tage Forecast, 24 Hourly-Slots pro Standort
- Backend: forecast_3day → forecast Feldname-Konsistenz
- Dashboard: 3-Spalten Wetter-Grid statt 4 (HourlyForecast → Modal)
- Admin: Tertiärer Standort konfigurierbar
- THERMAL Design: iris glow, modal animation, Portal-basiertes Modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sam 2026-03-03 01:13:49 +01:00
parent d9626108e6
commit 2f56be835e
13 changed files with 379 additions and 36 deletions

View file

@ -1,4 +1,4 @@
"""Weather data router -- primary + secondary locations and hourly forecast."""
"""Weather data router -- primary, secondary & tertiary locations with hourly forecasts."""
from __future__ import annotations
@ -31,29 +31,41 @@ async def get_weather() -> Dict[str, Any]:
# --- cache miss -- fetch all three in parallel ----------------------------
cfg = get_settings()
logger.info("[WEATHER] Cache miss — fetching '%s' + '%s'",
cfg.weather_location, cfg.weather_location_secondary)
logger.info("[WEATHER] Cache miss — fetching '%s' + '%s' + '%s'",
cfg.weather_location, cfg.weather_location_secondary,
cfg.weather_location_tertiary)
results = await asyncio.gather(
_safe_fetch_weather(cfg.weather_location),
_safe_fetch_weather(cfg.weather_location_secondary),
_safe_fetch_hourly(cfg.weather_location),
_safe_fetch_weather(cfg.weather_location_tertiary),
_safe_fetch_hourly(cfg.weather_location, max_slots=24),
_safe_fetch_hourly(cfg.weather_location_secondary, max_slots=24),
_safe_fetch_hourly(cfg.weather_location_tertiary, max_slots=24),
return_exceptions=False,
)
primary_data = results[0]
secondary_data = results[1]
hourly_data = results[2]
tertiary_data = results[2]
hourly_data = results[3]
hourly_secondary = results[4]
hourly_tertiary = results[5]
# Log result summary
_log_weather_result("primary", cfg.weather_location, primary_data)
_log_weather_result("secondary", cfg.weather_location_secondary, secondary_data)
logger.info("[WEATHER] Hourly: %d slots", len(hourly_data))
_log_weather_result("tertiary", cfg.weather_location_tertiary, tertiary_data)
logger.info("[WEATHER] Hourly: %d + %d + %d slots",
len(hourly_data), len(hourly_secondary), len(hourly_tertiary))
payload: Dict[str, Any] = {
"primary": primary_data,
"secondary": secondary_data,
"tertiary": tertiary_data,
"hourly": hourly_data,
"hourly_secondary": hourly_secondary,
"hourly_tertiary": hourly_tertiary,
}
await cache.set(CACHE_KEY, payload, cfg.weather_cache_ttl)
@ -83,10 +95,10 @@ async def _safe_fetch_weather(location: str) -> Dict[str, Any]:
return {"error": True, "message": str(exc), "location": location}
async def _safe_fetch_hourly(location: str) -> List[Dict[str, Any]]:
async def _safe_fetch_hourly(location: str, max_slots: int = 8) -> List[Dict[str, Any]]:
"""Fetch hourly forecast for *location*, returning ``[]`` on failure."""
try:
return await fetch_hourly_forecast(location)
return await fetch_hourly_forecast(location, max_slots=max_slots)
except Exception as exc:
logger.exception("[WEATHER] Unhandled error fetching hourly for '%s'", location)
return []