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:
rene 2026-06-18 21:13:47 +02:00
parent e2219fb8ba
commit 140140f690
8 changed files with 244 additions and 17 deletions

View file

@ -591,6 +591,13 @@ async def update_diary(dog_id: int, entry_id: int, data: DiaryUpdate,
if not exists:
raise HTTPException(404, "Eintrag nicht gefunden.")
# GPS-Stand VOR dem Update merken (für Anreicherungs-Entscheidung unten)
old = conn.execute(
"SELECT gps_lat, gps_lon FROM diary WHERE id=?", (entry_id,)
).fetchone()
old_lat = old["gps_lat"] if old else None
old_lon = old["gps_lon"] if old else None
# Felder updaten — location_name/gps_* dürfen explizit auf None gesetzt werden
raw = data.model_dump(exclude={"dog_ids"})
NULLABLE = {"location_name", "gps_lat", "gps_lon"}
@ -617,6 +624,49 @@ async def update_diary(dog_id: int, entry_id: int, data: DiaryUpdate,
dogs_map = _fetch_dog_ids(conn, [entry_id])
media_map = _fetch_media_items(conn, [entry_id])
# Nachträgliche Positionierung: wurde GPS neu gesetzt oder geändert, POIs
# (immer) + Wetter fürs Eintragsdatum (historisch korrekt) nachladen —
# analog zum Create-Pfad, aber NACH dem DB-Commit (async HTTP).
new_lat, new_lon = row["gps_lat"], row["gps_lon"]
coords_changed = (
old_lat is None or old_lon is None
or round(old_lat, 5) != round(new_lat, 5)
or round(old_lon, 5) != round(new_lon, 5)
) if (new_lat is not None and new_lon is not None) else False
if coords_changed:
weather_json = None
poi_json = None
# Stunde fürs historische Wetter aus created_at, falls selber Tag wie datum.
hour = None
ca = row["created_at"]
if ca and len(ca) >= 13 and ca[:10] == (row["datum"] or "")[:10]:
try: hour = int(ca[11:13])
except (ValueError, TypeError): hour = None
try:
wd = await weather_mod.get_weather_for_date(new_lat, new_lon, row["datum"], hour)
weather_json = json.dumps(wd)
except Exception as exc:
logger.warning("Wetter-Anreicherung beim Diary-Update fehlgeschlagen: %s", exc)
try:
pois = await _fetch_pois_for_coords(new_lat, new_lon, limit=5)
if pois:
poi_json = json.dumps(pois)
except Exception as exc:
logger.warning("POI-Anreicherung beim Diary-Update fehlgeschlagen: %s", exc)
# Nur erfolgreich geholte Felder schreiben — ein API-Fehler überschreibt
# vorhandene Daten nicht (kein Datenverlust).
sets, vals = [], []
if weather_json is not None:
sets.append("weather_json=?"); vals.append(weather_json)
if poi_json is not None:
sets.append("poi_json=?"); vals.append(poi_json)
if sets:
with db() as conn:
conn.execute(f"UPDATE diary SET {', '.join(sets)} WHERE id=?", vals + [entry_id])
row = conn.execute("SELECT * FROM diary WHERE id=?", (entry_id,)).fetchone()
return _entry_dict(row, dogs_map, media_map)