banyaro/backend/welfare_check.py
rene c8ae514c01 Feature: Tierschutz-Check, KI-Züchter-Features, Export, SEO-Update
Tierschutz-System (immer aktiv, nicht abschaltbar):
- welfare_check.py: regelbasierte Prüfung IK, Alter, Deckpause, Wurfanzahl, Genetik
- Grün/Gelb/Rot-Modal bei Wurf anlegen + Probeverpaarung
- Bei kritischem Befund + "Trotzdem fortfahren" → automatische Admin-Mail
- Tierschutz-Check nie durch Nutzer deaktivierbar

KI-Züchter-Features (pro User an/abschaltbar außer Tierschutz):
- routes/zucht_ki.py: 5 Endpunkte — Wurfankündigung, Genetik-Erklärung,
  Paarungsanalyse, Hund-Beschreibung, Jahresbericht
- Toggles in Einstellungen (ki_zucht_* Felder)
- KI-Buttons in litters.js + zuchthunde.js

KI-Routing: Privilegierte Rollen (Admin, Züchter, Moderator, Manager)
nutzen Claude Sonnet primär, lokales LLM als Fallback

Datenexport: routes/breeder_export.py — ZIP mit HTML-Dossier + ODS
(odfpy hinzugefügt in requirements.txt)

Admin-Profil: POST /admin/breeder/create-profile für Schnellprofil ohne
Antragsprozess; Admin-Rolle bleibt erhalten

Wurfformular: Dropdown aus Zuchtkartei für Vater/Mutter mit Auto-Fill;
litters.vater_id + mutter_id als FK auf zucht_hunde

Probeverpaarung: heart-fill Icon + Welfare-Block im Ergebnis

Landing Page: Züchter-Section + Feature-Gruppe, Meta-Tags, JSON-LD,
keywords, softwareVersion 2.1

SEO: llms.txt vollständig überarbeitet, robots.txt Züchter-Pfade,
sitemap.xml um Wurfbörse + Züchter-Profile erweitert

SW by-v474, APP_VER 451
2026-04-28 19:49:54 +02:00

