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
220 lines
10 KiB
Python
220 lines
10 KiB
Python
"""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 12–15 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 12–15 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}
|