Tagebuch: manuelle Positionierung reichert POIs + Wetter an (v1305)
Bisher holte nur der Create-/Foto-EXIF-Pfad Wetter+POIs. Wer einem Eintrag nachträglich (Edit) einen Standort gab, bekam nichts. Jetzt: GPS neu/geändert im Update-Handler -> POIs immer + Wetter fürs Eintragsdatum. - weather.get_weather_for_date(): heute -> aktuelles Wetter; Vergangenheit -> stündliche Historie (Open-Meteo Forecast <=90 Tage, sonst Archive-API), Stunde aus created_at. Gleiche Dict-Struktur wie get_weather_for_location. - diary.update_diary(): erfasst alten GPS-Stand, reichert nach DB-Commit an (async), schreibt nur erfolgreich geholte Felder (kein Datenverlust bei API-Fehler), identische Koordinaten -> kein erneuter Abruf. - Tests: tests/test_diary_location_enrich.py (Anreicherung, kein GPS=kein Abruf, Resave ohne Re-Fetch).
This commit is contained in:
parent
e2219fb8ba
commit
140140f690
8 changed files with 244 additions and 17 deletions
|
|
@ -164,6 +164,88 @@ async def get_weather_for_location(lat: float, lon: float) -> dict:
|
|||
_location_cache[key] = (now, data)
|
||||
return data
|
||||
|
||||
|
||||
async def get_weather_for_date(lat: float, lon: float, datum: str,
|
||||
hour: int | None = None) -> dict:
|
||||
"""Wetter für ein bestimmtes Datum (YYYY-MM-DD) an einem Standort.
|
||||
|
||||
Für nachträgliches Positionieren alter Tagebucheinträge: heutiges Datum
|
||||
→ aktuelles Wetter; vergangene Tage → stündliche Historie. Open-Meteo
|
||||
Forecast-API deckt die jüngsten ~90 Tage Vergangenheit ab, ältere Tage
|
||||
holt die Archive-API. `hour` (0–23, Default 12) wählt die Tagesstunde.
|
||||
Rückgabe hat dieselbe Struktur wie get_weather_for_location (die nach
|
||||
vorn gerichteten Felder next_rain_time/rain_warning_time sind None)."""
|
||||
from datetime import date as _date
|
||||
try:
|
||||
target = _date.fromisoformat(datum[:10])
|
||||
except (ValueError, TypeError):
|
||||
return await get_weather_for_location(lat, lon)
|
||||
|
||||
today = datetime.now().date()
|
||||
if target >= today:
|
||||
# Heute/Zukunft → aktuelles Wetter (inkl. Vorhersage-Felder + Ortsname).
|
||||
return await get_weather_for_location(lat, lon)
|
||||
|
||||
hour = 12 if hour is None else max(0, min(23, hour))
|
||||
age_days = (today - target).days
|
||||
base = ("https://archive-api.open-meteo.com/v1/archive"
|
||||
if age_days > 90 else
|
||||
"https://api.open-meteo.com/v1/forecast")
|
||||
url = (
|
||||
f"{base}?latitude={lat}&longitude={lon}"
|
||||
f"&start_date={target.isoformat()}&end_date={target.isoformat()}"
|
||||
"&hourly=temperature_2m,apparent_temperature,weathercode,windspeed_10m,is_day"
|
||||
"&daily=precipitation_probability_max,uv_index_max"
|
||||
"&timezone=Europe%2FBerlin"
|
||||
)
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
resp = await client.get(url)
|
||||
resp.raise_for_status()
|
||||
raw = resp.json()
|
||||
hourly = raw.get('hourly', {})
|
||||
daily = raw.get('daily', {})
|
||||
|
||||
def _at(arr):
|
||||
try:
|
||||
return arr[hour]
|
||||
except (IndexError, TypeError, KeyError):
|
||||
return None
|
||||
|
||||
temp = _at(hourly.get('temperature_2m', []))
|
||||
feels_like = _at(hourly.get('apparent_temperature', []))
|
||||
wcode = _at(hourly.get('weathercode', [])) or 0
|
||||
wind = _at(hourly.get('windspeed_10m', []))
|
||||
is_day_v = _at(hourly.get('is_day', []))
|
||||
is_day = 1 if is_day_v is None else is_day_v
|
||||
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'
|
||||
|
||||
zecken = None
|
||||
if temp is not None and temp > 7.0 and 3 <= target.month <= 10:
|
||||
zecken = 'hoch' if temp > 20 else ('mittel' if temp > 12 else 'niedrig')
|
||||
|
||||
return {
|
||||
'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,
|
||||
'next_rain_time': None,
|
||||
'rain_warning_time': None,
|
||||
'location_name': None,
|
||||
'historical': True,
|
||||
}
|
||||
|
||||
|
||||
# WMO-Wettercodes 95–99 = Gewitter
|
||||
THUNDERSTORM_CODES = {95, 96, 99}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue