banyaro/backend/routes/social.py
rene 1ff66a7083 Sicherheit + Tests + A11y, SW by-v1118
PYDANTIC max_length (38 Routen, ~400 Field-Constraints):
Schützt vor DoS durch Riesen-Payloads (10MB Thread-Titel etc.).
Pragmatische Limits:
- Titel/Name: 200 · Beschreibung/Body: 10000 · Notiz: 5000
- Email: 254 (RFC 5321) · URL: 500 · Slug/Kategorie: 100
- Hund-Name/Rasse: 80 · Hund-Bio: 2000

Top-betroffen: forum.py, diary.py, health.py, dogs.py, expenses.py,
notes.py, auth.py, profile.py. Manuelle len()-Checks in profile,
chat, ki entfernt (jetzt durch Field abgedeckt).

PYTEST COVERAGE (+19 Tests, 37 grün + 1 xfail):
- test_security.py: require_owner (Places GET/PATCH/DELETE mit
  Fremduser → 403), JWT-Blacklist (Logout invalidiert Token),
  Login-Lockout (5 Fehlversuche → 429 + Retry-After Header)
- test_race.py: Invoice-Counter (20 parallele Threads, alle unique),
  Founder-Number (atomare Vergabe, voll bei 100)
- test_validation.py: Forum-Titel 30k Zeichen → 422, Diary-Text
  50k → 422 (verifiziert Pydantic max_length-Sweep)

A11Y (Tap-Targets ≥44×44 + Dark-Mode-Kontrast):
- #header-user-btn 36→44px, .header-back 40→44, .header-menu-btn 40→44
- dog-profile Wrapped-Slider Prev/Next 40→44
- forum-Lightbox Close 40→44
- --c-text-muted Light: #B0A090 (2.37:1 FAIL) → #7F6B58 (4.74:1 PASS)
- --c-text-muted Dark:  #806A58 (3.58:1 FAIL) → #A08878 (5.46:1 PASS)
- Branding-Farben unangetastet
2026-05-27 13:40:30 +02:00

