""" BAN YARO — Wetter via Open-Meteo - get_weather_summary(): Push-Job, prüft 5 deutsche Städte - get_weather_for_location(): API-Endpoint, beliebiger Standort mit TTL-Cache """ import time import logging import httpx from datetime import datetime logger = logging.getLogger(__name__) # WMO-Wettercodes → (Beschreibung, Phosphor-Icon-Name) _WMO = { 0: ('Klar', 'sun'), 1: ('Überwiegend klar', 'cloud-sun'), 2: ('Teilweise bewölkt', 'cloud-sun'), 3: ('Bedeckt', 'cloud'), 45: ('Nebel', 'cloud-fog'), 48: ('Gefrierender Nebel', 'cloud-fog'), 51: ('Leichter Nieselregen', 'cloud-rain'), 53: ('Nieselregen', 'cloud-rain'), 55: ('Starker Nieselregen', 'cloud-rain'), 61: ('Leichter Regen', 'cloud-rain'), 63: ('Regen', 'cloud-rain'), 65: ('Starker Regen', 'cloud-rain'), 71: ('Leichter Schnee', 'snowflake'), 73: ('Schnee', 'snowflake'), 75: ('Starker Schnee', 'snowflake'), 77: ('Schneekörner', 'snowflake'), 80: ('Leichte Schauer', 'cloud-rain'), 81: ('Schauer', 'cloud-rain'), 82: ('Starke Schauer', 'cloud-rain'), 85: ('Schneeschauer', 'snowflake'), 86: ('Starke Schneeschauer', 'snowflake'), 95: ('Gewitter', 'cloud-lightning'), 96: ('Gewitter mit Hagel', 'cloud-lightning'), 99: ('Schweres Gewitter', 'cloud-lightning'), } # TTL-Cache: (round(lat,1), round(lon,1)) → (timestamp, data) _location_cache: dict = {} _CACHE_TTL = 1800 # 30 Minuten async def get_weather_for_location(lat: float, lon: float) -> dict: """Holt aktuelles Wetter für einen Standort. 30-min TTL-Cache.""" key = (round(lat, 1), round(lon, 1)) now = time.time() if key in _location_cache: ts, cached = _location_cache[key] if now - ts < _CACHE_TTL: return cached url = ( "https://api.open-meteo.com/v1/forecast" f"?latitude={lat}&longitude={lon}" "¤t=temperature_2m,apparent_temperature,weathercode,windspeed_10m,is_day" "&daily=precipitation_probability_max,uv_index_max" "&timezone=Europe%2FBerlin&forecast_days=1" ) async with httpx.AsyncClient(timeout=8.0) as client: resp = await client.get(url) resp.raise_for_status() raw = resp.json() cur = raw.get('current', {}) daily = raw.get('daily', {}) temp = cur.get('temperature_2m') feels_like = cur.get('apparent_temperature') wcode = cur.get('weathercode', 0) wind = cur.get('windspeed_10m') is_day = cur.get('is_day', 1) precip = (daily.get('precipitation_probability_max') or [None])[0] uv = (daily.get('uv_index_max') or [None])[0] desc, icon = _WMO.get(wcode, ('Unbekannt', 'cloud')) if wcode == 0 and not is_day: icon = 'moon' month = datetime.now().month zecken = None if temp is not None and temp > 7.0 and 3 <= month <= 10: zecken = 'hoch' if temp > 20 else ('mittel' if temp > 12 else 'niedrig') data = { 'temp_c': temp, 'feels_like_c': feels_like, 'weathercode': wcode, 'desc': desc, 'icon': icon, 'wind_kmh': wind, 'precip_prob': precip, 'uv_index': uv, 'is_day': bool(is_day), 'zecken_warnung': zecken, } _location_cache[key] = (now, data) return data # Wichtige deutsche Städte als Stichprobe CITIES = [ {"name": "Berlin", "lat": 52.52, "lon": 13.41}, {"name": "München", "lat": 48.14, "lon": 11.58}, {"name": "Hamburg", "lat": 53.55, "lon": 10.00}, {"name": "Frankfurt", "lat": 50.11, "lon": 8.68}, {"name": "Köln", "lat": 50.94, "lon": 6.96}, ] # WMO-Wettercodes 95–99 = Gewitter THUNDERSTORM_CODES = {95, 96, 99} async def get_weather_summary() -> dict: """ Holt Tagesprognose für mehrere deutsche Städte von Open-Meteo. Gibt zurück: {"max_temp_c": float, "thunderstorm": bool} """ max_temp = -999.0 thunderstorm = False async with httpx.AsyncClient(timeout=10.0) as client: for city in CITIES: url = ( "https://api.open-meteo.com/v1/forecast" f"?latitude={city['lat']}&longitude={city['lon']}" "&daily=temperature_2m_max,precipitation_probability_max,weathercode" "&timezone=Europe%2FBerlin&forecast_days=1" ) try: resp = await client.get(url) resp.raise_for_status() data = resp.json() daily = data.get("daily", {}) temps = daily.get("temperature_2m_max", [None]) precip_probs = daily.get("precipitation_probability_max", [None]) weathercodes = daily.get("weathercode", [None]) temp = temps[0] if temps else None precip_prob = precip_probs[0] if precip_probs else None weathercode = weathercodes[0] if weathercodes else None if temp is not None and temp > max_temp: max_temp = temp if ( precip_prob is not None and precip_prob > 50 and weathercode is not None and int(weathercode) in THUNDERSTORM_CODES ): thunderstorm = True logger.info(f"Gewitter erkannt: {city['name']} (Code {weathercode}, {precip_prob}% Niederschlag)") except Exception as e: logger.warning(f"Wetter-Abruf für {city['name']} fehlgeschlagen: {e}") if max_temp == -999.0: max_temp = 0.0 logger.info(f"Wetter-Zusammenfassung: max_temp={max_temp}°C, thunderstorm={thunderstorm}") return {"max_temp_c": max_temp, "thunderstorm": thunderstorm}