Session 2026-04-22: Training, Fixes, KI-Cloud, Dark-Mode
Training-System: - Einheit-Dialog Bugs behoben (UI.toast callable, _dogId via _appState, activeDog.id) - Virtueller Trainer (rein statistisch): üben/festigen/entdecken/levelup Empfehlungen auf Basis exercise_progress + sessions, Prognose bis 80% - Stand erfassen Modal: alle Übungen auf einmal setzen (onboarding) - Erfolgsindikatoren auf Karten: Ø-Quote + Trend-Pfeil + Anzahl Sessions - exercise_progress → synthetische Stats im Trainer (ohne Sessions nutzbar) - Levelup: Tricks empfehlen wenn ≥4 Grundkommandos sitzen - Kommandos & Fähigkeiten im Hundeprofil + öffentlichem Profil - 2 neue Problemverhalten-Übungen: Bellen/Kläffen, Enttriggern Mobile/UI-Fixes: - Übungskarten: Name + Difficulty oben, Buttons eigene Zeile (kein Umbruch) - Trainingsgrundlagen: Padding in allen Karten, Hinweis-Boxen Dark-Mode-sicher - Tab-Sichtbarkeit: Trainer/Suggestions nur auf Übungs-Tabs - Tagebuch FAB (Neu-Eintrag Button) + Quick-Add Eintrag - FAB Abstand fix (nav-bottom-height + safe-bottom) - Suggestion-Karten rgba (Dark-Mode) - routes.js + uebungen.js: alle Hellfarben → rgba (Dark-Mode-sicher) - ui.js: UI.toast als callable Function-Object (war nur plain Object) KI & Backend: - KI_MODE=cloud + ANTHROPIC_API_KEY gesetzt - ki.py: Cloud-Fallback wenn local nicht erreichbar + KI_MODE=cloud - KI-Trainer Tageslimit 10 Anfragen/User + ki_daily_calls Tabelle - Admin-Panel: KI-Nutzung (heute/Monat/User) - Status-Report Fix (lost-Tabelle) → 06:00 + 18:00 täglich - Wiki-Anreicherung läuft jetzt (50 Rassen Startup, 20/Nacht) - landing.html: Trainings-Features in JSON-LD + Feature-Karten
This commit is contained in:
parent
2b442ebd98
commit
44081a6b9d
16 changed files with 938 additions and 117 deletions
|
|
@ -218,20 +218,80 @@ async def delete_photo(dog_id: int, user=Depends(get_current_user)):
|
|||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Fähigkeiten / Kommandos (für Profil + öffentliche Seite)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _parse_exercise_name(exercise_id: str) -> str:
|
||||
"""grundkommandos_Hier__Komm → 'Hier / Komm'"""
|
||||
parts = exercise_id.split("_", 1)
|
||||
if len(parts) < 2:
|
||||
return exercise_id
|
||||
return parts[1].replace("__", " / ").replace("_", " ")
|
||||
|
||||
|
||||
def _load_skills(conn, dog_id: int, user_id: int) -> list:
|
||||
"""Gibt Übungen mit Status 'sitzt' oder 'meistens' zurück, die mit diesem Hund trainiert wurden."""
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT ep.exercise_id, ep.status,
|
||||
(SELECT ts.exercise_name FROM training_sessions ts
|
||||
WHERE ts.user_id = ep.user_id AND ts.dog_id = ?
|
||||
AND ts.exercise_id = ep.exercise_id
|
||||
ORDER BY ts.datum DESC, ts.created_at DESC LIMIT 1) AS exercise_name
|
||||
FROM exercise_progress ep
|
||||
WHERE ep.user_id = ?
|
||||
AND ep.status IN ('sitzt', 'meistens')
|
||||
AND EXISTS (SELECT 1 FROM training_sessions ts2
|
||||
WHERE ts2.user_id = ep.user_id AND ts2.dog_id = ?
|
||||
AND ts2.exercise_id = ep.exercise_id)
|
||||
ORDER BY ep.status DESC, ep.exercise_id
|
||||
""",
|
||||
(dog_id, user_id, dog_id)
|
||||
).fetchall()
|
||||
|
||||
return [
|
||||
{
|
||||
"exercise_id": r["exercise_id"],
|
||||
"exercise_name": r["exercise_name"] or _parse_exercise_name(r["exercise_id"]),
|
||||
"status": r["status"],
|
||||
"tab": r["exercise_id"].split("_")[0],
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
|
||||
|
||||
@router.get("/{dog_id}/skills")
|
||||
async def get_dog_skills(dog_id: int, user=Depends(get_current_user)):
|
||||
uid = user["id"]
|
||||
with db() as conn:
|
||||
dog = conn.execute(
|
||||
"SELECT id, user_id FROM dogs WHERE id=? AND (user_id=? OR id IN (SELECT dog_id FROM sitting_access WHERE friend_id=? AND expires_at > datetime('now')))",
|
||||
(dog_id, uid, uid)
|
||||
).fetchone()
|
||||
if not dog:
|
||||
raise HTTPException(404, "Hund nicht gefunden.")
|
||||
return _load_skills(conn, dog_id, dog["user_id"])
|
||||
|
||||
|
||||
# Öffentliches Profil (für NFC-Tag, kein Login nötig)
|
||||
@router.get("/public/{dog_id}")
|
||||
async def public_dog_profile(dog_id: int):
|
||||
with db() as conn:
|
||||
dog = conn.execute(
|
||||
"""SELECT d.id, d.name, d.rasse, d.geburtstag, d.foto_url, d.bio,
|
||||
u.name as besitzer_name
|
||||
d.user_id, u.name as besitzer_name
|
||||
FROM dogs d JOIN users u ON d.user_id=u.id
|
||||
WHERE d.id=? AND d.is_public=1""",
|
||||
(dog_id,)
|
||||
).fetchone()
|
||||
if not dog:
|
||||
raise HTTPException(404, "Profil nicht gefunden oder nicht öffentlich.")
|
||||
return dict(dog)
|
||||
if not dog:
|
||||
raise HTTPException(404, "Profil nicht gefunden oder nicht öffentlich.")
|
||||
skills = _load_skills(conn, dog_id, dog["user_id"])
|
||||
result = dict(dog)
|
||||
result.pop("user_id", None)
|
||||
result["skills"] = skills
|
||||
return result
|
||||
|
||||
|
||||
class FoundReport(BaseModel):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue