"""Weather data router -- primary + secondary locations and hourly forecast.""" from __future__ import annotations import asyncio import logging from typing import Any, Dict, List from fastapi import APIRouter from server.cache import cache from server.config import get_settings from server.services.weather_service import fetch_hourly_forecast, fetch_weather logger = logging.getLogger(__name__) router = APIRouter(prefix="/api", tags=["weather"]) CACHE_KEY = "weather" @router.get("/weather") async def get_weather() -> Dict[str, Any]: """Return weather for both configured locations plus an hourly forecast.""" # --- cache hit? ----------------------------------------------------------- cached = await cache.get(CACHE_KEY) if cached is not None: logger.debug("[WEATHER] Cache hit (key=%s)", CACHE_KEY) return cached # --- 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) results = await asyncio.gather( _safe_fetch_weather(cfg.weather_location), _safe_fetch_weather(cfg.weather_location_secondary), _safe_fetch_hourly(cfg.weather_location), return_exceptions=False, ) primary_data = results[0] secondary_data = results[1] hourly_data = results[2] # 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)) payload: Dict[str, Any] = { "primary": primary_data, "secondary": secondary_data, "hourly": hourly_data, } await cache.set(CACHE_KEY, payload, cfg.weather_cache_ttl) logger.debug("[WEATHER] Cached for %ds", cfg.weather_cache_ttl) return payload # -- internal helpers --------------------------------------------------------- def _log_weather_result(label: str, location: str, data: Dict[str, Any]) -> None: """Log a concise summary of a weather result.""" if data.get("error"): logger.error("[WEATHER] %s (%s): ERROR — %s", label, location, data.get("message", data.get("error"))) else: logger.info("[WEATHER] %s (%s): %d°C, %s", label, data.get("location", location), data.get("temp", 0), data.get("description", "?")) async def _safe_fetch_weather(location: str) -> Dict[str, Any]: """Fetch weather for *location*, returning an error stub on failure.""" try: return await fetch_weather(location) except Exception as exc: logger.exception("[WEATHER] Unhandled error fetching '%s'", location) return {"error": True, "message": str(exc), "location": location} async def _safe_fetch_hourly(location: str) -> List[Dict[str, Any]]: """Fetch hourly forecast for *location*, returning ``[]`` on failure.""" try: return await fetch_hourly_forecast(location) except Exception as exc: logger.exception("[WEATHER] Unhandled error fetching hourly for '%s'", location) return []