1688 lines
118 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
BAN YARO — Social Media Manager
KI-gestützte Content-Erstellung für TikTok & Instagram.
"""
import json
import logging
import random
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from pydantic import BaseModel, Field
from auth import get_current_user, require_social_media
from database import db
# ------------------------------------------------------------------
# Übungs-Bibliothek (gespiegelt aus uebungen.js)
# ------------------------------------------------------------------
_UEBUNGEN = [
# ── GRUNDKOMMANDOS ──────────────────────────────────────────────
{"id": "gk_sitz", "name": "Sitz", "kat": "Grundkommando", "schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "35 Min",
"beschreibung": "Das erste Kommando — Basis für alles weitere.",
"schritte": ["Leckerli vor die Nase, langsam nach oben/hinten führen", "Hinterteil senkt sich → Markerwort + Leckerli", "Wort 'Sitz' erst nach 10 sicheren Wiederholungen einführen"],
"tipp": "Nie zu früh das Kommandowort einführen."},
{"id": "gk_platz", "name": "Platz", "kat": "Grundkommando", "schwierigkeit": "Anfänger", "alter": "Ab 10 Wo", "dauer": "35 Min",
"beschreibung": "Hund legt sich auf Signal hin — wichtig für Ruhephasen und Wartesituationen.",
"schritte": ["Hund ins Sitz", "Leckerli senkrecht zwischen Vorderpfoten führen", "Ellenbogen + Hinterteil am Boden → belohnen", "Wort nach 1015 Wiederholungen"],
"tipp": "Leckerli unter dein angewinkeltes Knie halten — Hund kriecht drunter durch."},
{"id": "gk_bleib", "name": "Bleib", "kat": "Grundkommando", "schwierigkeit": "AnfängerFortg.", "alter": "Ab 12 Wo", "dauer": "5 Min",
"beschreibung": "Drei Dimensionen: Dauer, Distanz, Ablenkung — immer nur eine steigern.",
"schritte": ["Sitz/Platz", "2 Sek warten → Markerwort + Leckerli", "Freigabewort 'Okay' einführen", "Schrittweise: 2 → 5 → 10 → 30 Sek", "Erst dann einen Schritt zurück"],
"tipp": "Immer zum Hund zurückkehren — nie ihn zu dir rufen beim Bleib."},
{"id": "gk_hier", "name": "Hier / Komm", "kat": "Grundkommando", "schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "5 Min",
"beschreibung": "Lebensrettend wichtig — zuverlässiger Rückruf in jeder Situation.",
"schritte": ["Wohnung, 23 Meter, hinknien", "Freudige Stimme: 'Hier!'", "Ankommen = Mega-Belohnung", "Nur einmal rufen", "Schleppleine im Garten"],
"tipp": "Niemals rufen und dann etwas Unangenehmes tun."},
{"id": "gk_fuss", "name": "Fuß (Leinenführigkeit)", "kat": "Grundkommando", "schwierigkeit": "Fortg. Anfänger", "alter": "Ab 12 Wo", "dauer": "510 Min",
"beschreibung": "Hund läuft ruhig neben dir, Leine hängt locker durch.",
"schritte": ["Leckerli auf Hüfthöhe links halten", "Schritt vorwärts, Hund folgt", "Leine locker = sofort belohnen", "Zieht er: stehen bleiben oder Richtung wechseln"],
"tipp": "Nie mitziehen lassen — Leine locker = Vorwärtsbewegung, straff = Stopp."},
{"id": "gk_aus", "name": "Aus / Lass es", "kat": "Grundkommando", "schwierigkeit": "Anfänger", "alter": "Ab 10 Wo", "dauer": "35 Min",
"beschreibung": "Hund lässt Gegenstände auf Signal los — Sicherheit und Spielen.",
"schritte": ["Hund hält Spielzeug", "Leckerli vor Nase → er lässt los", "Markerwort + Leckerli", "Gegenstand zurückgeben"],
"tipp": "Immer zurückgeben → Hund lernt: Loslassen lohnt sich."},
{"id": "gk_warte", "name": "Warte", "kat": "Grundkommando", "schwierigkeit": "Anfänger", "alter": "Ab 10 Wo", "dauer": "35 Min",
"beschreibung": "Kurzes Innehalten vor Tür, Futter, Treppe — Impulskontrolle im Alltag.",
"schritte": ["Futterschüssel abdecken wenn Hund stürmt", "Schüssel freigeben sobald er wartet", "'Okay' als Freigabe", "An Türschwellen und Autoausstieg üben"],
"tipp": "Immer mit Freigabe beenden — Hund muss wissen wann er darf."},
{"id": "gk_steh", "name": "Steh", "kat": "Grundkommando", "schwierigkeit": "Anfänger", "alter": "Ab 12 Wo", "dauer": "35 Min",
"beschreibung": "Hund bleibt im Stand — praktisch beim Tierarzt, Bürsten, Pfoten abwischen.",
"schritte": ["Hund im Sitz", "Leckerli horizontal vor die Nase führen, weg vom Körper", "Hund steht auf → Markerwort + Leckerli", "Position halten üben"],
"tipp": "Wird oft vergessen — aber beim Tierarzt Gold wert!"},
{"id": "gk_rückwaerts", "name": "Rückwärtsgehen", "kat": "Grundkommando", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "5 Min",
"beschreibung": "Hund geht auf Signal rückwärts — cool und nützlich bei engen Situationen.",
"schritte": ["In engem Gang stehen, langsam auf Hund zugehen", "Hund weicht zurück → Markerwort + Leckerli", "Handzeichen einführen: Finger zeigen, wegbewegen", "Schrittweise mehr Schritte"],
"tipp": "Schmaler Flur oder zwischen zwei Stühlen macht es leichter."},
{"id": "gk_apport", "name": "Apportieren", "kat": "Grundkommando", "schwierigkeit": "Mittel", "alter": "Ab 4 Mo", "dauer": "10 Min",
"beschreibung": "Hund holt geworfenes Objekt und bringt es zurück — Spielen und Sport.",
"schritte": ["Spielzeug werfen, Hund läuft hin", "Sobald er es nimmt: 'Bring!'", "Zurückkommen belohnen", "'Aus' und Gegenstand übergeben", "Schrittweise weiter werfen"],
"tipp": "Viele Hunde apportieren gern — wichtig ist das Zurückbringen und Loslassen."},
# ── TRICKS & ENTERTAINMENT ──────────────────────────────────────
{"id": "trick_pfote", "name": "Pfote / Handschlag","kat": "Trick", "schwierigkeit": "Anfänger", "alter": "Ab 12 Wo", "dauer": "3 Min",
"beschreibung": "Klassiker — sieht toll aus und ist einfach zu lernen.",
"schritte": ["Leckerli in Faust, Faust auf Kniehöhe", "Hund kratzt → Faust öffnen + belohnen", "Auf flache Hand umstellen", "Wort 'Pfote' einführen"],
"tipp": "Auf flacher Hand wird aus Kratzen ein elegantes Ablegen."},
{"id": "trick_high5", "name": "High Five", "kat": "Trick", "schwierigkeit": "Anfänger", "alter": "Ab 12 Wo", "dauer": "3 Min",
"beschreibung": "Hund klatscht dir ab — Fortsetzung des Pfoten-Tricks.",
"schritte": ["Pfote beherrscht?", "Hand senkrecht halten statt waagrecht", "Hund hebt Pfote höher → belohnen", "Wort 'High Five' einführen"],
"tipp": "Erst Pfote sicher können, dann High Five — Aufbautraining!"},
{"id": "trick_dreh", "name": "Dreh / Runde", "kat": "Trick", "schwierigkeit": "Anfänger", "alter": "Ab 12 Wo", "dauer": "35 Min",
"beschreibung": "Hund dreht einen Kreis — sieht aus wie Tanzen!",
"schritte": ["Leckerli vor Nase, Kreis in der Luft führen", "Volle Drehung → Markerwort + Leckerli", "'Dreh' (links), 'Runde' (rechts)"],
"tipp": "Handbewegung schrittweise kleiner bis zum Fingerzeig."},
{"id": "trick_tod", "name": "Spiel tot", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund legt sich auf Kommando auf die Seite — der klassische Showstopper!",
"schritte": ["Hund im Platz", "Leckerli seitlich unter die Nase führen bis er kippt", "Auf Seite → Markerwort + Leckerli", "Freigabe: 'Leben'", "Wort 'Tod' oder Fingerzeig einführen"],
"tipp": "Ruhige Umgebung — viele Hunde fühlen sich erst unwohl auf der Seite."},
{"id": "trick_roll", "name": "Roll / Rollen", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund rollt sich auf dem Boden — Ausdauer-Trick mit Wow-Effekt.",
"schritte": ["'Spiel tot' beherrscht?", "Leckerli über den Rücken führen", "Hund rollt zur anderen Seite → belohnen", "Ganze Rolle → großes Lob"],
"tipp": "Auf weichem Untergrund üben — Hund rollt leichter."},
{"id": "trick_verbeugen","name": "Verbeugung", "kat": "Trick", "schwierigkeit": "Anfänger", "alter": "Ab 4 Mo", "dauer": "35 Min",
"beschreibung": "Hund macht einen Diener — Playbow, den Hunde von Natur aus kennen.",
"schritte": ["Hund im Stand", "Leckerli waagrecht Richtung Boden führen", "Vorderteil senkt sich, Hinterteil bleibt oben → belohnen", "Wort 'Verbeugung' einführen"],
"tipp": "Viele Hunde machen das spontan beim Spielen — einfach markieren!"},
{"id": "trick_kriech", "name": "Kriech / Robben", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund bleibt im Platz und kriecht vorwärts — sieht verrückt aus!",
"schritte": ["Hund im Platz", "Leckerli sehr flach auf dem Boden wegführen", "Hund bewegt sich vorwärts ohne aufzustehen → belohnen", "Schrittweise mehr Strecke"],
"tipp": "Unter einem Stuhl üben — natürliche Barriere zwingt ihn zu bleiben."},
{"id": "trick_slalom", "name": "Slalom Beine", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund schlängelt sich im Gehen durch deine Beine — der Augen-Trick!",
"schritte": ["Schritt machen, Leckerli führt Hund durch Beine", "Anderen Schritt, Leckerli durch andere Seite", "Immer enger, immer flüssiger", "Wort 'Slalom' einführen"],
"tipp": "Langsam gehen am Anfang — Hund braucht Zeit zum Orientieren."},
{"id": "trick_such_name","name": "Namen lernen (Spielzeug)", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund lernt Spielzeug beim Namen kennen und holt gezielt das richtige.",
"schritte": ["Ein Spielzeug: immer gleichen Namen sagen wenn er es nimmt", "Zwischen zwei Spielzeugen wählen lassen", "Gezielt das richtige holen lassen", "Weiteres Spielzeug dazufügen"],
"tipp": "Border Collies können bis zu 1000 Wörter lernen — das hier ist der Anfang!"},
{"id": "trick_schale", "name": "Leckerli auf Nase balancieren", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "5 Min",
"beschreibung": "Hund hält Leckerli auf der Nase — und schnappt es auf Signal!",
"schritte": ["Hund ins Sitz, Kopf ruhig halten", "Leckerli auf Nasenrücken legen", "Kurz warten → Freigabe: Hund schnappt", "Haltezeit schrittweise verlängern"],
"tipp": "Anfangs Hand unter Kinn um Kopf zu stabilisieren."},
{"id": "trick_schublade","name": "Schublade schließen","kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "10 Min",
"beschreibung": "Hund schubst Schublade mit Nase oder Pfote zu — Alltagstrick.",
"schritte": ["Haftnotiz an Schublade", "Hund berührt Notiz → belohnen", "Schublade halb offen: Berühren schließt sie → Jackpot", "Wort 'Zu' einführen"],
"tipp": "Zuerst das Berühren eines Zielobjekts trainieren (Targeting)."},
{"id": "trick_zählen", "name": "Zählen", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund klopft mit Pfote auf Boden — sieht aus wie echtes Zählen!",
"schritte": ["Pfote-Trick beherrscht?", "Pfote auf deiner Hand → Leckerli", "Hand tiefer bis Hund auf Boden klopft", "Anzahl variieren: 1x, 2x, 3x markieren"],
"tipp": "Du gibst heimlich das Signal — Publikum denkt der Hund rechnet wirklich."},
{"id": "trick_hupe", "name": "Klingel läuten", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund läutet eine Glocke — z.B. um rauszuwollen.",
"schritte": ["Glocke an Türklinke hängen", "Haftnotiz drauf, Hund berührt → belohnen", "Berühren läutet Glocke → Tür öffnen", "Nur klingeln wenn er raus will"],
"tipp": "Manche Hunde nutzen das dann selbstständig — klingeln wenn sie müssen!"},
{"id": "trick_korb", "name": "Spielzeug aufräumen", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund räumt Spielzeug in Körbe — nützlich und beeindruckend.",
"schritte": ["Apport und Aus beherrscht?", "Körb vor Hund stellen", "Spielzeug aufheben lassen → über Korb führen → Aus → belohnen", "Schrittweise mehrere Objekte"],
"tipp": "Einer der Tricks der am meisten Leute beeindruckt!"},
{"id": "trick_umarmung", "name": "Umarmung / Herz", "kat": "Trick", "schwierigkeit": "Anfänger", "alter": "Ab 4 Mo", "dauer": "35 Min",
"beschreibung": "Hund legt Kopf oder Vorderpfoten auf dich — für süße Fotos!",
"schritte": ["Auf Knie hinhocken", "Leckerli hinter deinen Nacken führen", "Hund legt Pfoten auf Schultern um dranzukommen", "Markerwort + Leckerli + Kamera bereit!"],
"tipp": "Perfekt für Insta-Content — immer einen Fotografen parat haben!"},
{"id": "trick_licht", "name": "Licht ausschalten", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund drückt Lichtschalter — praktisch und beeindruckend!",
"schritte": ["Targeting trainieren (Nase/Pfote berührt Ziel)", "Zielobjekt an Lichtschalter kleben", "Berühren löst Licht aus → Jackpot", "Wort 'Licht aus' einführen"],
"tipp": "Hund braucht dafür etwas Körpergröße — bei kleinen Hunden Schalter tiefer setzen."},
# ── PROBLEMVERHALTEN & ALLTAG ───────────────────────────────────
{"id": "pb_springen", "name": "Nicht springen", "kat": "Problemverhalten","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "Jede Begrüßung",
"beschreibung": "Hund begrüßt mit allen vier Pfoten am Boden.",
"schritte": ["Springt er: keine Reaktion", "Vier Pfoten unten → sofort belohnen", "Alle Haushaltsmitglieder gleich reagieren"],
"tipp": "Einmal springen lassen = wochenlanger Rückschritt."},
{"id": "pb_allein", "name": "Alleine bleiben", "kat": "Problemverhalten","schwierigkeit": "Mittel", "alter": "Ab 10 Wo", "dauer": "Mehrmals täglich",
"beschreibung": "Hund bleibt ruhig allein — ohne Stress, Bellen oder Zerstören.",
"schritte": ["Kong füllen", "10 Sek raus → ruhig rein", "Zeit schrittweise erhöhen", "Kommen und Gehen normalisieren"],
"tipp": "Welpen max. 12 Stunden, Erwachsene max. 46 Stunden."},
{"id": "pb_bellen", "name": "Weniger Bellen", "kat": "Problemverhalten","schwierigkeit": "Mittel", "alter": "Ab 8 Wo", "dauer": "510 Min täglich",
"beschreibung": "Hund beruhigt sich auf Signal — kein übermäßiges Kläffen.",
"schritte": ["Ursache kennen: Alarm, Frust, Aufmerksamkeit, Angst", "Aufmerksamkeits-Bellen ignorieren", "Pause abwarten → markieren + belohnen", "'Ruhig' einführen"],
"tipp": "Nie schreien — der Hund denkt du bellst mit!"},
{"id": "pb_leine", "name": "Nicht an Leine ziehen", "kat": "Problemverhalten","schwierigkeit": "Mittel", "alter": "Ab 12 Wo", "dauer": "Jeder Spaziergang",
"beschreibung": "Hund zieht nicht — Spaziergang wird für beide entspannter.",
"schritte": ["Ruhigen Start üben", "Zieht er: stehen bleiben", "Oder Richtung wechseln", "Leine locker = Leckerli + Vorwärtsbewegung"],
"tipp": "Brustgeschirr mit vorderer Befestigung hilft bei starken Ziehern."},
{"id": "pb_desensib", "name": "Desensibilisierung","kat": "Problemverhalten","schwierigkeit": "Fortg. Anfänger", "alter": "Ab 12 Wo", "dauer": "515 Min",
"beschreibung": "Hund bleibt unter Reizen (Hunde, Menschen, Fahrräder) entspannt.",
"schritte": ["Trigger identifizieren", "Schwellenabstand ermitteln", "Reiz zeigen → sofort hochwertiges Leckerli", "Distanz ganz langsam verringern"],
"tipp": "Bei Überreaktion: ruhig mehr Abstand, keine Strafe."},
{"id": "pb_auto", "name": "Autofahren / Ruhig im Auto", "kat": "Problemverhalten","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "Schrittweise",
"beschreibung": "Hund fährt entspannt mit — kein Hecheln, Bellen oder Erbrechen.",
"schritte": ["Auto als positiven Ort einführen: Leckerlis im stehenden Auto", "Kurze Fahrten (2 Min) → belohnen", "Zeit schrittweise erhöhen", "Immer mit leerem Magen fahren"],
"tipp": "Gesicherter Transport (Gurt, Box) schützt Hund und Fahrer."},
{"id": "pb_fremde", "name": "Ruhig bei Fremden", "kat": "Problemverhalten","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "Täglich",
"beschreibung": "Hund begrüßt Fremde freundlich aber ruhig.",
"schritte": ["Kontrollierte Begegnungen üben", "Hund darf Fremde begrüßen wenn ruhig", "Springen und Zappeln ignorieren", "Fremde geben Leckerlis wenn Hund ruhig ist"],
"tipp": "Sozialisierungsfenster bis 16 Wochen nutzen!"},
{"id": "pb_kinder", "name": "Hund und Kinder", "kat": "Problemverhalten","schwierigkeit": "Mittel", "alter": "Ab 8 Wo", "dauer": "Täglich üben",
"beschreibung": "Hund verhält sich sicher und ruhig rund um Kinder.",
"schritte": ["Ruhige, positive Begegnungen arrangieren", "Keine Aufregung fördern", "Hund hat immer Rückzugsmöglichkeit", "Kinder: nie rennen oder schreien beim Hund"],
"tipp": "Hund und Kind nie unbeaufsichtigt lassen — egal wie brav der Hund ist."},
{"id": "pb_box", "name": "Box-Training / Körbchen", "kat": "Problemverhalten","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "510 Min täglich",
"beschreibung": "Hund liebt seine Box als sicheren Rückzugsort.",
"schritte": ["Box mit Leckereien bestücken", "Hund geht freiwillig rein → feiern", "Tür erst nach Gewöhnung schließen", "Box nie zur Strafe nutzen"],
"tipp": "Box ist Rückzugsort, kein Gefängnis — dort nie stören."},
{"id": "pb_impulskontrolle", "name": "Impulskontrolle", "kat": "Problemverhalten","schwierigkeit": "Anfänger", "alter": "Ab 10 Wo", "dauer": "5 Min täglich",
"beschreibung": "Hund wartet und handelt nicht impulsiv — z.B. Leckerli nicht sofort schnappen.",
"schritte": ["Leckerli auf Handfläche: Hand schließen wenn er schnappt", "Sobald er wartet: Hand öffnen + belohnen", "Leckerli auf Boden: 'Warte' bis Freigabe", "Zeit variieren"],
"tipp": "Impulskontrolle ist die Basis für fast alle anderen Übungen."},
{"id": "pb_maulkorb", "name": "Maulkorb-Training", "kat": "Problemverhalten","schwierigkeit": "Anfänger", "alter": "Jedes Alter","dauer": "1015 Min",
"beschreibung": "Hund trägt Maulkorb entspannt — für Notfall, Tierarzt, Bus.",
"schritte": ["Maulkorb zeigen: Leckerli darin → Hund steckt Nase rein", "Nur am Maulkorb füttern", "Maulkorb kurz anlassen → belohnen", "Zeit schrittweise erhöhen"],
"tipp": "Präventiv trainieren — im Notfall ist es zu spät."},
{"id": "pb_begegnung", "name": "Hundebegegnungen an der Leine", "kat": "Problemverhalten","schwierigkeit": "Fortg.","alter": "Ab 3 Mo", "dauer": "1015 Min",
"beschreibung": "Hund reagiert entspannt auf andere Hunde an der Leine.",
"schritte": ["Großen Abstand halten", "Anderen Hund zeigen → Leckerli", "Hund schaut → du lobst", "Abstand sehr langsam verringern"],
"tipp": "Nie Hunde auf Leine frontal aufeinander treffen lassen."},
# ── MENTALE AUSLASTUNG ──────────────────────────────────────────
{"id": "mental_nase", "name": "Nasenarbeit Basis", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "510 Min",
"beschreibung": "10 Min Suchen ermüdet mehr als 1 Stunde Spaziergang!",
"schritte": ["Leckerli unter Becher verstecken", "Hund darf suchen → findet er es: belohnen", "3 Becher, 1 mit Leckerli", "Später im Gras verstecken: 'Such!'"],
"tipp": "Perfekt für Regentage."},
{"id": "mental_intelligenz", "name": "Intelligenzspielzeug", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "Ab 10 Wo", "dauer": "1020 Min",
"beschreibung": "Hund knobelt selbstständig — Futter holen durch Schieben, Drehen, Heben.",
"schritte": ["Einfaches Level zuerst", "Hund beobachten lassen wie Futter herauskommt", "Nicht helfen — Frustration kurz aushalten lassen", "Schwierigkeit steigern"],
"tipp": "Vorsicht Sucht: manche Hunde können nicht aufhören!"},
{"id": "mental_schnueffeln", "name": "Schnüffelrasen / Futtermatte", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "Ab 8 Wo","dauer": "510 Min",
"beschreibung": "Futter im Rasen/Matte verstecken — Nase an, Gehirn aus.",
"schritte": ["Futtermatte vorbereiten", "Hund mit 'Such' freigeben", "Ruhig beobachten", "Als Mahlzeit-Ersatz nutzen"],
"tipp": "Verlangsamt Fressen und beruhigt — ideal nach Aufregung."},
{"id": "mental_tracking","name": "Mantrailing / Fährtenarbeit", "kat": "Mentale Auslastung","schwierigkeit": "Fortg.","alter": "Ab 6 Mo", "dauer": "2030 Min",
"beschreibung": "Hund folgt einer Menschenfährte — uralter Instinkt wiederbeleben.",
"schritte": ["Kurze Fährte legen (5 Meter)", "Leckerlis auf Fährte", "'Such' Hund folgt Nase", "Schrittweise: länger, älter, schwieriger"],
"tipp": "Erschöpft selbst große aktive Hunde komplett."},
{"id": "mental_dummy", "name": "Dummy-Training Basis","kat": "Mentale Auslastung","schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "1520 Min",
"beschreibung": "Hund sucht geworfenes Dummy und bringt es zurück — Jagdhund-Instinkt.",
"schritte": ["Dummy zeigen, Begeisterung wecken", "Kurz werfen: 35 Meter", "Hund holt → Rückruf", "Dummy übergeben → Leckerli"],
"tipp": "Wasserdummys für See-Variante — Hunde lieben das!"},
{"id": "mental_tricks_sequenz", "name": "Trick-Sequenzen", "kat": "Mentale Auslastung","schwierigkeit": "Fortg.","alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund führt mehrere Tricks hintereinander durch — Konzentration pur.",
"schritte": ["Zwei bekannte Tricks verbinden: Sitz → Pfote", "Immer kürzer werdende Pausen", "Drei Tricks aneinanderreihen", "Als Show präsentieren"],
"tipp": "Trainiert Konzentration und Flexibilität gleichzeitig."},
{"id": "mental_zieltraining", "name": "Zieltraining (Targeting)", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "Ab 10 Wo","dauer": "5 Min",
"beschreibung": "Hund berührt ein Zielobjekt mit Nase oder Pfote — Basis für viele Tricks.",
"schritte": ["Hand vor Hund halten", "Hund berührt sie mit Nase → Markerwort + Leckerli", "Auf Stab oder Post-It übertragen", "Ziel bewegen: Hund folgt"],
"tipp": "Schlüssel für Schublade, Licht, Slam Dunk und Co."},
{"id": "mental_freiarbeit", "name": "Freiarbeit / Shaping", "kat": "Mentale Auslastung","schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund findet selbst heraus was belohnt wird — stärkt Eigeninitiative.",
"schritte": ["Neues Objekt in Raum stellen", "Jede Interaktion markieren + belohnen", "Schrittweise nur bestimmte Aktionen belohnen", "Hund erfindet Tricks selbst"],
"tipp": "Manche Hunde flippen aus vor Freude — macht süchtig!"},
# ── KÖRPERPFLEGE & HANDLING ─────────────────────────────────────
{"id": "body_pfoten", "name": "Pfoten anfassen / reinigen", "kat": "Körperpflege","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "35 Min täglich",
"beschreibung": "Hund lässt Pfoten anfassen, reinigen und Krallen schneiden.",
"schritte": ["Pfote kurz berühren → belohnen", "Länger halten → belohnen", "Zwischen Zehen fassen", "Krallenschneider zeigen bevor er benutzt wird"],
"tipp": "Jeden Tag ein paar Sekunden — besser als einmal im Monat kämpfen."},
{"id": "body_bürsten", "name": "Bürsten / Fellpflege", "kat": "Körperpflege","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "25 Min täglich",
"beschreibung": "Hund steht ruhig beim Bürsten — ohne Weglaufen oder Schnappen.",
"schritte": ["Bürste zeigen: Leckerli daneben", "Kurz bürsten → belohnen", "Empfindliche Stellen zuletzt", "Positive Assoziation mit Bürste aufbauen"],
"tipp": "Gleiche Routine täglich → Hund weiß was kommt und entspannt sich."},
{"id": "body_ohren", "name": "Ohren reinigen", "kat": "Körperpflege","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "35 Min",
"beschreibung": "Hund lässt Ohren reinigen — Gesundheitscheck ohne Stress.",
"schritte": ["Ohr kurz anfassen → belohnen", "Ohr hochhalten → belohnen", "Tuch zeigen: Leckerli drauf", "Kurz reinigen → große Belohnung"],
"tipp": "Bevor es schmerzt trainieren — nicht wenn Ohr schon entzündet ist."},
{"id": "body_zähne", "name": "Zähne putzen", "kat": "Körperpflege","schwierigkeit": "Mittel", "alter": "Ab 8 Wo", "dauer": "25 Min täglich",
"beschreibung": "Hund lässt Zähne putzen — spart teure Narkosen beim Tierarzt.",
"schritte": ["Finger in Mund → belohnen", "Hundezahnpasta zeigen (essbar!)", "Finger mit Paste reiben", "Zahnbürste einführen"],
"tipp": "Zahnpasta für Hunde → lecker für Hunde, sicher zu schlucken."},
{"id": "body_tierarzt", "name": "Tierarzt-Training", "kat": "Körperpflege","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "510 Min",
"beschreibung": "Hund ist beim Tierarzt kooperativ — kein Trauma, kein Stress.",
"schritte": ["Tisch von unten anfassen → belohnen", "Auf Tisch/Waage stellen → belohnen", "Körper überall berühren", "Bekannte Menschen 'untersuchen' lassen"],
"tipp": "Gesunde Besuche beim Tierarzt nur zum Leckerli holen."},
{"id": "body_staubsauger","name": "Staubsauger / Geräusche", "kat": "Körperpflege","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "510 Min",
"beschreibung": "Hund reagiert entspannt auf Haushaltsgeräusche.",
"schritte": ["Staubsauger ausgeschaltet zeigen → Leckerli", "Kurz einschalten, weiter weg", "Leckerlis während Lärm", "Schrittweise näher / länger"],
"tipp": "Sozialisierungsfenster nutzen — unter 16 Wochen am einfachsten."},
{"id": "body_handling", "name": "Allgemeines Handling", "kat": "Körperpflege","schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "5 Min täglich",
"beschreibung": "Hund lässt sich überall anfassen — Maul, Pfoten, Bauch, Schwanz.",
"schritte": ["Täglich verschiedene Körperstellen anfassen", "Immer mit Leckerli verbinden", "Schnell → langsam → fest", "Ungewohnte Geräusche dabei machen"],
"tipp": "Basis für alles — gut angepackter Hund ist ein entspannter Hund."},
# ── HUNDESPORT BASICS ───────────────────────────────────────────
{"id": "sport_sprung", "name": "Sprung / Hindernis", "kat": "Hundesport", "schwierigkeit": "Anfänger", "alter": "Ab 1 Jahr", "dauer": "510 Min",
"beschreibung": "Hund springt auf Signal über Hindernisse — Agility-Basis.",
"schritte": ["Hindernis flach am Boden", "Hund drüber locken mit Leckerli", "Wort 'Hopp' einführen", "Höhe sehr schrittweise erhöhen"],
"tipp": "Unter 12 Monate: keine Sprünge — Gelenke noch nicht ausgewachsen."},
{"id": "sport_tunnel", "name": "Tunnel", "kat": "Hundesport", "schwierigkeit": "Anfänger", "alter": "Ab 8 Mo", "dauer": "510 Min",
"beschreibung": "Hund läuft durch Tunnel — Agility-Klassiker.",
"schritte": ["Kurzen Tunnel zeigen (zusammengefaltet)", "Hund reinlocken mit Leckerli", "Am anderen Ende warten und rufen", "Tunnel schrittweise strecken"],
"tipp": "Die meisten Hunde lieben Tunnel — schnell gelernt!"},
{"id": "sport_weave", "name": "Slalom / Weave Poles", "kat": "Hundesport", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund schlängelt sich durch Stangen — anspruchsvollste Agility-Übung.",
"schritte": ["2 Stangen: durch die Mitte führen", "Stangen schrittweise enger", "Tempo erhöhen", "6, dann 12 Stangen"],
"tipp": "Dauert Monate bis es sitzt — Geduld zahlt sich aus!"},
{"id": "sport_trickdog", "name": "Trickdogging Basics", "kat": "Hundesport", "schwierigkeit": "Mittel", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Choreographierte Trick-Abfolgen zu Musik — Hund und Mensch als Team.",
"schritte": ["56 sichere Tricks auswählen", "In Reihenfolge üben", "Musik dazu abspielen", "Auf Rhythmus achten"],
"tipp": "Videos davon machen — sieht immer besser aus als man denkt!"},
{"id": "sport_balance", "name": "Balance / Balanceboard", "kat": "Hundesport", "schwierigkeit": "Mittel", "alter": "Ab 1 Jahr", "dauer": "510 Min",
"beschreibung": "Hund balanciert auf wackeligen Unterlagen — Körperbewusstsein stärken.",
"schritte": ["Instabiles Polster → Vorderpfoten drauf", "Alle vier Pfoten", "Balanceboard einführen", "Bewegung auf dem Board belohnen"],
"tipp": "Stärkt Koordination und Muskulatur — gut für Sportler."},
{"id": "sport_hürde_körper", "name": "Hürde mit Körper", "kat": "Hundesport", "schwierigkeit": "Anfänger", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund springt über dein ausgestrecktes Bein oder deinen Arm.",
"schritte": ["Bein flach auf Boden → Hund drüber locken", "Bein höher", "Arm als Hürde", "Tempo einbauen"],
"tipp": "Kein Equipment nötig — überall möglich!"},
# ── ERWEITERTE GRUNDKOMMANDOS ────────────────────────────────────
{"id": "gk_links_rechts", "name": "Links / Rechts lernen", "kat": "Grundkommando", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "10 Min",
"beschreibung": "Hund unterscheidet Links und Rechts auf Kommando.",
"schritte": ["Immer gleiches Leckerli-Signal für Links", "Anderem Signal für Rechts", "Konsequent gleiche Hand = gleiche Richtung", "Schrittweise ohne Handbewegung"],
"tipp": "Braucht Zeit — aber Wow-Faktor beim Publikum ist enorm."},
{"id": "gk_freifolge", "name": "Freifolge (ohne Leine)", "kat": "Grundkommando", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund geht neben dir ohne Leine auf Signal — höchste Verlässlichkeit.",
"schritte": ["Fuß an Leine perfekt?", "Leine locker lassen bis sie kaum Spannung hat", "Leine hängen lassen, dann ablegen", "Schrittweise offenere Umgebungen"],
"tipp": "Nur in sicherer Umgebung üben — nie auf Straße oder Hundewiese testen."},
{"id": "gk_schleich", "name": "Schleichen", "kat": "Grundkommando", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund bewegt sich in geduckter Haltung vorwärts — Geheimdienstmodus!",
"schritte": ["Hund im Platz", "Leckerli sehr tief halten und wegführen", "Hund kriecht vorwärts", "'Schleich' Wort einführen"],
"tipp": "Tunnelübung hilft als natürliche Führungshilfe."},
{"id": "gk_platzmit", "name": "Platz mit Distanz", "kat": "Grundkommando", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "10 Min",
"beschreibung": "Hund geht auf Kommando von weitem ins Platz — ohne hinzugehen.",
"schritte": ["Platz auf 1 Meter — Hund geht selbst hin", "Schrittweise weiter weg", "Handzeichen aus Entfernung", "Richtungswechsel einbauen"],
"tipp": "Braucht gefestigtes Platz und Bleib zuerst."},
{"id": "gk_stop", "name": "Stop / Stehenbleiben", "kat": "Grundkommando", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund bleibt auf Signal sofort stehen — auch aus der Bewegung.",
"schritte": ["Hund neben dir gehen", "Plötzlich stoppen, Leckerli halten", "Hund stoppt → belohnen", "Wort 'Stop' einführen, dann Entfernung aufbauen"],
"tipp": "Wichtig für Sicherheit — z.B. kurz vor einer Straße."},
# ── ERWEITERTE TRICKS ────────────────────────────────────────────
{"id": "trick_kopfsenken", "name": "Kopf senken / Schämen", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund legt Kopf auf die Pfoten — süßester Trick überhaupt!",
"schritte": ["Hund im Platz", "Leckerli auf Pfoten legen", "Hund schaut runter → Markerwort", "Wort 'Schäm dich' oder 'Kopf runter'"],
"tipp": "Für Instagram-Fotos unschlagbar schön."},
{"id": "trick_spiegeln", "name": "Spiegeln / Mirrorwork", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund ahmt deine Bewegungen nach — Trickdogging-Highlight.",
"schritte": ["Vor Hund stehen, Schritt links → Hund folgt rechts", "Belohnen wenn er spiegelt", "Verschiedene Bewegungen einbauen", "Rhythmisch werden"],
"tipp": "Basis für Tanzchoreographien."},
{"id": "trick_welle", "name": "Winke / Welle", "kat": "Trick", "schwierigkeit": "Anfänger", "alter": "Ab 12 Wo", "dauer": "35 Min",
"beschreibung": "Hund hebt Pfote und winkt — super für Videos und Fotos!",
"schritte": ["Pfote beherrscht?", "Hand hoch halten statt waagrecht", "Hund hebt Pfote höher, wackelt", "'Winke' oder 'Tschüss' einführen"],
"tipp": "Kombi mit 'High Five' macht super Video-Content."},
{"id": "trick_skateboard","name": "Skateboard fahren", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1020 Min",
"beschreibung": "Hund schiebt und fährt ein Skateboard — TikTok-Garant!",
"schritte": ["Board zeigen: Leckerli drauf → Pfoten drauf", "Alle 4 Pfoten auf Board", "Board leicht schieben", "Hund schiebt selbst mit Fuß ab"],
"tipp": "Bulldog-Rassen lieben das angeblich genetisch..."},
{"id": "trick_basketball","name": "Basketball / Dunking", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund wirft Ball in Korb — beeindruckendster Trick für Zuschauer.",
"schritte": ["Apport und Targeting beherrscht?", "Ball in den Korb targeten", "Ball aufnehmen und zu Korb gehen", "Ball über Korbrand fallen lassen"],
"tipp": "Körpergröße muss zum Korb passen."},
{"id": "trick_kreativ", "name": "101-Ding-Spiel", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "10 Min",
"beschreibung": "Hund erfindet spontan Aktionen mit einem Objekt — 101 Wege sind möglich!",
"schritte": ["Neues Objekt vor Hund stellen", "Jede Interaktion markieren und belohnen", "Nur noch bestimmte Aktionen belohnen", "Hund steigert Kreativität"],
"tipp": "Macht Hunde brillant — stärkt Eigeninitiative wie nichts sonst."},
{"id": "trick_koffer", "name": "Koffer ziehen", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "1015 Min",
"beschreibung": "Hund zieht ein Spielzeug oder kleines Objekt hinter sich her.",
"schritte": ["Seil an Objekt befestigen", "Hund nimmt Seil ins Maul", "Rückwärtsgehen einbauen", "Objekt zieht nach"],
"tipp": "Mit Apportier-Hund einfach umzusetzen."},
{"id": "trick_over_arm", "name": "Über den Arm klettern", "kat": "Trick", "schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "510 Min",
"beschreibung": "Hund klettert über deinen ausgestreckten Arm — Freestyle-Move!",
"schritte": ["Arm flach auf Boden", "Hund drüber locken", "Arm höher", "Hund klettert elegant drüber"],
"tipp": "Auch rückwärts möglich — sieht spektakulär aus."},
{"id": "trick_beinzwischen", "name": "Zwischen den Beinen stehen", "kat": "Trick", "schwierigkeit": "Anfänger", "alter": "Ab 12 Wo", "dauer": "35 Min",
"beschreibung": "Hund steht ruhig zwischen deinen Beinen — Foto-Klassiker!",
"schritte": ["Breitbeinig stehen", "Leckerli zwischen die Beine führen", "Hund folgt und steht drin", "Wort 'Zwischen' einführen"],
"tipp": "Basis für viele Weiterführungen: z.B. Slalom aus Stehposition."},
{"id": "trick_küsschen", "name": "Küsschen geben", "kat": "Trick", "schwierigkeit": "Anfänger", "alter": "Ab 12 Wo", "dauer": "3 Min",
"beschreibung": "Hund leckt auf Kommando die Wange — für süße Fotos!",
"schritte": ["Leckerli-Reste auf Wange kleben", "Hund leckt → Markerwort", "Ohne Köder wiederholen", "'Küsschen' einführen"],
"tipp": "Hygiene beachten — nach Spaziergang zuerst Maul reinigen 😄"},
{"id": "trick_twist_jump", "name": "Sprung mit Drehung", "kat": "Trick", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "10 Min",
"beschreibung": "Hund springt hoch und dreht sich in der Luft — Wow-Faktor!",
"schritte": ["Dreh-Trick beherrscht?", "Auf Hinterbeine stellen üben", "Beim Aufstehen Dreh-Signal geben", "Schrittweise mehr Höhe"],
"tipp": "Nur für agile Hunde — Gelenke schonen!"},
# ── ERWEITERTE PROBLEMVERHALTEN ──────────────────────────────────
{"id": "pb_gewitter", "name": "Angst vor Gewitter/Feuerwerk", "kat": "Problemverhalten","schwierigkeit": "Mittel", "alter": "Jedes Alter","dauer": "Langfristig",
"beschreibung": "Hund bleibt bei Lärm entspannt — kein Zittern, Verstecken, Panik.",
"schritte": ["Geräusche aufnehmen, sehr leise abspielen", "Leckerlis während Geräusch", "Lautstärke sehr langsam erhöhen", "Sicheren Rückzugsort anbieten"],
"tipp": "Thundershirt kann kurzfristig helfen — kein Ersatz für Training."},
{"id": "pb_jagd", "name": "Jagdinstinkt kontrollieren", "kat": "Problemverhalten","schwierigkeit": "Fortg.", "alter": "Ab 6 Mo", "dauer": "Dauerprojekt",
"beschreibung": "Hund kann Jagdtrieb unterdrücken — Rückruf auch bei Wild.",
"schritte": ["Impuls-Kontrolle sehr fest verankern", "Rückruf mit Hochwertigem belohnen", "An der Schleppleine üben", "Kontrolliertes Zeigen von Wild mit Ablenkung"],
"tipp": "Manche Rassen haben sehr hohen Jagdtrieb — professionelle Hilfe sinnvoll."},
{"id": "pb_ressourcen", "name": "Ressourcen-Verteidigung", "kat": "Problemverhalten","schwierigkeit": "Fortg.", "alter": "Ab 8 Wo", "dauer": "Langfristig",
"beschreibung": "Hund knurrt nicht über Futter oder Spielzeug — 'Tauschhandel' lernen.",
"schritte": ["Beim Fressen Hand neben Schüssel legen", "Etwas Besseres tauschen gegen Schüssel", "Nie Schüssel wegnehmen ohne Gegenleistung", "Frühzeitig anfangen"],
"tipp": "Niemals mit Hund kämpfen — Veterinär-Verhaltenstherapeut hinzuziehen."},
{"id": "pb_beißhemmung", "name": "Beißhemmung (Welpe)", "kat": "Problemverhalten","schwierigkeit": "Anfänger", "alter": "816 Wochen","dauer": "Täglich",
"beschreibung": "Welpe lernt: Zähne tun weh — sanft sein mit Menschen.",
"schritte": ["Zähne an Haut → 'Au!' und Spielpause", "Sanfter Druck → weiter spielen", "Spielzeug immer bereit als Ersatz", "Konsequenz aller Haushaltsmitglieder"],
"tipp": "Zeitfenster ist 816 Wochen — danach viel schwerer zu lernen."},
{"id": "pb_trennungsangst", "name": "Trennungsangst intensiv","kat": "Problemverhalten","schwierigkeit": "Fortg.", "alter": "Jedes Alter","dauer": "Wochen bis Monate",
"beschreibung": "Hund entspannt sich wenn Besitzer geht — kein Stress mehr.",
"schritte": ["Körperweitergabe-Test: reagiert er auf Körpersprache vor dem Gehen?", "Abschiedssignale entkoppeln", "Kurze Abwesenheiten unter Stressschwelle", "Sehr langsam steigern"],
"tipp": "Echte Trennungsangst braucht Veterinär-Verhaltenstherapeuten."},
{"id": "pb_klingel", "name": "Ruhig bei Klingel / Besuch", "kat": "Problemverhalten","schwierigkeit": "Mittel", "alter": "Ab 10 Wo", "dauer": "Täglich",
"beschreibung": "Hund bellt nicht oder kurz bei Klingel und bleibt ruhig.",
"schritte": ["Klingel-Sound am Handy: Leckerli bei jedem Klingeln", "Hund ins Körbchen schicken auf Klingel", "Echter Besuch: im Körbchen bleiben bis ruhig", "Freigabe: Hund darf begrüßen"],
"tipp": "Einheitliche Reaktion aller Haushaltsmitglieder ist entscheidend."},
{"id": "pb_graben", "name": "Nicht graben", "kat": "Problemverhalten","schwierigkeit": "Mittel", "alter": "Ab 10 Wo", "dauer": "Dauerprojekt",
"beschreibung": "Hund gräbt nicht im Garten — oder nur an erlaubten Stellen.",
"schritte": ["Erlaubte Grabstelle einrichten (Sandkasten)", "Leckerlis dort verstecken", "Unerwünschtes Graben: Hund umleiten", "Nie bestrafen — nur umleiten"],
"tipp": "Terrier graben genetisch — ihnen eine eigene Grabstelle geben."},
{"id": "pb_reaktivität", "name": "Reaktivität reduzieren", "kat": "Problemverhalten","schwierigkeit": "Fortg.", "alter": "Ab 12 Wo", "dauer": "Monate",
"beschreibung": "Hund überreagiert nicht auf Reize — ruhiger an Leine und allgemein.",
"schritte": ["Trigger-Liste erstellen", "Abstand zu jedem Trigger bis unter Schwelle", "Entspanntes Verhalten beim Trigger stark belohnen", "Reize langsam aufbauen"],
"tipp": "BAT (Behavior Adjustment Training) sehr effektiv — Videos auf YouTube."},
# ── ERWEITERTE MENTALE AUSLASTUNG ────────────────────────────────
{"id": "mental_verstecken", "name": "Verstecken spielen", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "Ab 4 Mo", "dauer": "1020 Min",
"beschreibung": "Menschen im Haus oder draußen suchen — macht riesigen Spaß!",
"schritte": ["Hund kurz ablenken", "Verstecken", "'Hund, such!'", "Gefunden = riesige Belohnung"],
"tipp": "Kinder lieben es als Suchpartner — Hund ist begeistert."},
{"id": "mental_welcher_becher", "name": "Welcher Becher?", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "Ab 8 Wo", "dauer": "510 Min",
"beschreibung": "Shell-Spiel für Hunde — Leckerli unter einem von drei Bechern finden.",
"schritte": ["Hund schaut: Leckerli unter Becher", "Becher nicht bewegen: Hund zeigt richtigen", "Becher langsam tauschen", "Tempo erhöhen"],
"tipp": "Hunde haben phänomenales Gedächtnis — kaum zu überlisten!"},
{"id": "mental_geräusche", "name": "Geräusch-Sozialisierung", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "816 Wochen","dauer": "Täglich 5 Min",
"beschreibung": "Welpe lernt Stadtlärm, Kinderlärm, Staubsauger — für entspanntes Leben.",
"schritte": ["Geräusch-CDs für Welpen abspielen", "Sehr leise beginnen", "Leckerlis während Geräusch", "Echte Situationen aufsuchen"],
"tipp": "Sozialisierungsfenster bis 16 Wochen — jetzt oder nie!"},
{"id": "mental_trick_routine", "name": "Tägliche Trick-Routine","kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "Ab 6 Mo", "dauer": "5 Min täglich",
"beschreibung": "5 Minuten Tricks täglich — Hund bleibt fit im Kopf und baut Bindung auf.",
"schritte": ["35 bekannte Tricks auswählen", "Täglich gleiche Zeit", "Variieren: heute dies, morgen das", "Neuen Trick pro Woche dazu"],
"tipp": "Besser 5 Min täglich als 1 Stunde einmal die Woche."},
{"id": "mental_knobelbox", "name": "DIY Knobelbox", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "Ab 10 Wo", "dauer": "1015 Min",
"beschreibung": "Selbstgebastelte Rätselboxen aus Pappröhren, Dosen, Flaschen — kostet nichts!",
"schritte": ["Leckerlis in Klopapierrolle einrollen, Enden umknicken", "Mehrere Rollen in Karton", "Hund soll Leckerlis befreien", "Schwierigkeit erhöhen"],
"tipp": "DIY-Inspirationen auf Pinterest oder YouTube finden."},
{"id": "mental_social", "name": "Hunde-Kindergarten / Sozialtraining", "kat": "Mentale Auslastung","schwierigkeit": "Anfänger","alter": "816 Wochen","dauer": "Wöchentlich",
"beschreibung": "Welpe spielt mit anderen Welpen — wichtigste Sozialisierung überhaupt.",
"schritte": ["Zertifizierten Welpen-Kurs suchen", "Impfschutz vorher prüfen", "Spielgruppen nach Größe getrennt", "Regelmäßig hingehen bis 16 Wochen"],
"tipp": "Kein Welpen-Kurs = größtes Trainingsversäumnis überhaupt."},
{"id": "mental_geruch_id", "name": "Geruchserkennung", "kat": "Mentale Auslastung","schwierigkeit": "Mittel", "alter": "Ab 6 Mo", "dauer": "1015 Min",
"beschreibung": "Hund lernt bestimmte Gerüche zu erkennen — Basis für professionelle Arbeit.",
"schritte": ["Wattestäbchen mit Gewürzduft", "An Box befestigen", "Hund zeigt Box an → belohnen", "Andere Duftstoffe als Ablenkung dazu"],
"tipp": "Hundenase riecht 10.000x besser als Menschennase — nutze das!"},
# ── ERWEITERTE KÖRPERPFLEGE ──────────────────────────────────────
{"id": "body_bad", "name": "Baden / Duschen", "kat": "Körperpflege", "schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "Schrittweise",
"beschreibung": "Hund steht ruhig in Wanne oder Dusche — kein Drama mehr.",
"schritte": ["Leere Wanne: Leckerlis reinwerfen", "Wasser einlaufen lassen: immer mehr", "Kurzes Abduschen", "Abtrocknen mit Handtuch trainieren"],
"tipp": "Lauwarmes Wasser — nie heiß oder kalt."},
{"id": "body_halsband", "name": "Halsband / Geschirr anlegen","kat": "Körperpflege", "schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "35 Min",
"beschreibung": "Hund duldet Halsband und Geschirr ohne Stress.",
"schritte": ["Halsband zeigen: Leckerli daneben", "Kurz anlegen → sofort abnehmen + belohnen", "Länger anlassen", "Geschirr: Kopf selbst reinstecken trainieren"],
"tipp": "Nie über Kopf werfen — von unten anlegen."},
{"id": "body_krallen", "name": "Krallen schneiden", "kat": "Körperpflege", "schwierigkeit": "Mittel", "alter": "Ab 8 Wo", "dauer": "510 Min",
"beschreibung": "Hund lässt Krallen schneiden — ohne Kampf und Schreien.",
"schritte": ["Pfoten täglich anfassen", "Krallenschneider zeigen: Leckerli", "Eine Kralle: schneiden → Leckerli", "Nicht zu kurz schneiden (Blutgefäß)"],
"tipp": "Kratzbretter trainieren: Hund schleift selbst ab!"},
{"id": "body_check", "name": "Körpercheck / Ganzkörperuntersuchung","kat":"Körperpflege","schwierigkeit":"Anfänger","alter":"Ab 8 Wo","dauer":"5 Min täglich",
"beschreibung": "Täglicher Ganzkörper-Check — Zecken, Beulen, Verletzungen früh entdecken.",
"schritte": ["Täglich von Nase bis Schwanz abtasten", "Pfoten zwischen Zehen prüfen", "Hund steht ruhig → belohnen", "Auffälligkeiten notieren"],
"tipp": "Verhindert teure Tierarztbesuche — Früherkennung ist alles."},
# ── ERWEITERTE HUNDESPORT ────────────────────────────────────────
{"id": "sport_canicross", "name": "Canicross / Jogging", "kat": "Hundesport", "schwierigkeit": "Anfänger", "alter": "Ab 1 Jahr", "dauer": "3060 Min",
"beschreibung": "Hund und Mensch laufen zusammen — der natürlichste Sport!",
"schritte": ["Leinenführigkeit zuerst", "Spezialgeschirr für Canicross", "Langsam: 10 Min → schrittweise mehr", "Links/Rechts-Kommandos für Richtung"],
"tipp": "Erst ab 12 Monaten — Skelett vorher nicht voll ausgewachsen."},
{"id": "sport_schwimmen", "name": "Schwimmen lernen", "kat": "Hundesport", "schwierigkeit": "Anfänger", "alter": "Ab 6 Mo", "dauer": "30 Min",
"beschreibung": "Hund lernt schwimmen — tolle Auslastung und gelenkschonend.",
"schritte": ["Seichtes Wasser: Pfoten nass machen", "Leckerlis ins Wasser werfen", "Spielzeug ins flache Wasser", "Schwimmweste für unsichere Hunde"],
"tipp": "Nicht alle Hunde können instinktiv schwimmen — Kurzmaulrassen aufpassen!"},
{"id": "sport_rally", "name": "Rally Obedience Einführung", "kat": "Hundesport", "schwierigkeit": "Mittel", "alter": "Ab 1 Jahr", "dauer": "2030 Min",
"beschreibung": "Gemeinsam durch Parcours mit Schildern — Sport für Kopf und Körper.",
"schritte": ["Grundkommandos sehr sicher", "Erste Schilder: Sitz, Platz, Hier", "Kleinen Parcours bauen", "Flüssig von Schild zu Schild"],
"tipp": "Einstieg in Hundesport ohne Agility-Equipment möglich."},
{"id": "sport_flyball_basic", "name": "Flyball Grundlagen", "kat": "Hundesport", "schwierigkeit": "Fortg.", "alter": "Ab 1 Jahr", "dauer": "2030 Min",
"beschreibung": "Hund springt Hindernisse, drückt Pedal, fängt Ball — Teamsport!",
"schritte": ["Ball-Motivation sehr hoch halten", "Pedal drücken trainieren", "Hürden einzeln", "Alles kombinieren in Sequenz"],
"tipp": "Wird in Teams gespielt — lokale Flyball-Vereine suchen!"},
{"id": "sport_gewicht", "name": "Weight Pulling Basics", "kat": "Hundesport", "schwierigkeit": "Mittel", "alter": "Ab 2 Jahre","dauer": "1530 Min",
"beschreibung": "Hund zieht Gewichte auf Kommando — Kraft und Fokus trainieren.",
"schritte": ["Geschirr trainieren", "Leichtes Objekt ziehen: Karton", "Mehr Gewicht schrittweise", "Kommando 'Zieh' einführen"],
"tipp": "Erst ab 2 Jahren — Wirbelsäule vorher nicht belastungsreif."},
# ── WELPEN-SPEZIAL ────────────────────────────────────────────────
{"id": "welpe_name", "name": "Namen lernen (Welpe)", "kat": "Welpe Basics", "schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "5 Min täglich",
"beschreibung": "Welpe reagiert zuverlässig auf seinen Namen — Basis für alles.",
"schritte": ["Namen sagen wenn Welpe schaut", "Markerwort + Leckerli", "Namen in vielen Situationen üben", "Nie beim Schimpfen den Namen verwenden"],
"tipp": "Namen = immer positiv. Nie als Strafe."},
{"id": "welpe_stubenrein","name": "Stubenreinheit", "kat": "Welpe Basics", "schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "Wochenlang",
"beschreibung": "Welpe macht Geschäfte nur draußen — ohne Unfälle in der Wohnung.",
"schritte": ["Alle 12 Stunden raus", "Nach Schlafen und Fressen sofort raus", "Draußen loben bei Erfolg", "Unfälle innen nie bestrafen — einfach wegmachen"],
"tipp": "Welpen können bis 46 Monate die Blase noch nicht lange halten."},
{"id": "welpe_transport", "name": "Transportbox (Welpe)", "kat": "Welpe Basics", "schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "Täglich",
"beschreibung": "Welpe liebt seine Box als sicheren Rückzugsort.",
"schritte": ["Box offen lassen, Leckerlis reinwerfen", "Welpe geht freiwillig rein", "Tür für kurze Zeit schließen", "Als Schlafplatz etablieren"],
"tipp": "Box nie als Strafe — sicherer Hafen, nicht Gefängnis."},
{"id": "welpe_bindung", "name": "Bindung aufbauen", "kat": "Welpe Basics", "schwierigkeit": "Anfänger", "alter": "Ab 8 Wo", "dauer": "Täglich",
"beschreibung": "Intensive positive Bindung zwischen Hund und Mensch — Basis für Training.",
"schritte": ["Täglich 10 Min gezielt spielen", "Augenkontakt belohnen", "Sanftes Handling täglich", "Hund beobachten — was liebt er?"],
"tipp": "Bindung ist wichtiger als jede Übung — investiere hier!"},
{"id": "welpe_sozial", "name": "Sozialisierung Tiere", "kat": "Welpe Basics", "schwierigkeit": "Anfänger", "alter": "816 Wochen","dauer": "Täglich",
"beschreibung": "Welpe kennt Katzen, andere Hunde, Vögel — bleibt ruhig dabei.",
"schritte": ["Kontrollierte, positive Begegnungen", "Nie überfordern", "Körpersprache des Welpen lesen", "Positive Assoziation aufbauen"],
"tipp": "Jede neue Begegnung in dieser Phase prägt lebenslang."},
]
# ------------------------------------------------------------------
# Pflege-Tipps-Bibliothek
# ------------------------------------------------------------------
_PFLEGE_TIPPS = [
# ── FELL ────────────────────────────────────────────────────────
{"id":"fell_buersten_kurz","titel":"Bürsten bei Kurzhaarfell","kat":"Fell",
"beschreibung":"Kurzhaar braucht weniger, aber regelmäßige Pflege — für Glanz und Gesundheit.",
"schritte":["Gummibürste oder Hundekamm","Gegen und mit dem Fell bürsten","Totreste entfernen","Glanzspray optional"],
"materialien":"Gummibürste, feiner Kamm, Mikrofasertuch","haeufigkeit":"12x pro Woche",
"fell_typ":"kurz","saison":None,"tipp":"Kurzhaar verliert trotzdem Haare — wöchentliches Bürsten hält Wohnung sauber."},
{"id":"fell_buersten_lang","titel":"Bürsten bei Langhaarfell","kat":"Fell",
"beschreibung":"Langhaar verfilzt schnell — tägliches Bürsten verhindert schmerzhafte Knoten.",
"schritte":["Abschnittweise arbeiten","Zuerst Unterwolle mit Undercoat-Rake","Dann Bürste über alles","Verfilzungen mit Entfilzer-Spray lösen, nie reißen"],
"materialien":"Undercoat-Rake, Slicker-Bürste, Entfilzer-Spray","haeufigkeit":"Täglich",
"fell_typ":"lang","saison":None,"tipp":"Von Beinen und Schwanz beginnen — dort filzt es zuerst."},
{"id":"fell_buersten_lockig","titel":"Pflege bei Lockenfell (Pudel, Labradoodle)","kat":"Fell",
"beschreibung":"Lockiges Fell verliert kaum Haare, verfilzt aber stark — spezielle Technik nötig.",
"schritte":["Täglich durchkämmen mit Metallkamm","Verfilzungen mit Finger lösen, dann Kamm","Alle 68 Wochen Schertermin","Zwischen Augen und Pfoten regelmäßig kürzen"],
"materialien":"Metallkamm, Pin-Bürste, Schere","haeufigkeit":"Täglich + 68 Wochen Schertermin",
"fell_typ":"lockig","saison":None,"fell_pflege_art":"schneiden","tipp":"Lockiges Fell = hypoallergen, aber Pflegeaufwand unterschätzt!"},
{"id":"fell_scheren_technik","titel":"Fell schneiden: Technik & Scheren-Tipps","kat":"Fell",
"beschreibung":"Für Rassen mit kontinuierlichem Fellwuchs (Pudel, Bichon, Spoodle) — Scheren statt Trimmen!",
"schritte":["Fell nach dem Bad komplett trocknen und bürsten","Schere oder Clipper parallel zur Haarwuchsrichtung führen","Empfindliche Stellen (Gesicht, Pfoten) mit Effilierschere","Alle 68 Wochen zum Groomer oder selbst lernen","Nach dem Scheren: Fell bürsten und kontrollieren"],
"materialien":"Effilierschere, Clipper, Metallkamm","haeufigkeit":"Alle 68 Wochen",
"fell_typ":"lockig","saison":None,"fell_pflege_art":"schneiden","tipp":"Nie nasses Fell scheren — immer erst trocknen, sonst ungleichmäßiges Ergebnis."},
{"id":"fell_trimmen_technik","titel":"Fell trimmen: Stripping beim Rauhaar-Terrier","kat":"Fell",
"beschreibung":"Rauhaardrahtiges Fell (Westie, Schnauzer, Jack Russell) hat natürliche Wachstumsbegrenzung — Trimmen statt Scheren!",
"schritte":["Daumen und Zeigefinger: abgestorbene Haare zupfen (Stripping)","Immer in Haarwuchsrichtung arbeiten","Unterwolle mit feinem Rake ausbürsten","Niemals scheren — zerstört Textur dauerhaft","Alle 34 Monate professionelles Hand-Stripping"],
"materialien":"Stripper-Messer (Trimmmesser), Rake, Kreide für Grip","haeufigkeit":"Alle 34 Monate",
"fell_typ":"alle","saison":None,"fell_pflege_art":"trimmen","tipp":"Geschorenes Drahthaarfell verliert seine typische Textur dauerhaft — immer trimmen!"},
{"id":"fell_unterwolle", "titel":"Unterwolle ausbürsten (Fellwechsel)","kat":"Fell",
"beschreibung":"Zweimal jährlich toter Unterwolle-Berg — richtig ausgebürstet statt überall verteilt.",
"schritte":["Undercoat-Rake gegen Haarwuchsrichtung","Abschnittweise: Rücken, Flanken, Bauch","Furminator maximal 2x/Woche","Nach dem Bürsten: Hund ausschütteln lassen"],
"materialien":"Furminator oder Undercoat-Rake","haeufigkeit":"Täglich während Fellwechsel",
"fell_typ":"doppel","saison":"fruehling,herbst","tipp":"Einen Schuh voll Haare pro Session ist normal bei Huskies & Co."},
{"id":"fell_bad_timing", "titel":"Wann und wie oft Baden?","kat":"Fell",
"beschreibung":"Zu häufiges Baden zerstört den natürlichen Fellschutz — die richtige Balance.",
"schritte":["Kurzhaar: alle 68 Wochen","Langhaar/Lockig: alle 46 Wochen","Vor dem Bad: komplett ausbürsten","Nur Hundeshampoo verwenden — Menschen-Shampoo macht Fell spröde"],
"materialien":"Hundeshampoo, Handbrause, große Handtücher","haeufigkeit":"48 Wochen",
"fell_typ":"alle","saison":None,"tipp":"Fön auf niedrigster Stufe — hohe Hitze schadet dem Fell."},
{"id":"fell_trocknen", "titel":"Richtiges Trocknen nach dem Bad","kat":"Fell",
"beschreibung":"Nasses Fell eingerollt schläft = Hautprobleme — so trocknet man richtig.",
"schritte":["Handtuch: klopfen, nicht reiben", "Hundeföhn oder normaler Fön auf Kalt/Niedrig","Fell gleichzeitig bürsten","Niemals nass in kalte Luft"],
"materialien":"Absorbier-Handtuch, Hundeföhn","haeufigkeit":"Nach jedem Bad",
"fell_typ":"alle","saison":None,"tipp":"Mikrofaser-Handtücher nehmen 5x mehr Wasser auf als normale."},
{"id":"fell_geruch", "titel":"Fell-Geruch zwischen Bädern","kat":"Fell",
"beschreibung":"Hund riecht ohne Baden — Trockenshampoo und Hausmittel helfen.",
"schritte":["Trockenshampoo einsprühen","3 Min einwirken lassen","Ausbürsten","Alternative: Backpulver einmassieren, ausklopfen"],
"materialien":"Trockenshampoo für Hunde, Bürste","haeufigkeit":"Nach Bedarf",
"fell_typ":"alle","saison":None,"tipp":"Geruch oft durch Unterkieferdrüsen — dort bürsten!"},
{"id":"fell_ernaehrung", "titel":"Ernährung für gesundes, glänzendes Fell","kat":"Fell",
"beschreibung":"Schönes Fell kommt von innen — die richtigen Nährstoffe machen den Unterschied.",
"schritte":["Lachsöl täglich: 1 TL pro 10 kg","Omega-3-Fettsäuren im Futter","Hochwertige Proteine","Biotin-Supplement bei stumpfem Fell"],
"materialien":"Lachsöl, hochwertiges Futter","haeufigkeit":"Täglich",
"fell_typ":"alle","saison":None,"tipp":"Verbesserung sichtbar nach 48 Wochen."},
# ── KRALLEN ─────────────────────────────────────────────────────
{"id":"krallen_schneiden", "titel":"Krallen richtig schneiden","kat":"Krallen",
"beschreibung":"Zu lange Krallen verändern die Körperhaltung und können schmerzhaft sein.",
"schritte":["Ruhige Umgebung, Hund entspannt","Krallenschneider scharf und sauber","Weißes Fell: rosa Bereich (Quick) sehen → 2mm davor schneiden","Schwarze Krallen: kleine Scheiben bis weiß/grau sichtbar"],
"materialien":"Krallenschneider oder -feile, Blutungspulver für Notfall","haeufigkeit":"Alle 34 Wochen",
"fell_typ":"alle","saison":None,"tipp":"Schneidet man zu kurz: Blutung sofort mit Stärke oder Kaffeepulver stoppen."},
{"id":"krallen_schleifen", "titel":"Krallen-Schleifbrett trainieren","kat":"Krallen",
"beschreibung":"Hund schleift Krallen selbst ab — nie wieder Krallen-Kampf!",
"schritte":["Schleifbrett (Sandpapier auf Brett) vorlegen","Pfote draufstellen belohnen","Kratzbewegung formen","Hund macht es selbst auf Kommando"],
"materialien":"Kratzbrett selbstgebaut: Holz + grobes Sandpapier","haeufigkeit":"Wöchentlich",
"fell_typ":"alle","saison":None,"tipp":"Hinterkrallen schleifen sich beim Laufen oft selbst ab."},
{"id":"krallen_dew", "titel":"Daumenkralle / Wolfskralle","kat":"Krallen",
"beschreibung":"Die Wolfskralle berührt keinen Boden — wächst ins Fleisch wenn vergessen!",
"schritte":["Wolfskralle an Innenseite des Vorderbeins suchen","Einige Hunde haben sie auch hinten","Häufiger schneiden als andere Krallen","Auf Einwachsung achten"],
"materialien":"Krallenschneider","haeufigkeit":"Alle 23 Wochen",
"fell_typ":"alle","saison":None,"tipp":"Wolfskralle ist DER häufigste Pflegefehler — vergiss sie nie!"},
# ── ZÄHNE ───────────────────────────────────────────────────────
{"id":"zahne_putzen", "titel":"Zähne täglich putzen","kat":"Zähne",
"beschreibung":"Zahnfleischentzündung betrifft 80% aller Hunde ab 3 Jahren — täglich putzen hilft.",
"schritte":["Hundezahnpasta auftragen (nie Menschenzahnpasta!)", "Finger einführen → kreisende Bewegung","Schrittweise zur Zahnbürste wechseln","Außenflächen priorisieren"],
"materialien":"Hundezahnpasta (Hühnergeschmack!), Fingerling oder Zahnbürste","haeufigkeit":"Täglich",
"fell_typ":"alle","saison":None,"tipp":"Menschenzahnpasta ist giftig für Hunde — immer Hundezahnpasta!"},
{"id":"zahne_kauen", "titel":"Kauartikel für gesunde Zähne","kat":"Zähne",
"beschreibung":"Kauen reinigt Zähne mechanisch — das natürlichste Zahnpflegeprogramm.",
"schritte":["Roher Rinderknochen (nur roh!)", "Kauknochen aus Rinderhaut", "Dental-Chews (Greenies, Milkbone)", "Kauspielzeug aus Nylon"],
"materialien":"Kauknochen, Dental Chews","haeufigkeit":"Täglich",
"fell_typ":"alle","saison":None,"tipp":"Gekochte Knochen splittern und sind lebensgefährlich — nur ROH!"},
{"id":"zahne_zahnstein", "titel":"Zahnstein erkennen & vorbeugen","kat":"Zähne",
"beschreibung":"Gelb-braune Beläge = Zahnstein → nur Tierarzt oder Ultraschall entfernt das.",
"schritte":["Regelmäßig Zähne kontrollieren (Lippen hochziehen)","Gelb = Plaque (wegputzbar), Braun = Zahnstein (Tierarzt)","Zahnpflege-Diät-Futter verwenden","Tierarzt-Kontrolle 1x/Jahr"],
"materialien":"Zahnpflegefutter, Zahngel","haeufigkeit":"Kontrolle wöchentlich",
"fell_typ":"alle","saison":None,"tipp":"Zahnsteinentfernung in Narkose kostet 300800€ — Vorsorge lohnt sich!"},
# ── OHREN ───────────────────────────────────────────────────────
{"id":"ohren_reinigen", "titel":"Ohren sauber halten","kat":"Ohren",
"beschreibung":"Dreckige Ohren = Ohrenentzündung — regelmäßige Reinigung schützt.",
"schritte":["Äußeren Gehörgang begutachten","Ohrenpflege-Lösung einträufeln","Mit weichem Tuch/Wattebausch reinigen","Nie mit Wattestäbchen tief rein"],
"materialien":"Ohrenreiniger für Hunde, Wattebausch","haeufigkeit":"Alle 24 Wochen",
"fell_typ":"alle","saison":None,"tipp":"Übler Geruch = Entzündung → sofort zum Tierarzt!"},
{"id":"ohren_haare", "titel":"Ohrenbehaarung trimmen","kat":"Ohren",
"beschreibung":"Dichte Ohrenbehaarung staut Luft — Otitis vorprogrammiert bei Hängeohrrassen.",
"schritte":["Haare im Außenohr vorsichtig mit Schere trimmen","Haare im inneren Ohrkanal: Trimmer oder Zupfen (nur wenn gelernt)", "Nach Schwimmen: Ohren immer trocknen","Beim Groomer: Ohren entnehmen lassen"],
"materialien":"Schere, Trimmer, Ohrenpfleger","haeufigkeit":"Monatlich",
"fell_typ":"alle","saison":"sommer","rassengruppe":"Hängeohrrassen","tipp":"Hängeohren (Cocker, Beagle) = höheres Risiko → häufiger kontrollieren."},
{"id":"ohren_check", "titel":"Wöchentlicher Ohren-Check","kat":"Ohren",
"beschreibung":"Früherkennung von Entzündungen spart Schmerzen und Kosten.",
"schritte":["Ohren hochklappen und reinschauen","Normal: hellrosa, leicht glänzend, kaum Geruch","Alarm: rot, geschwollen, dunkler Belag, Kratzen","Bei Auffälligkeiten: Tierarzt binnen 24h"],
"materialien":"Taschenlicht","haeufigkeit":"Wöchentlich",
"fell_typ":"alle","saison":None,"tipp":"Hunde mit Allergien haben häufiger Ohrprobleme."},
# ── AUGEN ───────────────────────────────────────────────────────
{"id":"augen_reinigen", "titel":"Augenränder reinigen","kat":"Augen",
"beschreibung":"Tränenflecken und Schlieren — mit der richtigen Technik verschwinden sie.",
"schritte":["Feuchtes weiches Tuch oder Wattebausch","Von innen nach außen wischen","Nie auf die Hornhaut drücken","Augenreiniger für Hunde optional"],
"materialien":"Augenreiniger, Wattepads","haeufigkeit":"Täglich oder bei Bedarf",
"fell_typ":"alle","saison":None,"tipp":"Braune Tränenflecken bei Weißfell: spezielle Produkte oder Tierarzt."},
{"id":"augen_fell", "titel":"Fell vor Augen schneiden","kat":"Augen",
"beschreibung":"Fell im Gesicht kann Hornhaut kratzen — regelmäßiges Trimmen schützt.",
"schritte":["Abgerundete Schere verwenden","Fell von Augenrändern wegschneiden","Nie beim unruhigen Hund schneiden","Professioneller Groomer alle 46 Wochen"],
"materialien":"Abgerundete Schere","haeufigkeit":"Monatlich",
"fell_typ":"lang,lockig","saison":None,"rassengruppe":"Langhaarrassen","tipp":"Niemals spitze Schere in Augennähe — immer stumpf/abgerundet!"},
{"id":"augen_check", "titel":"Täglicher Augen-Check","kat":"Augen",
"beschreibung":"Augen erzählen viel über die Gesundheit — täglich kurz hinschauen.",
"schritte":["Klar und glänzend = gut","Trüb, gerötet, Ausfluss = Problem","Blinzeln oder Kratzen = Tierarzt","Licht mal mit Taschenlampe auf Pupillen"],
"materialien":None,"haeufigkeit":"Täglich",
"fell_typ":"alle","saison":None,"tipp":"Plötzliche Trübung = Tierarzt HEUTE."},
# ── PFOTEN ──────────────────────────────────────────────────────
{"id":"pfoten_sommer", "titel":"Pfoten im Sommer schützen","kat":"Pfoten",
"beschreibung":"Asphalt im Sommer bis 70°C heiß — Pfotenballen verbrennen in Sekunden.",
"schritte":["Handtest: Hand 5 Sek auf Asphalt → zu heiß wenn unerträglich","Morgens/abends spazieren","Pfotenbalsam schützt und pflegt","Hundeschuhe für empfindliche Hunde"],
"materialien":"Pfotenbalsam, Hundeschuhe","haeufigkeit":"Täglich im Sommer",
"fell_typ":"alle","saison":"sommer","tipp":"Regel: Wenn du barfuß nicht draufstehen kannst, dein Hund auch nicht!"},
{"id":"pfoten_winter", "titel":"Pfoten im Winter / Streusalz","kat":"Pfoten",
"beschreibung":"Streusalz ist ätzend — rissige Ballen und Vergiftung durch Ablecken.",
"schritte":["Vor dem Spaziergang: Pfotenbalsam auftragen","Nach dem Spaziergang: Pfoten mit lauwarmem Wasser abspülen","Zwischen Zehen prüfen: kein Eis","Hundeschuhe bei viel Salz"],
"materialien":"Pfotenbalsam, Wasser, kleines Becken","haeufigkeit":"Täglich im Winter",
"fell_typ":"alle","saison":"winter","tipp":"Musher's Secret ist der Pfoten-Klassiker für den Winter."},
{"id":"pfoten_haare", "titel":"Pfotenhaare trimmen","kat":"Pfoten",
"beschreibung":"Zu lange Haare zwischen den Zehen = Rutschgefahr und Schmutzfänger.",
"schritte":["Haare zwischen Zehenballen mit Schere oder Trimmer","Bündig mit Ballenoberfläche kürzen","Nicht zu kurz — Schutzfunktion erhalten","Auf Wunden und Einschüsse achten"],
"materialien":"Abgerundete Schere oder Trimmer","haeufigkeit":"Alle 34 Wochen",
"fell_typ":"lang,lockig","saison":None,"tipp":"Rutschiger Parkett + lange Pfotenhaare = Kreuzbandriss-Risiko!"},
{"id":"pfoten_ballen", "titel":"Pfotenballen pflegen","kat":"Pfoten",
"beschreibung":"Rissige Ballen sind schmerzhaft — regelmäßige Pflege beugt vor.",
"schritte":["Ballen wöchentlich kontrollieren","Risse eincremen mit Pfotenbalsam","Tiefe Risse: Verbandsmull + Tierarzt","Im Sommer nach Sand/Strand extra eincremen"],
"materialien":"Pfotenbalsam, Wattepads","haeufigkeit":"Wöchentlich (täglich im Winter/Sommer)",
"fell_typ":"alle","saison":None,"tipp":"Bienen-Propolis-Balsam hat heilende Wirkung auf rissige Ballen."},
{"id":"pfoten_reinigen", "titel":"Pfoten nach dem Spaziergang reinigen","kat":"Pfoten",
"beschreibung":"Dreck, Giftstoffe und Parasiten kommen mit rein — kurze Routine macht Unterschied.",
"schritte":["Kleines Becken mit lauwarmem Wasser","Jede Pfote kurz eintauchen und abwischen","Zwischen Zehen kontrollieren","Abtrocknen — feuchte Pfoten = Pilzgefahr"],
"materialien":"Pfoten-Reinigungsbecher, Handtuch","haeufigkeit":"Nach jedem Spaziergang",
"fell_typ":"alle","saison":None,"tipp":"Pfotenwasch-Becher (Dexas Mudbuster) ist der Gamechanger!"},
# ── PARASITEN ───────────────────────────────────────────────────
{"id":"parasiten_zecken_check","titel":"Zecken-Check nach Spaziergang","kat":"Parasiten",
"beschreibung":"Zecken übertragen Borreliose und FSME — täglicher Check rettet Leben.",
"schritte":["Kopf: um Augen, Ohren, Hals","Achseln und Leiste","Zwischen Zehen","Schwanzwurzel und Analbereich","Fell gegen Strich durchkämmen"],
"materialien":"Feine Zeckenzange oder Zeckenhaken","haeufigkeit":"Nach jedem Spaziergang im Gras",
"fell_typ":"alle","saison":"fruehling,sommer,herbst","tipp":"Zecken erst nach 24h übertragen meist Erreger — täglich reicht!"},
{"id":"parasiten_zecken_entfernen","titel":"Zecke richtig entfernen","kat":"Parasiten",
"beschreibung":"Falsch entfernt = Erkrankungsrisiko erhöht — diese Technik ist richtig.",
"schritte":["Zeckenzange nah an Haut ansetzen","Gerade rausziehen, nicht drehen oder quetschen","Kein Öl, Klebstoff oder Nagellack","Bissstelle 2 Wochen beobachten","Bei Rötung: Tierarzt"],
"materialien":"Zeckenzange/-haken","haeufigkeit":"Sofort wenn Zecke gefunden",
"fell_typ":"alle","saison":"fruehling,sommer,herbst","tipp":"Fotografiere Zecke und Bissstelle zur Dokumentation."},
{"id":"parasiten_prophylaxe","titel":"Zecken- und Floh-Prophylaxe","kat":"Parasiten",
"beschreibung":"Spot-on, Tablette oder Halsband — die Vor- und Nachteile.",
"schritte":["Spot-on: monatlich auf Nacken, zuverlässig","Tabletten (NexGard, Bravecto): bequem, für Wasserhunde","Zeckenhalsband (Seresto): 8 Monate Schutz","Natürlich: Kokosöl, Schwarzkümmelöl (schwächerer Schutz)"],
"materialien":"Tierarzt-Empfehlung","haeufigkeit":"Je nach Produkt",
"fell_typ":"alle","saison":"fruehling,sommer,herbst","tipp":"Produkte immer vom Tierarzt — manche Katzen-Präparate töten Hunde!"},
{"id":"parasiten_floh", "titel":"Floh-Befall erkennen und behandeln","kat":"Parasiten",
"beschreibung":"Flöhe sieht man selten — aber Flohkot ist eindeutig.",
"schritte":["Weißes Tuch unter Hund, kämmen → schwarze Punkte = Flohkot","Feuchtes Küchentuch: Flohkot wird rötlich-braun","Hund + Wohnung gleichzeitig behandeln","Flohpuder und -spray für Umgebung"],
"materialien":"Floh-Kamm, Flohspray, Tierarzt-Präparat","haeufigkeit":"Bei Verdacht + Prophylaxe",
"fell_typ":"alle","saison":"sommer","tipp":"80% der Flöhe leben in der Wohnung, nicht am Hund!"},
{"id":"parasiten_wurm", "titel":"Wurm-Prophylaxe","kat":"Parasiten",
"beschreibung":"Würmer übertragen sich auf Menschen — regelmäßige Behandlung schützt die Familie.",
"schritte":["Wurmkur alle 36 Monate (je nach Lebensweise)","Kot-Untersuchung beim Tierarzt","Nach Zeckenbiss: auf Herzwurm testen (Reisehunde)","Hände waschen nach Hundekontakt"],
"materialien":"Wurmmittel vom Tierarzt","haeufigkeit":"Alle 36 Monate",
"fell_typ":"alle","saison":None,"tipp":"Rohfleisch-Hunde öfter entwurmen — rohes Fleisch überträgt Parasiten."},
# ── SAISONAL ────────────────────────────────────────────────────
{"id":"saison_hitze", "titel":"Pflege im Sommer / Hitzeschutz","kat":"Saisonal",
"beschreibung":"Hunde schwitzen durch Pfoten und Hecheln — Überhitzung ist lebensgefährlich.",
"schritte":["Spaziergänge in früh/spät","Immer Wasser dabei","Nasses Tuch auf Bauch kühlt schnell","Niemals im Auto lassen"],
"materialien":"Kühlmatte, Kühlweste, Wassernapf","haeufigkeit":"Täglich im Sommer",
"fell_typ":"alle","saison":"sommer","tipp":"Kurznasige Rassen (Mops, Bulldogge): extreme Hitzegefahr!"},
{"id":"saison_scheren", "titel":"Sommerhaarschnitt — ja oder nein?","kat":"Saisonal",
"beschreibung":"Doppelfell scheren schadet mehr als es nützt — Missverständnis aufklären!",
"schritte":["Doppelfell (Husky, Retriever): NICHT scheren — kühlt selbst","Einfaches langes Fell: kürzen ok","Stattdessen: intensiv ausbürsten, Unterwolle entfernen","Scheren nur wenn verfilzt oder Veterinär rät"],
"materialien":"Bürste, Undercoat-Rake","haeufigkeit":"Saisonal",
"fell_typ":"doppel,lang","saison":"sommer","tipp":"Geschorenes Doppelfell kann sich falsch nachwachsen — Textur-Veränderung dauerhaft."},
{"id":"saison_winter_kalt","titel":"Winterpflege für empfindliche Hunde","kat":"Saisonal",
"beschreibung":"Kleine, kurzhaarige und ältere Hunde frieren — Schutz ist Pflicht.",
"schritte":["Hundemantel für Kurzhaar unter 5°C","Nach draußen: nie nass raus","Fell nach Schnee/Regen sofort trocknen","Alters-Check: Gelenke im Winter öfter warm halten"],
"materialien":"Hundemantel, Pfotenschutz","haeufigkeit":"Täglich im Winter",
"fell_typ":"kurz","saison":"winter","rassengruppe":"Kurzhaarrassen","tipp":"Chihuahua und Co. im Winter wirklich mit Mantel — kein Modethema!"},
{"id":"saison_pollen", "titel":"Allergiezeit / Pollenflug","kat":"Saisonal",
"beschreibung":"Hunde haben Pollenallergien — Jucken, Pfotenlecken, Ohrenprobleme.",
"schritte":["Nach Spaziergang Fell und Pfoten abwischen","Pollen-App nutzen: Hochphasen meiden","Tierarzt: Antihistaminika für Hunde","Hypoallergenes Shampoo verwenden"],
"materialien":"Pollen-App, feuchte Tücher","haeufigkeit":"Täglich im Frühling",
"fell_typ":"alle","saison":"fruehling","tipp":"Pollenallergie kann Hautprobleme verursachen — oft verwechselt mit Futtermittelallergie."},
{"id":"saison_fell_wechsel","titel":"Fellwechsel-Saison meistern","kat":"Saisonal",
"beschreibung":"2x jährlich Haare-Chaos — mit der richtigen Routine überleben alle.",
"schritte":["Täglich bürsten während Fellwechsel","Furminator 2x/Woche (nicht öfter)","Lüfter hilft lose Haare wegzublasen","Professionelles Grooming buchen"],
"materialien":"Furminator, Staubsauger mit Tierhaar-Aufsatz","haeufigkeit":"Täglich MärzMai, SepNov",
"fell_typ":"doppel","saison":"fruehling,herbst","tipp":"Ein 10-Minuten-Bürstsession täglich ersetzt stundenlange Staubsauger-Session."},
# ── GESUNDHEITSVORSORGE ──────────────────────────────────────────
{"id":"gesund_impfung", "titel":"Impfkalender verstehen","kat":"Gesundheitsvorsorge",
"beschreibung":"Welche Impfungen wirklich nötig sind — Kern- vs. Non-Core-Impfungen.",
"schritte":["Kernimpfungen: Staupe, Parvo, Leptospirose jährlich/alle 3 Jahre","Non-Core je nach Lebensweise: Tollwut, Zwinger-Husten","Impfpass immer aktuell halten","Jährlicher Tierarztbesuch zum Check"],
"materialien":"Impfpass","haeufigkeit":"Jährlich",
"fell_typ":"alle","saison":None,"tipp":"Überimpfung gibt es — Titer-Test statt Routine-Booster möglich."},
{"id":"gesund_vorsorge", "titel":"Jährlicher Gesundheitscheck","kat":"Gesundheitsvorsorge",
"beschreibung":"Blutbild + körperliche Untersuchung einmal jährlich — Früherkennung rettet Leben.",
"schritte":["Einmal jährlich Tierarzt auch ohne Anlass","Ab 7 Jahren: halbjährlich","Blutbild gibt Einblick in Organfunktion","Gewicht kontrollieren"],
"materialien":"Tierarzt-Termin","haeufigkeit":"Jährlich (ab 7 J. halbjährlich)",
"fell_typ":"alle","saison":None,"tipp":"Senior-Hunde ab 7 Jahre: halbjährlicher Check lohnt sich sehr."},
{"id":"gesund_gewicht", "titel":"Idealgewicht halten","kat":"Gesundheitsvorsorge",
"beschreibung":"70% aller Hunde in Deutschland sind übergewichtig — Rippen müssen fühlbar sein.",
"schritte":["Rippentest: leicht zu fühlen, nicht zu sehen → Idealgewicht","Taille von oben erkennbar","Leckerlis: max. 10% des Tagesbedarfs","Futtermenge nach Aktivität anpassen"],
"materialien":"Küchenwaage für Futter","haeufigkeit":"Wöchentliche Sichtkontrolle",
"fell_typ":"alle","saison":None,"tipp":"1 kg Übergewicht beim 5-kg-Hund = 20 kg beim Menschen!"},
{"id":"gesund_zahnkontrolle","titel":"Monatliche Mundhöhlen-Kontrolle","kat":"Gesundheitsvorsorge",
"beschreibung":"Zahn- und Zahnfleischprobleme schmerzen — und Hunde zeigen das selten.",
"schritte":["Lippen hochziehen: Zähne und Zahnfleisch sehen","Zahnfleisch: rosa, glatt = gut; rot, geschwollen = Problem","Zähne: weiß/crème = ok; braun = Zahnstein","Mundgeruch plötzlich stark = Tierarzt"],
"materialien":None,"haeufigkeit":"Monatlich",
"fell_typ":"alle","saison":None,"tipp":"Hunde zeigen Zahnschmerzen durch schlechteres Fressen, weniger Spielen."},
{"id":"gesund_körpercheck","titel":"Wöchentlicher Körper-Check","kat":"Gesundheitsvorsorge",
"beschreibung":"5 Minuten wöchentlich — Beulen, Wunden, Veränderungen früh erkennen.",
"schritte":["Kopf bis Schwanz abtasten","Lymphknoten fühlen (Kieferwinkel, Hals, Achsel, Leiste)","Auf Schmerz-Reaktionen achten","Alles unbekannte: Tierarzt"],
"materialien":"Gutes Licht","haeufigkeit":"Wöchentlich",
"fell_typ":"alle","saison":None,"tipp":"Du kennst deinen Hund am besten — vertrau deinem Gefühl."},
{"id":"gesund_senior", "titel":"Pflege für Senior-Hunde","kat":"Gesundheitsvorsorge",
"beschreibung":"Ab 78 Jahren ändern sich Bedürfnisse — angepasste Pflege macht Unterschied.",
"schritte":["Kürzere, öftere Spaziergänge statt langer Touren","Orthopädische Schlafmatte","Gelenk-Supplemente (Glucosamin, Omega-3)","Mehr Körperchecks, öftere Tierarztbesuche"],
"materialien":"Orthopädie-Schlafmatte, Gelenk-Supplements","haeufigkeit":"Täglich",
"fell_typ":"alle","saison":None,"tipp":"Senior-Hund = mehr Pflege, mehr Liebe, mehr Tierarzt — so einfach."},
# ── WELPEN-PFLEGE ────────────────────────────────────────────────
{"id":"welpe_erste_pflege","titel":"Erste Pflege-Einführung beim Welpen","kat":"Welpen-Pflege",
"beschreibung":"Was du jetzt mit dem Welpen übst, macht das spätere Leben einfacher.",
"schritte":["Täglich Pfoten, Ohren, Mund anfassen — immer positiv", "Kämmen einführen mit weicher Bürste","Nagel-Feile zeigen: noch nicht benutzen, nur zeigen","Alles mit Leckerli verbinden"],
"materialien":"Weiche Bürste, Leckerlis","haeufigkeit":"Täglich",
"fell_typ":"alle","saison":None,"tipp":"Was du im ersten Jahr tust, sparst du im Rest des Lebens."},
{"id":"welpe_badegewöhnung","titel":"Welpe ans Baden gewöhnen","kat":"Welpen-Pflege",
"beschreibung":"Erste Baderr-Erfahrung prägt lebenslang — positiv starten!",
"schritte":["Leeres Becken mit Leckerlis auskleiden","Wasser einlaufen lassen: Welpe schaut zu","Pfoten einweichen lassen","Erstes Bad sehr kurz (2 Min), viel Lob"],
"materialien":"Flache Wanne, mildes Welpen-Shampoo","haeufigkeit":"Alle 68 Wochen",
"fell_typ":"alle","saison":None,"tipp":"Wassertemperatur: wie für ein Baby — lauwarm."},
]
_PROMPT_PFLEGE = '''\
Erstelle einen Social-Media-Post über diesen Hunde-Pflegetipp für Ban Yaro (banyaro.app).
Pflegetipp: {titel}
Kategorie: {kat}
Beschreibung: {beschreibung}
Schritte: {schritte}
Materialien: {materialien}
Häufigkeit: {haeufigkeit}
Profi-Tipp: {tipp}
{rasse_kontext}
Antworte NUR als JSON:
{{
"caption": "Post-Text mit Emojis, lehrreich aber locker, max 700 Zeichen. Startet mit einem Fakten-Hook.",
"hashtags": "10 Hashtags kommagetrennt: hundepflege, hundegesundheit, {kat_lower}, banyaro + passende",
"hook": "Erste Zeile die sofort Aufmerksamkeit fängt (Fakt oder Frage)",
"cta": "Frage ans Publikum passend zum Thema",
"visual_brief": "Was man fotografieren/filmen sollte: konkretes Bild das den Tipp zeigt",
"canva_notes": "Infografik-Idee: z.B. Schritt-für-Schritt mit Icons",
"unsplash_query": "23 englische Suchbegriffe für passendes Stockfoto",
"ai_score": <1-5>,
"category": "pflege",
"coaching": "Warum ist dieser Pflegetipp wichtig für die Zielgruppe? Wie macht man das als Video? (12 Sätze)"
}}
'''
_TRAINING_STILE = [
"tutorial", # "So geht das:"
"community", # "Sind grad dran das zu lernen"
"aspirational",# "Das könnte euer Hund auch"
]
_PROMPT_TRAINING = '''\
Erstelle einen Social-Media-Post über den Hundetraining-Tipp "{name}" für Ban Yaro (banyaro.app).
Kategorie: {kat}
Schwierigkeit: {schwierigkeit}
Alter: {alter}
Dauer: {dauer}
Beschreibung: {beschreibung}
Schritte: {schritte}
Profi-Tipp: {tipp}
Stil dieses Posts: "{stil}"
- "tutorial""Schaut, so geht das: [Schritte knapp erklärt]"
- "community""Sind grad dran das zu lernen 🙋 Wer kennt das?"
- "aspirational""Das könnte euer Hund auch — in nur [Dauer]!"
Antworte NUR als JSON:
{{
"caption": "Post-Text passend zum Stil, mit Emojis, max 600 Zeichen, lehrreich aber locker",
"hashtags": "10 Hashtags kommagetrennt ohne #: hundetraining, hundeschule, positivreinforcement, {name_lower}, banyaro + passende",
"hook": "Erste Zeile die sofort Aufmerksamkeit fängt",
"cta": "Frage ans Publikum: z.B. 'Übt ihr das auch gerade?' oder 'Zeigt uns eure Videos!'",
"visual_brief": "Was im Video/Foto zu sehen sein sollte: Person mit Hund beim Training, Leckerli sichtbar, bestimmte Körperhaltung",
"canva_notes": "Text-Overlay: Übungsname + 1 Key-Tipp als Grafik",
"unsplash_query": "dog training {name_en} positive reinforcement",
"ai_score": <1-5>,
"category": "training",
"coaching": "Tipp für die Creatorin: Warum spricht dieser Post die Zielgruppe an? Wie filmt man das am besten? (1-2 Sätze)"
}}
'''
logger = logging.getLogger(__name__)
router = APIRouter()
_PLATFORMS = {"tiktok", "instagram", "both"}
_FORMATS = {"reel", "story", "post", "carousel"}
_STATUSES = {"idea", "draft", "scheduled", "published", "archived"}
_SYSTEM = (
"Du bist Luna, der freundliche Social-Media-Coach von Ban Yaro (banyaro.app). "
"Du begleitest eine junge Content-Creatorin (15 Jahre) dabei, für die Hunde-App "
"auf TikTok und Instagram zu wachsen. "
"Erkläre immer kurz WARUM etwas funktioniert — sie soll lernen, nicht nur kopieren. "
"Ton: locker, ermutigend, auf Augenhöhe — nie von oben herab. "
"Zielgruppe der App: Hundebesitzer in DACH, 2045 Jahre, Fokus Frauen. "
"Antworte immer auf Deutsch."
)
_PROMPT_SUGGESTIONS = '''\
Schlage 5 aktuelle, kreative Content-Ideen für Ban Yaro (Hunde-App) vor.
Heute: {datum} ({saison}).
Bereits verwendete Themen (nicht wiederholen):
{used_topics}
Antworte NUR als JSON-Array:
[
{{
"thema": "Konkretes Thema für einen Post (1 Satz)",
"warum": "Kurze Erklärung warum das gerade gut funktioniert (1-2 Sätze, als Coach)",
"format": "reel|post|story|carousel",
"platform": "tiktok|instagram|both",
"emoji": "1 passendes Emoji"
}}
]
Misch Formate und Plattformen. Aktuelle Trends, Saisonales und Evergreen-Themen kombinieren.
'''
_PROMPT_GENERATE = '''\
Erstelle einen vollständigen Social-Media-Content-Plan für folgenden Post.
Plattform: {platform}
Format: {format}
Thema: {topic}
{breed_info}
Bereits verwendete Themen — NICHT wiederholen, ähnliche Inhalte VERMEIDEN:
{used_topics}
Kategorie-Verteilung der letzten Posts (vermeide überfüllte Kategorien):
{category_stats}
Antworte NUR mit einem JSON-Objekt:
{{
"caption": "Post-Text (plattformgerecht, mit Emojis, max 2200 Zeichen für Instagram / 150 für TikTok)",
"hashtags": "kommagetrennte Hashtags ohne #, 5-15 Stück, mix aus groß/nische",
"hook": "Ersten 1-3 Sätze / Sekunden — sofortiger Aufmerksamkeitsfänger",
"cta": "Call-to-Action am Ende (Frage, Aufforderung zum Kommentieren/Teilen)",
"visual_brief": "Detaillierte Beschreibung was auf dem Foto/Video zu sehen sein soll (Motiv, Stimmung, Licht, Perspektive)",
"image_prompt": "Englischer DALL-E/Midjourney Prompt für ein passendes Illustration/Cartoon (wenn kein eigenes Foto verfügbar)",
"canva_notes": "Empfehlung für Canva: Template-Typ, Textüberlagerungen, Farbstimmung",
"script": {script_field},
"unsplash_query": "2-3 englische Suchbegriffe für Unsplash-Stockfotos",
"ai_score": <Zahl 1-5, geschätztes Engagement-Potenzial>,
"category": "Eine Kategorie aus: tipps|humor|rasse|community|gesundheit|training|saisonal|emotional|behind-the-scenes|quiz",
"coaching": "2-3 Sätze für die Creatorin: Warum dieser Hook? Warum diese Hashtags? Was macht diesen Post stark? Locker und ermutigend formuliert."
}}
'''
_SCRIPT_FIELD_REEL = '''"Videostruktur: Hook (0-3s): ... | Hauptteil (3-25s): Schritt 1... Schritt 2... | CTA (25-30s): ..."'''
_SCRIPT_FIELD_OTHER = "null"
_PROMPT_EVALUATE = '''\
Bewerte und verbessere diesen Social-Media-Entwurf für Ban Yaro (Hunde-App).
Plattform: {platform}
Format: {format}
Entwurf:
{draft}
Antworte NUR mit einem JSON-Objekt:
{{
"caption": "Verbesserter Post-Text",
"hashtags": "Optimierte Hashtags, kommagetrennt ohne #",
"hook": "Verbesserter Hook",
"cta": "Verbesserter CTA",
"visual_brief": "Empfehlung für passendes Bildmaterial",
"image_prompt": "DALL-E/Midjourney Prompt für Illustration",
"canva_notes": "Canva-Tipps",
"script": null,
"unsplash_query": "Unsplash-Suchbegriffe",
"ai_score": <1-5>,
"feedback": "Kurzes Feedback was gut war und was verbessert wurde (2-3 Sätze)"
}}
'''
class GenerateRequest(BaseModel):
platform: str = Field("both", max_length=30)
format: str = Field("post", max_length=30)
topic: str = Field(..., min_length=2, max_length=500)
breed_id: Optional[int] = None
class EvaluateRequest(BaseModel):
platform: str = Field("instagram", max_length=30)
format: str = Field("post", max_length=30)
draft: str = Field(..., min_length=1, max_length=10000)
class StatusUpdate(BaseModel):
status: Optional[str] = Field(None, max_length=50)
scheduled_at: Optional[str] = Field(None, max_length=64)
published_at: Optional[str] = Field(None, max_length=64)
notes: Optional[str] = Field(None, max_length=5000)
post_url: Optional[str] = Field(None, max_length=500)
def _used_topics(limit: int = 30) -> str:
with db() as conn:
rows = conn.execute(
"SELECT topic FROM social_content ORDER BY created_at DESC LIMIT ?",
(limit,),
).fetchall()
if not rows:
return "(noch keine)"
return "\n".join(f"- {r['topic']}" for r in rows)
def _category_stats(limit: int = 30) -> str:
with db() as conn:
rows = conn.execute(
"""SELECT category, COUNT(*) as n FROM social_content
WHERE category IS NOT NULL
ORDER BY created_at DESC LIMIT ?""",
(limit,),
).fetchall()
if not rows:
return "(noch keine Kategorien)"
from collections import Counter
counts = Counter(r["category"] for r in rows)
total = sum(counts.values())
lines = [f"- {cat}: {n}x ({round(n/total*100)}%)" for cat, n in counts.most_common()]
return "\n".join(lines)
def _diversity_warning(limit: int = 20) -> dict | None:
"""Gibt Warnung zurück wenn eine Kategorie >40% der letzten Posts dominiert."""
with db() as conn:
rows = conn.execute(
"""SELECT category FROM social_content
WHERE category IS NOT NULL
ORDER BY created_at DESC LIMIT ?""",
(limit,),
).fetchall()
if len(rows) < 5:
return None
from collections import Counter
counts = Counter(r["category"] for r in rows)
total = len(rows)
for cat, n in counts.most_common(1):
if n / total > 0.4:
_CAT_DE = {
"tipps": "Tipps", "humor": "Humor/Spaß", "rasse": "Rassen-Info",
"community": "Community", "gesundheit": "Gesundheit",
"training": "Training", "saisonal": "Saison-Content",
"emotional": "Emotionale Posts", "behind-the-scenes": "Behind the Scenes",
"quiz": "Quiz/Fragen",
}
missing = [c for c in _CAT_DE if c not in counts]
return {
"dominant": cat,
"dominant_de": _CAT_DE.get(cat, cat),
"pct": round(n / total * 100),
"suggestions": missing[:3],
"suggestions_de": [_CAT_DE.get(c, c) for c in missing[:3]],
}
return None
def _breed_info(breed_id: int | None) -> str:
if not breed_id:
return ""
with db() as conn:
r = conn.execute(
"SELECT name, beschreibung, temperament, groesse FROM wiki_rassen WHERE id=?",
(breed_id,),
).fetchone()
if not r:
return ""
parts = [f"Rasse: {r['name']}"]
if r["beschreibung"]:
parts.append(f"Info: {r['beschreibung'][:300]}")
if r["temperament"]:
parts.append(f"Temperament: {r['temperament']}")
return "\n".join(parts)
async def _ki_complete(prompt: str) -> str:
import ki as ki_module
return await ki_module.complete(
prompt,
system=_SYSTEM,
max_tokens=1200,
requires_premium=False,
)
async def _ki_complete_tracked(prompt: str, user_id: int) -> str:
"""Wie _ki_complete, zählt die Anfrage in ki_daily_calls (source='luna')."""
import datetime as _dt
import ki as ki_module
text = await ki_module.complete(
prompt,
system=_SYSTEM,
max_tokens=1200,
requires_premium=False,
)
today = str(_dt.date.today())
try:
with db() as conn:
conn.execute("""
INSERT INTO ki_daily_calls (user_id, date, count, source) VALUES (?, ?, 1, 'luna')
ON CONFLICT(user_id, date, source) DO UPDATE SET count = count + 1
""", (user_id, today))
except Exception:
pass
return text
def _parse_json(raw: str) -> dict:
import re
try:
return json.loads(raw)
except json.JSONDecodeError:
pass
m = re.search(r"```(?:json)?\s*([\s\S]+?)\s*```", raw)
if m:
try:
return json.loads(m.group(1))
except json.JSONDecodeError:
pass
m = re.search(r"\{[\s\S]+\}", raw)
if m:
try:
return json.loads(m.group(0))
except json.JSONDecodeError:
pass
raise ValueError(f"Kein JSON in Antwort: {raw[:200]}")
# ------------------------------------------------------------------
# GET /api/social/diversity — Kreativitäts-Check
# ------------------------------------------------------------------
@router.get("/diversity")
async def get_diversity(user=Depends(require_social_media)):
warning = _diversity_warning()
with db() as conn:
rows = conn.execute(
"""SELECT category, COUNT(*) as n FROM social_content
WHERE category IS NOT NULL
ORDER BY n DESC""",
).fetchall()
return {
"warning": warning,
"distribution": [dict(r) for r in rows],
}
# ------------------------------------------------------------------
# GET /api/social/suggestions — KI schlägt 5 Themen vor
# ------------------------------------------------------------------
@router.get("/suggestions")
async def get_suggestions(user=Depends(require_social_media)):
from datetime import date
import calendar
today = date.today()
month = today.month
saison = (
"Frühling" if month in (3,4,5) else
"Sommer" if month in (6,7,8) else
"Herbst" if month in (9,10,11) else "Winter"
)
prompt = _PROMPT_SUGGESTIONS.format(
datum=today.strftime("%d.%m.%Y"),
saison=saison,
used_topics=_used_topics(20),
)
try:
raw = await _ki_complete(prompt)
import re
m = re.search(r"\[[\s\S]+\]", raw)
data = json.loads(m.group(0)) if m else []
return data
except Exception as e:
logger.error("Suggestions-Fehler: %s", e)
raise HTTPException(500, f"KI-Fehler: {e}")
# ------------------------------------------------------------------
# POST /api/social/ideas/save — Vorschlag als Idee speichern (zurückstellen)
# ------------------------------------------------------------------
@router.post("/ideas/save", status_code=201)
async def save_idea(data: dict, user=Depends(require_social_media)):
"""Speichert einen KI-Vorschlag als Idee in der DB (status='idea')."""
topic = (data.get("topic") or data.get("thema") or "").strip()
platform = data.get("platform", "both")
fmt = data.get("format", "post")
category = data.get("category")
if not topic:
raise HTTPException(400, "topic fehlt.")
with db() as conn:
cur = conn.execute(
"""INSERT INTO social_content
(created_by, platform, format, topic, status, category, source)
VALUES (?,?,?,?,'idea',?,'saved_suggestion')""",
(user["id"], platform, fmt, topic, category),
)
return {"id": cur.lastrowid, "topic": topic, "status": "idea"}
# GET /api/social/content — alle Einträge
# ------------------------------------------------------------------
@router.get("/content")
async def list_content(
status: Optional[str] = None,
user=Depends(require_social_media),
):
with db() as conn:
if status:
rows = conn.execute(
"SELECT * FROM social_content WHERE status=? ORDER BY created_at DESC",
(status,),
).fetchall()
else:
rows = conn.execute(
"SELECT * FROM social_content ORDER BY created_at DESC LIMIT 200",
).fetchall()
return [dict(r) for r in rows]
# ------------------------------------------------------------------
# POST /api/social/generate — KI-Content generieren
# ------------------------------------------------------------------
@router.post("/generate")
async def generate_content(req: GenerateRequest, user=Depends(require_social_media)):
if req.platform not in _PLATFORMS:
raise HTTPException(400, f"Ungültige Plattform: {req.platform}")
if req.format not in _FORMATS:
raise HTTPException(400, f"Ungültiges Format: {req.format}")
if not req.topic.strip():
raise HTTPException(400, "Thema darf nicht leer sein.")
script_field = _SCRIPT_FIELD_REEL if req.format == "reel" else _SCRIPT_FIELD_OTHER
prompt = _PROMPT_GENERATE.format(
platform=req.platform,
format=req.format,
topic=req.topic,
breed_info=_breed_info(req.breed_id),
used_topics=_used_topics(),
category_stats=_category_stats(),
script_field=script_field,
)
try:
raw = await _ki_complete_tracked(prompt, user["id"])
data = _parse_json(raw)
except Exception as e:
logger.error("Social-Media-Generierung fehlgeschlagen: %s", e)
raise HTTPException(500, f"KI-Fehler: {e}")
with db() as conn:
cur = conn.execute(
"""INSERT INTO social_content
(created_by, platform, format, topic, caption, hashtags,
visual_brief, image_prompt, canva_notes, script, hook, cta,
unsplash_query, ai_score, source, breed_id, coaching, category)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
user["id"], req.platform, req.format, req.topic,
data.get("caption"), data.get("hashtags"),
data.get("visual_brief"), data.get("image_prompt"),
data.get("canva_notes"), data.get("script"),
data.get("hook"), data.get("cta"),
data.get("unsplash_query"), data.get("ai_score"),
"generated", req.breed_id, data.get("coaching"), data.get("category"),
),
)
entry_id = cur.lastrowid
with db() as conn:
row = conn.execute(
"SELECT * FROM social_content WHERE id=?", (entry_id,)
).fetchone()
result = dict(row)
result["feedback"] = data.get("feedback")
return result
# ------------------------------------------------------------------
# POST /api/social/evaluate — eigenen Entwurf bewerten + verbessern
# ------------------------------------------------------------------
@router.post("/evaluate")
async def evaluate_content(req: EvaluateRequest, user=Depends(require_social_media)):
if not req.draft.strip():
raise HTTPException(400, "Entwurf darf nicht leer sein.")
prompt = _PROMPT_EVALUATE.format(
platform=req.platform,
format=req.format,
draft=req.draft,
)
try:
raw = await _ki_complete_tracked(prompt, user["id"])
data = _parse_json(raw)
except Exception as e:
raise HTTPException(500, f"KI-Fehler: {e}")
with db() as conn:
cur = conn.execute(
"""INSERT INTO social_content
(created_by, platform, format, topic, caption, hashtags,
visual_brief, image_prompt, canva_notes, script, hook, cta,
unsplash_query, ai_score, source, notes)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
user["id"], req.platform, req.format,
req.draft[:80] + ("" if len(req.draft) > 80 else ""),
data.get("caption"), data.get("hashtags"),
data.get("visual_brief"), data.get("image_prompt"),
data.get("canva_notes"), data.get("script"),
data.get("hook"), data.get("cta"),
data.get("unsplash_query"), data.get("ai_score"),
"user", data.get("feedback"),
),
)
entry_id = cur.lastrowid
with db() as conn:
row = conn.execute(
"SELECT * FROM social_content WHERE id=?", (entry_id,)
).fetchone()
result = dict(row)
result["feedback"] = data.get("feedback")
return result
# ------------------------------------------------------------------
# PATCH /api/social/content/{id} — Status / Notizen aktualisieren
# ------------------------------------------------------------------
@router.patch("/content/{cid}")
async def update_content(cid: int, data: StatusUpdate, user=Depends(require_social_media)):
updates = {k: v for k, v in data.model_dump().items() if v is not None}
if not updates:
raise HTTPException(400, "Keine Felder zum Aktualisieren.")
if "status" in updates and updates["status"] not in _STATUSES:
raise HTTPException(400, f"Ungültiger Status: {updates['status']}")
cols = ", ".join(f"{k}=?" for k in updates)
values = list(updates.values()) + [cid]
with db() as conn:
conn.execute(f"UPDATE social_content SET {cols} WHERE id=?", values)
return {"ok": True}
# ------------------------------------------------------------------
# DELETE /api/social/content/{id}
# ------------------------------------------------------------------
@router.delete("/content/{cid}", status_code=204)
async def delete_content(cid: int, user=Depends(require_social_media)):
with db() as conn:
conn.execute("DELETE FROM social_content WHERE id=?", (cid,))
# ------------------------------------------------------------------
# Übungen in DB seeden (einmalig beim Import)
# ------------------------------------------------------------------
def _seed_exercises():
with db() as conn:
for u in _UEBUNGEN:
conn.execute(
"""INSERT OR IGNORE INTO training_exercises
(exercise_id, name, kategorie, schwierigkeit, alter_ab,
dauer, beschreibung, schritte, tipp)
VALUES (?,?,?,?,?,?,?,?,?)""",
(u["id"], u["name"], u["kat"], u["schwierigkeit"],
u.get("alter"), u.get("dauer"), u.get("beschreibung"),
json.dumps(u.get("schritte", []), ensure_ascii=False),
u.get("tipp")),
)
def _seed_pflege():
with db() as conn:
for p in _PFLEGE_TIPPS:
conn.execute(
"""INSERT OR IGNORE INTO pflege_tipps
(tipp_id, titel, kategorie, beschreibung, schritte,
materialien, haeufigkeit, fell_typ, saison, rassengruppe, tipp,
fell_pflege_art)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
(p["id"], p["titel"], p["kat"],
p.get("beschreibung"), json.dumps(p.get("schritte", []), ensure_ascii=False),
p.get("materialien"), p.get("haeufigkeit"),
p.get("fell_typ"), p.get("saison"), p.get("rassengruppe"), p.get("tipp"),
p.get("fell_pflege_art")),
)
# UPDATE für bereits bestehende Tipps (idempotent)
if p.get("fell_pflege_art"):
conn.execute(
"UPDATE pflege_tipps SET fell_pflege_art=? WHERE tipp_id=? AND (fell_pflege_art IS NULL OR fell_pflege_art != ?)",
(p["fell_pflege_art"], p["id"], p["fell_pflege_art"]),
)
try:
_seed_exercises()
_seed_pflege()
except Exception:
pass
# ------------------------------------------------------------------
# POST /api/social/training-tip — Trainingstipp-Post generieren
# ------------------------------------------------------------------
@router.post("/training-tip")
async def training_tip(user=Depends(require_social_media)):
# Übung wählen die noch nicht als Social-Post verwendet wurde.
# Per SQL: zuerst eine unbenutzte zufällig wählen, sonst Reset (irgendeine).
with db() as conn:
row = conn.execute(
"""SELECT * FROM training_exercises
WHERE exercise_id NOT IN (
SELECT exercise_id FROM social_content WHERE exercise_id IS NOT NULL
)
ORDER BY RANDOM() LIMIT 1"""
).fetchone()
if not row:
# Alle durch — Reset: irgendeine zufällige nehmen
row = conn.execute(
"SELECT * FROM training_exercises ORDER BY RANDOM() LIMIT 1"
).fetchone()
if not row:
raise HTTPException(404, "Keine Übungen gefunden.")
ex = dict(row)
stil = random.choice(_TRAINING_STILE)
schritte_list = json.loads(ex["schritte"] or "[]")
schritte_text = "\n".join(f" {i+1}. {s}" for i, s in enumerate(schritte_list[:4]))
prompt = _PROMPT_TRAINING.format(
name=ex["name"],
kat=ex["kategorie"],
schwierigkeit=ex["schwierigkeit"] or "Anfänger",
alter=ex["alter_ab"] or "Ab 8 Wochen",
dauer=ex["dauer"] or "5 Min",
beschreibung=ex["beschreibung"] or ex["name"],
schritte=schritte_text,
tipp=ex["tipp"] or "",
stil=stil,
name_lower=ex["name"].lower().replace(" ", ""),
name_en=ex["name"],
)
try:
raw = await _ki_complete_tracked(prompt, user["id"])
data = _parse_json(raw)
except Exception as e:
raise HTTPException(500, f"KI-Fehler: {e}")
stil_label = {
"tutorial": f"So geht's: {ex['name']}",
"community": f"Lernen gerade: {ex['name']} 🙋",
"aspirational": f"Das kann dein Hund auch: {ex['name']}",
}[stil]
with db() as conn:
cur = conn.execute(
"""INSERT INTO social_content
(created_by, platform, format, topic, caption, hashtags,
visual_brief, canva_notes, hook, cta, unsplash_query,
ai_score, source, coaching, category, exercise_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(user["id"], "both", "reel" if stil == "tutorial" else "post",
stil_label, data.get("caption"), data.get("hashtags"),
data.get("visual_brief"), data.get("canva_notes"),
data.get("hook"), data.get("cta"), data.get("unsplash_query"),
data.get("ai_score"), "generated", data.get("coaching"),
"training", ex["exercise_id"]),
)
entry_id = cur.lastrowid
with db() as conn:
row = conn.execute("SELECT * FROM social_content WHERE id=?", (entry_id,)).fetchone()
result = dict(row)
result["exercise_name"] = ex["name"]
result["exercise_kat"] = ex["kategorie"]
result["stil"] = stil
return result
# ------------------------------------------------------------------
# GET /api/social/exercises — alle Übungen mit Nutzungsstatistik
# ------------------------------------------------------------------
@router.get("/exercises")
async def get_exercises(user=Depends(require_social_media)):
with db() as conn:
rows = conn.execute(
"""SELECT e.*,
(SELECT COUNT(*) FROM social_content s
WHERE s.exercise_id = e.exercise_id) as posts_count
FROM training_exercises e
ORDER BY e.kategorie, e.name"""
).fetchall()
return [dict(r) for r in rows]
# ------------------------------------------------------------------
# GET /api/social/breeds — alle Rassen für Autocomplete
# ------------------------------------------------------------------
@router.get("/breeds")
async def social_breeds(user=Depends(require_social_media)):
with db() as conn:
rows = conn.execute(
"SELECT id, name FROM wiki_rassen WHERE ki_enriched=1 ORDER BY name ASC"
).fetchall()
return [dict(r) for r in rows]
# ------------------------------------------------------------------
# GET /api/social/unused-breeds — noch nie in Post verwendete Rassen
# ------------------------------------------------------------------
@router.get("/unused-breeds")
async def unused_breeds(limit: int = 6, user=Depends(require_social_media)):
with db() as conn:
rows = conn.execute(
"""SELECT r.id, r.name FROM wiki_rassen r
WHERE r.ki_enriched = 1
AND r.id NOT IN (
SELECT breed_id FROM social_content
WHERE breed_id IS NOT NULL
)
ORDER BY RANDOM()
LIMIT ?""",
(limit,),
).fetchall()
return [dict(r) for r in rows]
# ------------------------------------------------------------------
# POST /api/social/breed-of-day — "Wusstest du schon?" Post generieren
# ------------------------------------------------------------------
_PROMPT_BREED_DAY = '''\
Erstelle einen begeisternden Social-Media-Post über die Hunderasse "{name}" für Ban Yaro (banyaro.app).
Rassen-Daten aus unserer Datenbank:
- Herkunft: {herkunft}
- Größe: {groesse}
- Gewicht: {gewicht_min}{gewicht_max} kg
- Lebensdauer: {lebensdauer}
- Aktivität: {aktivitaet}
- Erfahrung: {erfahrung}
- Temperament: {temperament}
- Für Kinder geeignet: {kinder}
- Wohnungsgeeignet: {wohnung}
- Beschreibung: {beschreibung}
Antworte NUR als JSON:
{{
"caption": "Post-Text der mit 🐾 Wusstest du schon, wie cool der/die {name} ist? beginnt. Fakten aus den Daten, mit Emojis, max 800 Zeichen.",
"hashtags": "10-12 Hashtags kommagetrennt ohne #: Rassenname, hundeleben, banyaro, passende Eigenschaften",
"hook": "Erste Zeile als Aufmerksamkeitsfänger",
"cta": "Frage ans Publikum passend zur Rasse",
"visual_brief": "Beschreibung: typisches Bild dieser Rasse, Stimmung, Setting",
"canva_notes": "Textüberlagerungen für das Rassenfoto: Rassenname groß, 1-2 Fakten",
"unsplash_query": "2-3 englische Suchbegriffe für diese Rasse",
"ai_score": <1-5>,
"category": "rasse",
"coaching": "Tipp für die Creatorin: Warum ist diese Rasse spannend für die Zielgruppe? (1-2 Sätze)"
}}
'''
@router.post("/breed-of-day")
async def breed_of_day(user=Depends(require_social_media)):
with db() as conn:
rasse = conn.execute(
"""SELECT r.id, r.name, r.herkunft, r.groesse,
r.gewicht_min_kg, r.gewicht_max_kg, r.lebensdauer,
r.aktivitaet, r.erfahrung, r.temperament,
r.kinder_geeignet, r.wohnung_geeignet,
r.beschreibung, r.foto_url
FROM wiki_rassen r
WHERE r.ki_enriched = 1
AND r.beschreibung IS NOT NULL
AND r.id NOT IN (
SELECT breed_id FROM social_content WHERE breed_id IS NOT NULL
)
ORDER BY RANDOM()
LIMIT 1""",
).fetchone()
if not rasse:
raise HTTPException(404, "Alle Rassen wurden bereits verwendet — Reset nötig.")
def _yn(v): return "Ja" if v else "Nein"
prompt = _PROMPT_BREED_DAY.format(
name=rasse["name"],
herkunft=rasse["herkunft"] or "unbekannt",
groesse=rasse["groesse"] or "unbekannt",
gewicht_min=rasse["gewicht_min_kg"] or "?",
gewicht_max=rasse["gewicht_max_kg"] or "?",
lebensdauer=rasse["lebensdauer"] or "unbekannt",
aktivitaet=rasse["aktivitaet"] or "mittel",
erfahrung=rasse["erfahrung"] or "fortgeschritten",
temperament=rasse["temperament"] or "unbekannt",
kinder=_yn(rasse["kinder_geeignet"]),
wohnung=_yn(rasse["wohnung_geeignet"]),
beschreibung=(rasse["beschreibung"] or "")[:400],
)
try:
raw = await _ki_complete_tracked(prompt, user["id"])
data = _parse_json(raw)
except Exception as e:
raise HTTPException(500, f"KI-Fehler: {e}")
with db() as conn:
cur = conn.execute(
"""INSERT INTO social_content
(created_by, platform, format, topic, caption, hashtags,
visual_brief, image_prompt, canva_notes, hook, cta,
unsplash_query, ai_score, source, breed_id, coaching,
category, media_url)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
user["id"], "both", "post",
f"Rasse des Tages: {rasse['name']}",
data.get("caption"), data.get("hashtags"),
data.get("visual_brief"), None,
data.get("canva_notes"), data.get("hook"), data.get("cta"),
data.get("unsplash_query"), data.get("ai_score"),
"generated", rasse["id"], data.get("coaching"),
"rasse", rasse["foto_url"],
),
)
entry_id = cur.lastrowid
with db() as conn:
row = conn.execute("SELECT * FROM social_content WHERE id=?", (entry_id,)).fetchone()
result = dict(row)
result["breed_foto"] = rasse["foto_url"]
return result
# ------------------------------------------------------------------
# GET /api/social/stats — Level / XP
# ------------------------------------------------------------------
_LEVELS = [
(0, 50, "🌱 Rookie", "Anfängerin"),
(50, 150, "✏️ Creator", "Content Creatorin"),
(150, 400, "🌟 Influencer", "Influencerin"),
(400, 800, "🏆 Pro Creator", "Profi"),
(800, 99999,"👑 Star", "Social Media Star"),
]
@router.get("/stats")
async def get_stats(user=Depends(require_social_media)):
with db() as conn:
generated = conn.execute(
"SELECT COUNT(*) FROM social_content WHERE source='generated'"
).fetchone()[0]
published = conn.execute(
"SELECT COUNT(*) FROM social_content WHERE status='published'"
).fetchone()[0]
evaluated = conn.execute(
"SELECT COUNT(*) FROM social_content WHERE source='user'"
).fetchone()[0]
five_star = conn.execute(
"SELECT COUNT(*) FROM social_content WHERE ai_score=5"
).fetchone()[0]
xp = generated * 10 + published * 20 + evaluated * 5 + five_star * 15
lvl = _LEVELS[0]
for l in _LEVELS:
if xp >= l[0]:
lvl = l
idx = _LEVELS.index(lvl)
nxt = _LEVELS[idx + 1] if idx < len(_LEVELS) - 1 else None
return {
"xp": xp, "generated": generated, "published": published,
"evaluated": evaluated, "five_star": five_star,
"level": lvl[2], "level_desc": lvl[3],
"xp_current_min": lvl[0], "xp_next": nxt[0] if nxt else xp,
"next_level": nxt[2] if nxt else None,
}
# ------------------------------------------------------------------
# POST /api/social/pflege-tipp — Pflegetipp generieren (allg. oder rassenspezifisch)
# ------------------------------------------------------------------
@router.post("/pflege-tipp")
async def pflege_tipp(breed_id: Optional[int] = None, user=Depends(require_social_media)):
with db() as conn:
used_ids = {r["exercise_id"] for r in conn.execute(
"SELECT exercise_id FROM social_content WHERE exercise_id IS NOT NULL"
).fetchall()}
# Rassenspezifische Tipps bevorzugen wenn breed_id angegeben
rasse = None
if breed_id:
rasse = conn.execute(
"SELECT name, groesse, beschreibung, herkunft FROM wiki_rassen WHERE id=?",
(breed_id,),
).fetchone()
# LIMIT 100 deckelt das Result-Set ab (Tabelle hat aktuell ~43 Einträge);
# der Python-Filter unten braucht mehrere Kandidaten für Fell-Typ-Auswahl.
tipps = conn.execute(
"SELECT * FROM pflege_tipps ORDER BY RANDOM() LIMIT 100"
).fetchall()
# Noch nicht verwendete bevorzugen
unused = [t for t in tipps if t["tipp_id"] not in used_ids]
pool = unused if unused else list(tipps)
# Bei Rasse: Fell-Typ-Filter wenn möglich
if rasse and rasse["groesse"]:
fell_filter = "kurz" if rasse["groesse"] in ("klein", "mittel") else "lang"
relevant = [t for t in pool if not t["fell_typ"] or t["fell_typ"] == "alle"
or fell_filter in (t["fell_typ"] or "")]
if relevant:
pool = relevant
if not pool:
raise HTTPException(404, "Keine Pflegetipps gefunden.")
t = dict(pool[0])
schritte_list = json.loads(t["schritte"] or "[]")
schritte_text = "\n".join(f" {i+1}. {s}" for i, s in enumerate(schritte_list[:5]))
rasse_kontext = ""
if rasse:
rasse_kontext = (
f"\nRassenspezifisch für: {rasse['name']} "
f"(Größe: {rasse['groesse'] or 'unbekannt'})\n"
"Passe den Post gezielt an diese Rasse an."
)
prompt = _PROMPT_PFLEGE.format(
titel=t["titel"],
kat=t["kategorie"],
beschreibung=t["beschreibung"] or t["titel"],
schritte=schritte_text,
materialien=t["materialien"] or "Standard Pflegeutensilien",
haeufigkeit=t["haeufigkeit"] or "Nach Bedarf",
tipp=t["tipp"] or "",
rasse_kontext=rasse_kontext,
kat_lower=t["kategorie"].lower().replace(" ", "").replace("-", ""),
)
try:
raw = await _ki_complete_tracked(prompt, user["id"])
data = _parse_json(raw)
except Exception as e:
raise HTTPException(500, f"KI-Fehler: {e}")
topic = f"Pflegetipp: {t['titel']}"
if rasse:
topic += f" ({rasse['name']})"
with db() as conn:
cur = conn.execute(
"""INSERT INTO social_content
(created_by, platform, format, topic, caption, hashtags,
visual_brief, canva_notes, hook, cta, unsplash_query,
ai_score, source, coaching, category, exercise_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(user["id"], "both", "post", topic,
data.get("caption"), data.get("hashtags"),
data.get("visual_brief"), data.get("canva_notes"),
data.get("hook"), data.get("cta"), data.get("unsplash_query"),
data.get("ai_score"), "generated", data.get("coaching"),
"pflege", t["tipp_id"]),
)
entry_id = cur.lastrowid
with db() as conn:
row = conn.execute("SELECT * FROM social_content WHERE id=?", (entry_id,)).fetchone()
result = dict(row)
result["pflege_titel"] = t["titel"]
result["pflege_kat"] = t["kategorie"]
result["rasse_name"] = rasse["name"] if rasse else None
return result
# ------------------------------------------------------------------
# GET /api/social/pflege-kategorien — Statistik Pflegetipps
# ------------------------------------------------------------------
@router.get("/pflege-kategorien")
async def pflege_kategorien(user=Depends(require_social_media)):
with db() as conn:
rows = conn.execute(
"""SELECT p.kategorie, COUNT(*) as total,
SUM(CASE WHEN sc.exercise_id IS NOT NULL THEN 1 ELSE 0 END) as used
FROM pflege_tipps p
LEFT JOIN social_content sc ON sc.exercise_id = p.tipp_id
GROUP BY p.kategorie ORDER BY p.kategorie"""
).fetchall()
return [dict(r) for r in rows]
# ------------------------------------------------------------------
# POST /api/social/media — Medien-Upload (Foto/Video)
# ------------------------------------------------------------------
@router.post("/media")
async def upload_media(
file: UploadFile = File(...),
user=Depends(require_social_media),
):
import shutil, uuid, os
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
social_dir = os.path.join(MEDIA_DIR, "social")
os.makedirs(social_dir, exist_ok=True)
ext = os.path.splitext(file.filename or "")[-1].lower() or ".jpg"
fname = f"{uuid.uuid4().hex}{ext}"
path = os.path.join(social_dir, fname)
with open(path, "wb") as f:
shutil.copyfileobj(file.file, f)
return {"url": f"/media/social/{fname}", "filename": fname}