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
This commit is contained in:
parent
91340be5a3
commit
c8ae514c01
20 changed files with 2129 additions and 200 deletions
220
backend/welfare_check.py
Normal file
220
backend/welfare_check.py
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
"""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}
|
||||
Loading…
Add table
Add a link
Reference in a new issue