Feature: Trauer-Feature, Futter-Verträglichkeit, Multi-Hund-Fixes, Wetter-Ort (Sprint 47)

- dog-profile.js: Verstorben-Button, Gedenkseite, KI-Abschiedstext
- database.py: futter_eintraege/reaktionen, route_dogs, exercise_progress.dog_id
- routes/ernaehrung.py: Futter-Verträglichkeit mit 20 Reaktionstypen + Analyse
- routes/routen.py: route_dogs Many-to-Many, Routen editierbar
- routes/training.py: exercise_progress per dog_id
- routes/ki.py: /ki/abschied Trauer-KI
- weather.py: Nominatim Ortsname parallel geladen
- ui.js: dogChip/bindDogChip, visualViewport-Modal
- api.js: gedenken, gedenkseite, futter-Methoden, route_dogs
- worlds.js: Ortsname im Wetter-Chip
- uebungen.js: _progressLoaded-Flag, dog-spezifischer Fortschritt
- trainingsplaene.js: dog_id Unterstützung
- diary.js/health.js: P-Badge Cleanup
- map.js: Wetter-Ort-Anzeige entfernt
- wetter.js: Ort in Wetter-Detail
This commit is contained in:
rene 2026-05-11 19:28:38 +02:00
parent 1ce802c8dc
commit bda61a0e40
16 changed files with 713 additions and 181 deletions

View file

@ -361,3 +361,65 @@ Falls kein Hund erkennbar: ist_hund=false und leeres rassen-Array."""
"hinweis": parsed.get("hinweis") or None,
"verbleibende_anfragen": remaining_after,
}
# ------------------------------------------------------------------
# POST /ki/abschied — Persönlicher Abschiedstext für verstorbenen Hund
# ------------------------------------------------------------------
class AbschiedRequest(BaseModel):
dog_id: int
name: str
rasse: Optional[str] = None
km_total: Optional[float] = None
diary_count: Optional[int] = None
gemeinsam_tage: Optional[int] = None
last_entry_titel: Optional[str] = None
@router.post("/abschied")
async def ki_abschied(req: AbschiedRequest, request: Request,
user=Depends(get_current_user)):
"""Persönlicher Abschiedstext — einmalig generiert, DB-gecacht."""
with db() as conn:
cached = conn.execute(
"SELECT content FROM bday_ki_cache WHERE dog_id=? AND year=9999 AND mode='abschied'",
(req.dog_id,)
).fetchone()
if cached:
return {"text": cached["content"], "cached": True}
name = req.name.strip()[:40]
rasse = req.rasse or ""
km = f"{req.km_total:.0f} km" if req.km_total else None
tage = f"{req.gemeinsam_tage} gemeinsame Tage" if req.gemeinsam_tage else None
eintr = f"{req.diary_count} Tagebucheinträge" if req.diary_count else None
stats_str = ", ".join(filter(None, [km, tage, eintr]))
rasse_str = f" ({rasse})" if rasse else ""
system = (
"Du bist ein einfühlsamer Begleiter für Menschen in Trauer um ihren Hund. "
"Schreibe warmherzig, persönlich und respektvoll auf Deutsch. "
"Keine Floskeln, kein Kitsch — echte Wärme. "
"Erwähne die Statistiken natürlich eingebunden."
)
prompt = (
f"{name}{rasse_str} ist über die Regenbogenbrücke gegangen. "
f"Schreibe einen kurzen, persönlichen Abschiedstext (ca. 80100 Wörter) "
f"der die Verbundenheit würdigt. "
f"Statistiken: {stats_str or 'nicht bekannt'}. "
f"Sei warm, nicht sentimental überladen. Schließe mit einem hoffnungsvollen Gedanken."
)
try:
text = await ki_module.complete(
system=system, prompt=prompt, max_tokens=300,
requires_premium=False, user_id=user["id"],
)
with db() as conn:
conn.execute(
"INSERT OR REPLACE INTO bday_ki_cache (dog_id, year, mode, content) VALUES (?,9999,'abschied',?)",
(req.dog_id, text)
)
return {"text": text, "cached": False}
except Exception as e:
raise HTTPException(503, str(e))