Fix: Ernährung Hund-spezifisch, Erinnerungen in Settings, Übung des Tages per Hund (SW by-v872)
- ernaehrung.js: onDogChange setzt activeTab zurück, Hund klar sichtbar
- settings.js: Erinnerungen-Sektion lädt verstorbene Hunde + öffnet Gedenkseite
- dogs.py: GET /dogs/verstorben Endpoint (korrekte Route-Reihenfolge vor /{dog_id})
- dogs.py: Übung des Tages filtert jetzt nach dog_id statt user_id (sitzt-Übungen korrekt ausgeschlossen)
- Routen zeigen verstorbene Hunde korrekt als Teilnehmer (route_dogs ohne verstorben-Filter)
This commit is contained in:
parent
265d3d4fe2
commit
1ce802c8dc
8 changed files with 1106 additions and 28 deletions
|
|
@ -41,7 +41,7 @@ class DogUpdate(BaseModel):
|
|||
async def list_dogs(user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
own = conn.execute(
|
||||
"SELECT *, NULL AS shared_by, NULL AS share_role FROM dogs WHERE user_id=? ORDER BY id",
|
||||
"SELECT *, NULL AS shared_by, NULL AS share_role FROM dogs WHERE user_id=? AND (verstorben_am IS NULL) ORDER BY id",
|
||||
(user["id"],)
|
||||
).fetchall()
|
||||
shared = conn.execute(
|
||||
|
|
@ -255,15 +255,16 @@ async def get_welcome_dashboard(dog_id: int, user=Depends(get_current_user)):
|
|||
day_num = (_dt.date.today() - _dt.date(2024, 1, 1)).days
|
||||
|
||||
# Versuche JOIN (funktioniert wenn js_exercise_id-Spalte vorhanden)
|
||||
# Nur Übungen des aktiven Hundes, 'sitzt' ausschließen
|
||||
try:
|
||||
joined = conn.execute(
|
||||
"""SELECT ep.exercise_id, te.name, te.kategorie AS kategorie_raw,
|
||||
te.schwierigkeit, te.js_exercise_id
|
||||
FROM exercise_progress ep
|
||||
JOIN training_exercises te ON te.js_exercise_id = ep.exercise_id
|
||||
WHERE ep.user_id = ? AND ep.status IN ('noch-nicht', 'manchmal', 'meistens')
|
||||
WHERE ep.dog_id = ? AND ep.status IN ('noch-nicht', 'manchmal', 'meistens')
|
||||
ORDER BY ep.updated_at ASC LIMIT 50""",
|
||||
(user["id"],)
|
||||
(dog_id,)
|
||||
).fetchall()
|
||||
except Exception:
|
||||
joined = []
|
||||
|
|
@ -288,9 +289,9 @@ async def get_welcome_dashboard(dog_id: int, user=Depends(get_current_user)):
|
|||
)
|
||||
raw = conn.execute(
|
||||
"""SELECT exercise_id FROM exercise_progress
|
||||
WHERE user_id = ? AND status IN ('noch-nicht', 'manchmal', 'meistens')
|
||||
WHERE dog_id = ? AND status IN ('noch-nicht', 'manchmal', 'meistens')
|
||||
ORDER BY updated_at ASC LIMIT 50""",
|
||||
(user["id"],)
|
||||
(dog_id,)
|
||||
).fetchall()
|
||||
valid = [r["exercise_id"] for r in raw
|
||||
if any(r["exercise_id"].startswith(p) for p in _KNOWN_PREFIXES)]
|
||||
|
|
@ -779,6 +780,21 @@ async def get_hunde_buch(
|
|||
return HTMLResponse(content=html_page)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/dogs/verstorben — Alle verstorbenen Hunde des Users
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/verstorben")
|
||||
async def get_verstorbene_hunde(user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
rows = conn.execute(
|
||||
"""SELECT id, name, rasse, foto_url, verstorben_am, geburtstag
|
||||
FROM dogs WHERE user_id=? AND verstorben_am IS NOT NULL
|
||||
ORDER BY verstorben_am DESC""",
|
||||
(user["id"],)
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/{dog_id}")
|
||||
async def get_dog(dog_id: int, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
|
|
@ -1208,14 +1224,15 @@ async def get_dog_timeline(dog_id: int, user=Depends(get_current_user)):
|
|||
"ref_id": r["id"],
|
||||
})
|
||||
|
||||
# --- Routen ---
|
||||
# --- Routen (nur Routen wo dieser Hund mitgegangen ist) ---
|
||||
route_rows = conn.execute(
|
||||
"""SELECT id, name, distanz_km,
|
||||
date(created_at) AS datum
|
||||
FROM routes
|
||||
WHERE user_id=?
|
||||
ORDER BY created_at ASC""",
|
||||
(user["id"],)
|
||||
"""SELECT r.id, r.name, r.distanz_km,
|
||||
date(r.created_at) AS datum
|
||||
FROM routes r
|
||||
JOIN route_dogs rd ON rd.route_id = r.id AND rd.dog_id = ?
|
||||
WHERE r.user_id = ?
|
||||
ORDER BY r.created_at ASC""",
|
||||
(dog_id, user["id"])
|
||||
).fetchall()
|
||||
|
||||
route_first = True
|
||||
|
|
@ -1263,3 +1280,108 @@ async def get_dog_timeline(dog_id: int, user=Depends(get_current_user)):
|
|||
"geburtstag": dog["geburtstag"],
|
||||
"events": events,
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /api/dogs/{id}/gedenken — Hund als verstorben markieren
|
||||
# ------------------------------------------------------------------
|
||||
class GedenkenData(BaseModel):
|
||||
verstorben_am: str # YYYY-MM-DD
|
||||
|
||||
@router.post("/{dog_id}/gedenken")
|
||||
async def mark_verstorben(dog_id: int, data: GedenkenData, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
updated = conn.execute(
|
||||
"UPDATE dogs SET verstorben_am=? WHERE id=? AND user_id=?",
|
||||
(data.verstorben_am, dog_id, user["id"])
|
||||
).rowcount
|
||||
if not updated:
|
||||
raise HTTPException(404, "Hund nicht gefunden.")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/dogs/{id}/gedenkseite — Memorial-Daten
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/{dog_id}/gedenkseite")
|
||||
async def get_gedenkseite(dog_id: int, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
dog = conn.execute(
|
||||
"SELECT * FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])
|
||||
).fetchone()
|
||||
if not dog:
|
||||
raise HTTPException(404)
|
||||
dog = dict(dog)
|
||||
|
||||
# Statistiken
|
||||
km_total = conn.execute(
|
||||
"SELECT COALESCE(ROUND(SUM(distanz_km),1),0) AS km FROM routes r "
|
||||
"JOIN route_dogs rd ON rd.route_id=r.id WHERE rd.dog_id=?", (dog_id,)
|
||||
).fetchone()["km"]
|
||||
|
||||
diary_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM diary WHERE dog_id=?", (dog_id,)
|
||||
).fetchone()[0]
|
||||
|
||||
media_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM diary_media dm JOIN diary d ON d.id=dm.diary_id "
|
||||
"WHERE d.dog_id=? AND dm.media_type='image'", (dog_id,)
|
||||
).fetchone()[0]
|
||||
|
||||
training_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM training_sessions WHERE dog_id=?", (dog_id,)
|
||||
).fetchone()[0]
|
||||
|
||||
# Letzter Tagebucheintrag
|
||||
last_entry = conn.execute(
|
||||
"SELECT titel, datum FROM diary WHERE dog_id=? ORDER BY datum DESC LIMIT 1",
|
||||
(dog_id,)
|
||||
).fetchone()
|
||||
|
||||
# Erste und letzte Aufnahme
|
||||
first_entry = conn.execute(
|
||||
"SELECT datum FROM diary WHERE dog_id=? ORDER BY datum ASC LIMIT 1",
|
||||
(dog_id,)
|
||||
).fetchone()
|
||||
|
||||
# Letzte 6 Fotos für Galerie
|
||||
photos = conn.execute(
|
||||
"""SELECT dm.url FROM diary_media dm
|
||||
JOIN diary d ON d.id=dm.diary_id
|
||||
WHERE d.dog_id=? AND dm.media_type='image'
|
||||
AND dm.img_width IS NOT NULL AND dm.img_width > dm.img_height
|
||||
ORDER BY d.datum DESC, dm.id DESC LIMIT 6""",
|
||||
(dog_id,)
|
||||
).fetchall()
|
||||
if not photos:
|
||||
photos = conn.execute(
|
||||
"""SELECT dm.url FROM diary_media dm
|
||||
JOIN diary d ON d.id=dm.diary_id
|
||||
WHERE d.dog_id=? AND dm.media_type='image'
|
||||
ORDER BY d.datum DESC, dm.id DESC LIMIT 6""",
|
||||
(dog_id,)
|
||||
).fetchall()
|
||||
|
||||
# Gemeinsame Zeit berechnen
|
||||
joined = dog.get("geburtstag") or (first_entry["datum"] if first_entry else None)
|
||||
passed = dog.get("verstorben_am")
|
||||
gemeinsam_tage = None
|
||||
if joined and passed:
|
||||
try:
|
||||
from datetime import date as _date
|
||||
d1 = _date.fromisoformat(joined)
|
||||
d2 = _date.fromisoformat(passed)
|
||||
gemeinsam_tage = (d2 - d1).days
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"dog": dog,
|
||||
"km_total": km_total,
|
||||
"diary_count": diary_count,
|
||||
"media_count": media_count,
|
||||
"training_count": training_count,
|
||||
"last_entry": dict(last_entry) if last_entry else None,
|
||||
"gemeinsam_tage": gemeinsam_tage,
|
||||
"photos": [r["url"] for r in photos],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,3 +143,305 @@ async def ki_ernaehrung(dog_id: int, body: KiBeratungRequest,
|
|||
raise HTTPException(503, str(e))
|
||||
except Exception:
|
||||
raise HTTPException(500, "KI momentan nicht verfügbar.")
|
||||
|
||||
|
||||
# ==================================================================
|
||||
# FUTTER-VERTRÄGLICHKEIT
|
||||
# ==================================================================
|
||||
|
||||
REAKTION_TYPEN = {
|
||||
# Positiv
|
||||
"verdauung_gut": {"label": "Gute Verdauung", "kategorie": "positiv", "fenster_h": 8},
|
||||
"energie_hoch": {"label": "Viel Energie", "kategorie": "positiv", "fenster_h": 12},
|
||||
"fell_glaenzend": {"label": "Glänzendes Fell", "kategorie": "positiv", "fenster_h": 336}, # 2 Wochen
|
||||
# Gastrointestinal
|
||||
"erbrechen": {"label": "Erbrechen", "kategorie": "gastro_negativ", "fenster_h": 6},
|
||||
"durchfall": {"label": "Durchfall", "kategorie": "gastro_negativ", "fenster_h": 8},
|
||||
"blaehungen": {"label": "Blähungen", "kategorie": "gastro_negativ", "fenster_h": 6},
|
||||
"weicher_stuhl": {"label": "Weicher Stuhl", "kategorie": "gastro_negativ", "fenster_h": 8},
|
||||
"appetitlosigkeit":{"label": "Appetitlosigkeit", "kategorie": "gastro_negativ", "fenster_h": 12},
|
||||
# Haut & Fell
|
||||
"juckreiz": {"label": "Juckreiz / Kratzen", "kategorie": "haut_negativ", "fenster_h": 72},
|
||||
"haarausfall": {"label": "Haarausfall", "kategorie": "haut_negativ", "fenster_h": 336},
|
||||
"stumpfes_fell": {"label": "Stumpfes Fell", "kategorie": "haut_negativ", "fenster_h": 336},
|
||||
"schuppenbildung":{"label": "Schuppenbildung", "kategorie": "haut_negativ", "fenster_h": 168},
|
||||
"roetungen": {"label": "Hautrötungen / Entzündung", "kategorie": "haut_negativ", "fenster_h": 72},
|
||||
"pfotenlecken": {"label": "Pfoten lecken (chronisch)", "kategorie": "haut_negativ", "fenster_h": 168},
|
||||
"ohrentzuendung": {"label": "Ohrentzündung", "kategorie": "haut_negativ", "fenster_h": 168},
|
||||
"fettiges_fell": {"label": "Fettiges Fell / Seborrhö", "kategorie": "haut_negativ", "fenster_h": 336},
|
||||
# Allgemeinbefinden
|
||||
"schlappheit": {"label": "Schlappheit / Apathie", "kategorie": "allgemein_negativ", "fenster_h": 12},
|
||||
"nervositaet": {"label": "Nervosität / Unruhe", "kategorie": "allgemein_negativ", "fenster_h": 12},
|
||||
"viel_trinken": {"label": "Ungewöhnlich viel trinken", "kategorie": "allgemein_negativ", "fenster_h": 24},
|
||||
"sonstiges": {"label": "Sonstiges", "kategorie": "sonstiges", "fenster_h": 24},
|
||||
}
|
||||
|
||||
_POSITIV_KAT = {"positiv"}
|
||||
_NEGATIV_KAT = {"gastro_negativ", "haut_negativ", "allgemein_negativ"}
|
||||
_HAUT_HINWEIS = "Haut- & Fell-Symptome wie {label} entwickeln sich typischerweise über Wochen. Mindestens 4–6 Wochen Beobachtung empfohlen — auch nach einem Futterwechsel dauert eine Besserung 2–6 Wochen."
|
||||
_GASTRO_HINWEIS = "Magen-Darm-Symptome wie {label} treten meist innerhalb weniger Stunden auf. Wenn sie häufig wiederkehren, ist ein Tierarztbesuch empfohlen."
|
||||
|
||||
|
||||
class FutterEintragCreate(BaseModel):
|
||||
datum: str
|
||||
uhrzeit: str
|
||||
futter_name: str
|
||||
futter_typ: Optional[str] = "trockenfutter"
|
||||
menge_g: Optional[int] = None
|
||||
notiz: Optional[str] = None
|
||||
|
||||
|
||||
class ReaktionCreate(BaseModel):
|
||||
datum: str
|
||||
uhrzeit: str
|
||||
reaktion_typ: str
|
||||
intensitaet: Optional[int] = 3
|
||||
notiz: Optional[str] = None
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /dogs/{dog_id}/futter
|
||||
# ------------------------------------------------------------------
|
||||
@router.post("/{dog_id}/futter")
|
||||
async def create_futter_eintrag(dog_id: int, body: FutterEintragCreate,
|
||||
user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_check_dog_access(conn, dog_id, user["id"])
|
||||
cur = conn.execute("""
|
||||
INSERT INTO futter_eintraege
|
||||
(dog_id, datum, uhrzeit, futter_name, futter_typ, menge_g, notiz)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (dog_id, body.datum, body.uhrzeit, body.futter_name,
|
||||
body.futter_typ or "trockenfutter", body.menge_g, body.notiz))
|
||||
row = conn.execute(
|
||||
"SELECT * FROM futter_eintraege WHERE id=?", (cur.lastrowid,)
|
||||
).fetchone()
|
||||
return dict(row)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /dogs/{dog_id}/futter
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/{dog_id}/futter")
|
||||
async def list_futter_eintraege(dog_id: int, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_check_dog_access(conn, dog_id, user["id"])
|
||||
rows = conn.execute("""
|
||||
SELECT * FROM futter_eintraege
|
||||
WHERE dog_id=?
|
||||
ORDER BY datum DESC, uhrzeit DESC
|
||||
LIMIT 50
|
||||
""", (dog_id,)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# DELETE /dogs/{dog_id}/futter/{entry_id}
|
||||
# ------------------------------------------------------------------
|
||||
@router.delete("/{dog_id}/futter/{entry_id}")
|
||||
async def delete_futter_eintrag(dog_id: int, entry_id: int,
|
||||
user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_check_dog_access(conn, dog_id, user["id"])
|
||||
result = conn.execute(
|
||||
"DELETE FROM futter_eintraege WHERE id=? AND dog_id=?",
|
||||
(entry_id, dog_id)
|
||||
)
|
||||
if result.rowcount == 0:
|
||||
raise HTTPException(404, "Eintrag nicht gefunden.")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /dogs/{dog_id}/futter/reaktion
|
||||
# ------------------------------------------------------------------
|
||||
@router.post("/{dog_id}/futter/reaktion")
|
||||
async def create_reaktion(dog_id: int, body: ReaktionCreate,
|
||||
user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_check_dog_access(conn, dog_id, user["id"])
|
||||
cur = conn.execute("""
|
||||
INSERT INTO futter_reaktionen
|
||||
(dog_id, datum, uhrzeit, reaktion_typ, intensitaet, notiz)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""", (dog_id, body.datum, body.uhrzeit, body.reaktion_typ,
|
||||
body.intensitaet or 3, body.notiz))
|
||||
row = conn.execute(
|
||||
"SELECT * FROM futter_reaktionen WHERE id=?", (cur.lastrowid,)
|
||||
).fetchone()
|
||||
return dict(row)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /dogs/{dog_id}/futter/reaktionen
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/{dog_id}/futter/reaktionen")
|
||||
async def list_reaktionen(dog_id: int, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_check_dog_access(conn, dog_id, user["id"])
|
||||
rows = conn.execute("""
|
||||
SELECT * FROM futter_reaktionen
|
||||
WHERE dog_id=?
|
||||
ORDER BY datum DESC, uhrzeit DESC
|
||||
LIMIT 50
|
||||
""", (dog_id,)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# DELETE /dogs/{dog_id}/futter/reaktion/{react_id}
|
||||
# ------------------------------------------------------------------
|
||||
@router.delete("/{dog_id}/futter/reaktion/{react_id}")
|
||||
async def delete_reaktion(dog_id: int, react_id: int,
|
||||
user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_check_dog_access(conn, dog_id, user["id"])
|
||||
result = conn.execute(
|
||||
"DELETE FROM futter_reaktionen WHERE id=? AND dog_id=?",
|
||||
(react_id, dog_id)
|
||||
)
|
||||
if result.rowcount == 0:
|
||||
raise HTTPException(404, "Reaktion nicht gefunden.")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /dogs/{dog_id}/futter/analyse
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/{dog_id}/futter/analyse")
|
||||
async def futter_analyse(dog_id: int, user=Depends(get_current_user)):
|
||||
from datetime import datetime
|
||||
|
||||
with db() as conn:
|
||||
_check_dog_access(conn, dog_id, user["id"])
|
||||
|
||||
eintraege = conn.execute(
|
||||
"SELECT * FROM futter_eintraege WHERE dog_id=? ORDER BY datum, uhrzeit",
|
||||
(dog_id,)
|
||||
).fetchall()
|
||||
reaktionen = conn.execute(
|
||||
"SELECT * FROM futter_reaktionen WHERE dog_id=? ORDER BY datum, uhrzeit",
|
||||
(dog_id,)
|
||||
).fetchall()
|
||||
|
||||
def parse_ts(datum, uhrzeit):
|
||||
try:
|
||||
return datetime.fromisoformat(f"{datum}T{uhrzeit}")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
# futter_name → {typ, mahlzeiten, positiv, negativ, kategorien: {kat: count}}
|
||||
futter_stats: dict = {}
|
||||
|
||||
for e in eintraege:
|
||||
name = e["futter_name"]
|
||||
if name not in futter_stats:
|
||||
futter_stats[name] = {
|
||||
"name": name,
|
||||
"typ": e["futter_typ"],
|
||||
"mahlzeiten": 0,
|
||||
"positiv": 0,
|
||||
"negativ": 0,
|
||||
"kategorien": {},
|
||||
}
|
||||
futter_stats[name]["mahlzeiten"] += 1
|
||||
|
||||
for r in reaktionen:
|
||||
r_ts = parse_ts(r["datum"], r["uhrzeit"])
|
||||
if not r_ts:
|
||||
continue
|
||||
r_typ = r["reaktion_typ"]
|
||||
meta = REAKTION_TYPEN.get(r_typ, {"kategorie": "sonstiges", "fenster_h": 24})
|
||||
kat = meta["kategorie"]
|
||||
fenster = meta["fenster_h"]
|
||||
# Mindestfenster 1h, maximales Fenster wie angegeben
|
||||
min_h = 1
|
||||
|
||||
for e in eintraege:
|
||||
e_ts = parse_ts(e["datum"], e["uhrzeit"])
|
||||
if not e_ts:
|
||||
continue
|
||||
diff = (r_ts - e_ts).total_seconds() / 3600
|
||||
if min_h <= diff <= fenster:
|
||||
name = e["futter_name"]
|
||||
if name not in futter_stats:
|
||||
continue
|
||||
if kat in _POSITIV_KAT:
|
||||
futter_stats[name]["positiv"] += 1
|
||||
elif kat in _NEGATIV_KAT:
|
||||
futter_stats[name]["negativ"] += 1
|
||||
# Kategorie-Zähler
|
||||
futter_stats[name]["kategorien"][kat] = \
|
||||
futter_stats[name]["kategorien"].get(kat, 0) + 1
|
||||
|
||||
result_futter = []
|
||||
for stats in futter_stats.values():
|
||||
positiv = stats["positiv"]
|
||||
negativ = stats["negativ"]
|
||||
total = positiv + negativ
|
||||
if total == 0:
|
||||
score = 50
|
||||
status = "neu"
|
||||
else:
|
||||
raw = (positiv - negativ * 2) / max(1, total)
|
||||
# raw liegt zwischen -2 und 1 → normieren auf 0-100
|
||||
score = int(max(0, min(100, (raw + 2) / 3 * 100)))
|
||||
if score >= 60:
|
||||
status = "gut"
|
||||
elif score >= 30:
|
||||
status = "neutral"
|
||||
else:
|
||||
status = "problematisch"
|
||||
|
||||
result_futter.append({
|
||||
"name": stats["name"],
|
||||
"typ": stats["typ"],
|
||||
"mahlzeiten": stats["mahlzeiten"],
|
||||
"positiv": positiv,
|
||||
"negativ": negativ,
|
||||
"score": score,
|
||||
"status": status,
|
||||
"kategorien": stats["kategorien"],
|
||||
})
|
||||
|
||||
# Sortierung: problematisch → neutral → gut → neu, dann nach Score
|
||||
ORDER = {"problematisch": 0, "neutral": 1, "gut": 2, "neu": 3}
|
||||
result_futter.sort(key=lambda x: (ORDER.get(x["status"], 9), -x["score"]))
|
||||
|
||||
# Hinweis ableiten: erstes problematisches Futter mit Haut/Gastro-Symptomen
|
||||
hinweis = None
|
||||
for f in result_futter:
|
||||
if f["status"] != "problematisch":
|
||||
continue
|
||||
kats = f["kategorien"]
|
||||
if kats.get("haut_negativ", 0) > 0:
|
||||
# Häufigstes Haut-Symptom finden
|
||||
haut_rxn = [
|
||||
r["reaktion_typ"] for r in reaktionen
|
||||
if REAKTION_TYPEN.get(r["reaktion_typ"], {}).get("kategorie") == "haut_negativ"
|
||||
]
|
||||
label = REAKTION_TYPEN.get(haut_rxn[0], {}).get("label", "Haut-Symptome") if haut_rxn else "Haut-Symptome"
|
||||
hinweis = _HAUT_HINWEIS.format(label=label)
|
||||
break
|
||||
if kats.get("gastro_negativ", 0) > 0:
|
||||
gastro_rxn = [
|
||||
r["reaktion_typ"] for r in reaktionen
|
||||
if REAKTION_TYPEN.get(r["reaktion_typ"], {}).get("kategorie") == "gastro_negativ"
|
||||
]
|
||||
label = REAKTION_TYPEN.get(gastro_rxn[0], {}).get("label", "Magen-Darm-Symptome") if gastro_rxn else "Magen-Darm-Symptome"
|
||||
hinweis = _GASTRO_HINWEIS.format(label=label)
|
||||
break
|
||||
|
||||
# Kategorien-Übersicht über alle Reaktionen
|
||||
kategorien_gesamt: dict = {}
|
||||
for r in reaktionen:
|
||||
kat = REAKTION_TYPEN.get(r["reaktion_typ"], {}).get("kategorie", "sonstiges")
|
||||
kategorien_gesamt[kat] = kategorien_gesamt.get(kat, 0) + 1
|
||||
|
||||
return {
|
||||
"eintraege_count": len(eintraege),
|
||||
"reaktionen_count": len(reaktionen),
|
||||
"futter": result_futter,
|
||||
"kategorien": kategorien_gesamt,
|
||||
"hinweis": hinweis,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue