Wetter-Chip auf Karte + Bugfix private Routen zählen für km-Stats

- GET /api/weather?lat=&lon= (Open-Meteo, 30-min TTL-Cache)
- Zecken-Warnung regelbasiert: März–Okt + Temp > 7°C
- Karte: Wetterchip oben rechts nach GPS-Fix
- stats.py + achievements.py: is_public-Filter entfernt —
  private Routen zählen jetzt für eigene km/Achievements
- SW by-v320, APP_VER 308
This commit is contained in:
rene 2026-04-24 07:59:15 +02:00
parent 43d33c0fd1
commit 0461f936ce
9 changed files with 185 additions and 13 deletions

View file

@ -1,13 +1,105 @@
"""
BAN YARO Wetter-Zusammenfassung via Open-Meteo
Prüft mehrere deutsche Städte und liefert max. Temperatur und Gewitterwarnung.
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}"
"&current=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},