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:
rene 2026-04-28 19:49:54 +02:00
parent 91340be5a3
commit c8ae514c01
20 changed files with 2129 additions and 200 deletions

View file

@ -685,6 +685,30 @@ window.Page_settings = (() => {
_loadBreederCard();
}
// ----------------------------------------------------------
// KI-Toggle-Zeile (Hilfsfunktion für Züchter-Card)
// ----------------------------------------------------------
function _kiToggleRow(key, label, user) {
const active = user[key] !== 0;
return `
<div style="display:flex;justify-content:space-between;align-items:center;
padding:var(--space-2) 0;font-size:var(--text-sm)">
<span>${_esc(label)}</span>
<button class="by-toggle ki-toggle-btn" data-key="${_esc(key)}"
data-active="${active ? '1' : '0'}"
style="position:relative;display:inline-block;width:44px;height:24px;
border:none;border-radius:12px;cursor:pointer;flex-shrink:0;
background:${active ? 'var(--c-primary)' : 'var(--c-border)'};
transition:background .2s">
<span class="by-toggle-thumb"
style="position:absolute;top:2px;left:${active ? '22px' : '2px'};
width:20px;height:20px;border-radius:50%;
background:#fff;transition:left .2s;
box-shadow:0 1px 3px rgba(0,0,0,.3)"></span>
</button>
</div>`;
}
// ----------------------------------------------------------
// ZÜCHTER-CARD — asynchron laden und in Slot rendern
// ----------------------------------------------------------
@ -722,7 +746,30 @@ window.Page_settings = (() => {
${rolle === 'breeder' && profile ? `
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" style="margin-top:var(--space-3)">
${UI.icon('pencil-simple')} Profil bearbeiten
</button>` : ''}`;
</button>` : ''}
${rolle === 'admin' && !profile ? `
<button class="btn btn-primary btn-sm" id="breeder-admin-create-btn" style="margin-top:var(--space-3)">
${UI.icon('plus')} Admin-Züchterprofil anlegen
</button>` : ''}
${rolle === 'admin' && profile ? `
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" style="margin-top:var(--space-3)">
${UI.icon('pencil-simple')} Profil bearbeiten
</button>` : ''}
${profile ? `
<div style="margin-top:var(--space-4);padding-top:var(--space-3);border-top:1px solid var(--c-border)">
<div style="font-size:var(--text-xs);font-weight:600;color:var(--c-text-secondary);
text-transform:uppercase;letter-spacing:0.05em;margin-bottom:var(--space-3)">
KI-Züchter-Assistenz
</div>
${_kiToggleRow('ki_zucht_wurfankuendigung', 'Wurfankündigungen schreiben', _appState.user || {})}
${_kiToggleRow('ki_zucht_genetik', 'Genetik-Erklärung für Käufer', _appState.user || {})}
${_kiToggleRow('ki_zucht_paarung', 'Paarungsanalyse', _appState.user || {})}
${_kiToggleRow('ki_zucht_beschreibung', 'Hunde-Beschreibungen', _appState.user || {})}
${_kiToggleRow('ki_zucht_jahresbericht', 'Jahresauswertung', _appState.user || {})}
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-2)">
${UI.icon('info')} Der Tierschutz-Check läuft immer automatisch und ist nicht abschaltbar.
</div>
</div>` : ''}`;
} else if (breeder_status === 'pending') {
statusBadge = `<span class="badge" style="background:#f59e0b;color:#fff">
${UI.icon('hourglass')} Antrag wird geprüft
@ -770,6 +817,48 @@ window.Page_settings = (() => {
slot.querySelector('#breeder-edit-profile-btn')?.addEventListener('click', () =>
_openBreederEditModal(profile)
);
slot.querySelector('#breeder-admin-create-btn')?.addEventListener('click', async (e) => {
const btn = e.currentTarget;
btn.disabled = true;
btn.textContent = 'Wird angelegt…';
try {
await API.breeder.adminCreateProfile();
UI.toast.success('Admin-Züchterprofil angelegt. Bitte Seite neu laden.');
_loadBreederCard();
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Anlegen.');
btn.disabled = false;
btn.innerHTML = `${UI.icon('plus')} Admin-Züchterprofil anlegen`;
}
});
// KI-Toggle-Handler
slot.querySelectorAll('.ki-toggle-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const key = btn.dataset.key;
const active = btn.dataset.active === '1';
const newVal = active ? 0 : 1;
// Optimistisches UI-Update
btn.dataset.active = newVal ? '1' : '0';
btn.style.background = newVal ? 'var(--c-primary)' : 'var(--c-border)';
const thumb = btn.querySelector('.by-toggle-thumb');
if (thumb) thumb.style.left = newVal ? '22px' : '2px';
try {
const updated = await API.patch('/profile', { [key]: newVal });
if (_appState?.user) _appState.user[key] = newVal;
UI.toast.success(newVal ? 'KI-Feature aktiviert.' : 'KI-Feature deaktiviert.');
} catch (err) {
// Revert
btn.dataset.active = active ? '1' : '0';
btn.style.background = active ? 'var(--c-primary)' : 'var(--c-border)';
if (thumb) thumb.style.left = active ? '22px' : '2px';
UI.toast.error(err?.message || 'Einstellung konnte nicht gespeichert werden.');
}
});
});
}
// ----------------------------------------------------------