220 lines
10 KiB
Python
Raw 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 — Tierschutz-Check für Züchter
Regelbasierte Prüfung bei Verpaarungen und Wurfanlage.
Läuft immer automatisch, ist nicht abschaltbar.
"""
import logging
from datetime import date
logger = logging.getLogger(__name__)
# VDH-Richtwerte (Orientierung, nicht Gesetz)
MIN_ALTER_MONATE = 18 # Mindestalter Zuchthündin
MAX_ALTER_JAHRE = 8 # Empfohlenes Höchstalter
MAX_WUERFE_LIFETIME = 4 # VDH-Empfehlung
MIN_PAUSE_TAGE_WARN = 365 # 12 Monate Mindestpause (Warnung)
MIN_PAUSE_TAGE_KRIT = 270 # 9 Monate (Kritisch)
IK_WARN_PROZENT = 6.25
IK_KRIT_PROZENT = 12.5
def _level_max(a, b):
order = {"ok": 0, "info": 1, "warning": 2, "critical": 3}
return a if order.get(a, 0) >= order.get(b, 0) else b
def check_welfare(conn, breeder_id: int,
vater_id: int = None,
mutter_id: int = None,
ik_prozent: float = None,
genetic_risks: list = None) -> dict:
"""
Gibt zurück:
{
"level": "ok" | "info" | "warning" | "critical",
"issues": [{"code": str, "level": str, "text": str}],
"ok_points": [str] # Positive Punkte
}
"""
issues = []
ok_pts = []
level = "ok"
# ------------------------------------------------------------------
# 1. Inzuchtkoeffizient
# ------------------------------------------------------------------
if ik_prozent is not None:
if ik_prozent >= IK_KRIT_PROZENT:
issues.append({
"code": "IK_KRITISCH",
"level": "critical",
"text": f"Inzuchtkoeffizient {ik_prozent:.1f}% — kritisch (≥{IK_KRIT_PROZENT}%). "
"Das erhöht das Risiko für erbliche Erkrankungen und Vitalitätsverlust erheblich.",
})
elif ik_prozent >= IK_WARN_PROZENT:
issues.append({
"code": "IK_ERHOEHT",
"level": "warning",
"text": f"Inzuchtkoeffizient {ik_prozent:.1f}% — erhöht (≥{IK_WARN_PROZENT}%). "
"VDH-Empfehlung liegt unter 6,25%.",
})
elif ik_prozent < 2.5:
ok_pts.append(f"Sehr niedriger Inzuchtkoeffizient ({ik_prozent:.1f}%) — genetische Vielfalt ist gut.")
# ------------------------------------------------------------------
# 2. Genetische Risiken (Träger × Träger / Affected)
# ------------------------------------------------------------------
if genetic_risks:
for r in genetic_risks:
if r.get("offspring_risk") and "betroffen" in r.get("offspring_risk", ""):
pct_text = r["offspring_risk"]
if "100%" in pct_text:
issues.append({
"code": f"GENETIK_KRITISCH_{r['marker']}",
"level": "critical",
"text": f"Genetisches Risiko {r['marker']}: {pct_text}. "
"Alle Nachkommen wären betroffen.",
})
elif "50%" in pct_text:
issues.append({
"code": f"GENETIK_HOCH_{r['marker']}",
"level": "critical",
"text": f"Genetisches Risiko {r['marker']}: {pct_text}.",
})
elif "25%" in pct_text:
issues.append({
"code": f"GENETIK_WARN_{r['marker']}",
"level": "warning",
"text": f"Genetisches Risiko {r['marker']}: {pct_text}. "
"Jeder 4. Welpe könnte betroffen sein.",
})
# ------------------------------------------------------------------
# 3. Zuchthündin-Checks
# ------------------------------------------------------------------
if mutter_id:
try:
hund = conn.execute(
"SELECT name, geburtsdatum FROM zucht_hunde WHERE id=?", (mutter_id,)
).fetchone()
if hund:
hund_name = hund["name"] or "Zuchthündin"
# Alterscheck
if hund["geburtsdatum"]:
try:
geb = date.fromisoformat(str(hund["geburtsdatum"])[:10])
alter_tage = (date.today() - geb).days
alter_monate = alter_tage / 30.44
if alter_monate < MIN_ALTER_MONATE:
issues.append({
"code": "MUTTER_ZU_JUNG",
"level": "critical",
"text": f"{hund_name} ist erst {int(alter_monate)} Monate alt. "
f"Mindestalter für Erstzucht: {MIN_ALTER_MONATE} Monate. "
"Frühzucht belastet Körper und Psyche des Tieres erheblich.",
})
elif alter_monate > MAX_ALTER_JAHRE * 12:
issues.append({
"code": "MUTTER_ZU_ALT",
"level": "warning",
"text": f"{hund_name} ist {int(alter_monate / 12)} Jahre alt "
f"(empfohlenes Höchstalter: {MAX_ALTER_JAHRE} Jahre). "
"Ältere Hündinnen tragen ein höheres Geburtsrisiko.",
})
else:
ok_pts.append(f"{hund_name} ist in einem geeigneten Zuchtalter ({int(alter_monate)} Monate).")
except ValueError:
pass
# Wurfanzahl
wuerfe_gesamt = conn.execute(
"SELECT COUNT(*) FROM litters WHERE breeder_id=? AND mutter_id=?",
(breeder_id, mutter_id)
).fetchone()[0]
if wuerfe_gesamt > 5:
issues.append({
"code": "ZU_VIELE_WUERFE",
"level": "critical",
"text": f"{hund_name} hatte bereits {wuerfe_gesamt} Würfe. "
f"Die VDH-Empfehlung liegt bei maximal {MAX_WUERFE_LIFETIME} Würfen pro Hündin.",
})
elif wuerfe_gesamt >= MAX_WUERFE_LIFETIME:
issues.append({
"code": "WUERFE_GRENZE",
"level": "warning",
"text": f"{hund_name} hat bereits {wuerfe_gesamt} Würfe — "
f"das entspricht der VDH-Empfehlung von max. {MAX_WUERFE_LIFETIME} Würfen.",
})
elif wuerfe_gesamt == 0:
ok_pts.append(f"{hund_name} ist noch ohne Vorwürfe.")
# Pause seit letztem Wurf
letzter = conn.execute(
"SELECT MAX(geburt_datum) FROM litters "
"WHERE breeder_id=? AND mutter_id=? AND geburt_datum IS NOT NULL",
(breeder_id, mutter_id)
).fetchone()[0]
if letzter:
try:
letzter_date = date.fromisoformat(str(letzter)[:10])
abstand = (date.today() - letzter_date).days
if abstand < MIN_PAUSE_TAGE_KRIT:
issues.append({
"code": "PAUSE_KRITISCH",
"level": "critical",
"text": f"Letzter Wurf von {hund_name} liegt erst {abstand} Tage zurück "
f"({abstand // 30} Monate). "
"Für die Erholung von Körper und Hormonhaushalt werden "
"mindestens 1215 Monate empfohlen.",
})
elif abstand < MIN_PAUSE_TAGE_WARN:
issues.append({
"code": "PAUSE_KURZ",
"level": "warning",
"text": f"Letzter Wurf von {hund_name}: {abstand // 30} Monate her. "
"VDH empfiehlt mindestens 1215 Monate Pause zwischen Würfen.",
})
else:
ok_pts.append(f"Ausreichende Pause seit letztem Wurf von {hund_name} ({abstand // 30} Monate).")
except ValueError:
pass
except Exception as e:
logger.warning(f"Welfare-Check Mutter fehlgeschlagen: {e}")
# ------------------------------------------------------------------
# Vater-Alterscheck (weniger kritisch, aber der Vollständigkeit halber)
# ------------------------------------------------------------------
if vater_id:
try:
rüde = conn.execute(
"SELECT name, geburtsdatum FROM zucht_hunde WHERE id=?", (vater_id,)
).fetchone()
if rüde and rüde["geburtsdatum"]:
geb = date.fromisoformat(str(rüde["geburtsdatum"])[:10])
alter_monate = (date.today() - geb).days / 30.44
if alter_monate < 12:
issues.append({
"code": "VATER_ZU_JUNG",
"level": "warning",
"text": f"Deckrüde {rüde['name']} ist erst {int(alter_monate)} Monate alt. "
"Empfohlenes Mindestalter: 12 Monate.",
})
except Exception:
pass
# ------------------------------------------------------------------
# Gesamtlevel
# ------------------------------------------------------------------
for issue in issues:
level = _level_max(level, issue["level"])
if level == "ok" and not ok_pts:
ok_pts.append("Alle geprüften Tierschutz-Kriterien sind erfüllt.")
return {"level": level, "issues": issues, "ok_points": ok_pts}