Beim Prod-Nachtest aufgefallen: Bei Schecken meldete die Akte eine Abweichung,
obwohl eingetragener und errechneter Farbschlag identisch angezeigt wurden
(JackJack: „Saphir Schecke" vs. „Saphir Schecke"). Zwei Ursachen:
- Der Vergleich schnitt „Schecke"/„Rex" nur auf der ERRECHNETEN Seite ab, nicht
auf der eingetragenen (die den Zusatz über formatVarietyName trägt). Jetzt
werden beide Seiten gleich normalisiert.
- formatVarietyName hängte den Modifier auch an, wenn der Katalogname ihn schon
trägt („Schwarz Schecke" → „Schwarz Schecke Schecke").
Tests: 3 vitest-Fälle zu formatVarietyName, e2e SCHECKE-MISMATCH mit Gegenprobe
(Mock-Tier mit echter Abweichung zeigt den Hinweis weiter).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Genetik-Engine (bde4ec70, f89e95ad, 2322c2a8):
- Saphir/Platin/Platin-Hell unterscheiden sich NUR in der C-Zygotie und waren im
Katalog identisch (locusToken reduziert auf das dominante Allel) → Saphir war
unerreichbar. Token darf jetzt ein exaktes, ungeordnetes Allelpaar "x/y"
verlangen: Platin C/C, Saphir C/cchm, Platin-Hell C/ch. TS- und Python-Mirror
identisch, Seeds regeneriert, Migration ReseedColorVarietiesGen6Saphir.
- c[hm] wird als Alias auf c[chm] normalisiert (Tippfehler in zwei Charts) —
vorher war Jays Gencode unparsebar und ergab "Zobel-Hell".
- 6 Tiere wechseln den errechneten Farbschlag (5x Platin→Saphir, Jay→Zobel).
Importer-Logik:
- renameTo-Notiz-Sweep: der alte Name blieb in Wurf-Notizen stehen ("Blacky +
Kruke") — jetzt wortgenau ersetzt, mit vier Guards gegen Kollateralschaden.
- Neuer Resolution-Schlüssel goHomeDate (autoritativ, zieht GivenAway nach) und
spottingType; Gencode-Overrides gewinnen jetzt bei präzisem Match (vorher
wurden Entscheidungen stumm verschluckt, z. B. Eliza und Chris).
- parse_date verwirft implausible Jahre (JackJack hatte 1310-05-13).
- extract.py erkennt Stammbaum-Blöcke auch ohne Stern vor dem Geburtsdatum
(Bijou bekam dadurch den Nachbar-Ast als Mutter).
- Backend-Ingest überträgt SpottingType (nur bei Payload-Wert).
Daten (conflict-decisions.json, re-ingest-stabil): Kuke-Merge + Zuchtname,
Jamie- und Sakura-Dubletten, Phantom-Tier "Unbekannt", Eltern von Kathlin,
Fast Boy/Ziwa und dem Q4-/TS-Wurf, Eliza-Gencode, Merle-Abgabe, JackJack.
Frontend (24522f5f): Abgabedatum steht in der Tier-Akte jetzt direkt unter dem
Abnehmer und hängt nicht mehr am Status.
Tests: +25 Checks test_merge_resolve, +7 test_genotype, +8 genetics.test.ts,
neuer Extract- und Ingest-Test, e2e GOHOME-ROW. Alles grün.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Suche: Volltext über Nachricht/Frage/Antwort/Changelog/Name/Kategorie, plus
Kategorie-Filter (KI-gesetzte Kategorie als Chip + Auswahlmenü), je innerhalb der
aktiven Ansicht.
- 👍/👎 auf gelösten Tickets: 👍 speichert die Rückmeldung, 👎 öffnet das Ticket
wieder (zurück in die Rückfragen) und bittet um die fehlende Info.
- Melde-Fenster: schlägt beim Tippen ähnliche bereits gelöste Tickets vor
(Wort-Überlappung + Bonus bei gleichem Tier/Wurf/Kontakt) — Self-Service.
Backend: Feedback.Category + Feedback.Helpful (+ PUT-Handling), Migration
FeedbackCategoryAndHelpful (beide nullable). Tests: 260 Backend grün, e2e Tickets
Desktop +4 (Suche/Kategorie/👍/👎), vitest 149.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sobald ein Ticket eine Antwort hat, ist der ursprüngliche Ticket-Text nicht
mehr bearbeitbar. Editierbar ist dann nur noch die neueste Antwort — und auch
die nur, bis die KI darauf reagiert hat (Status verlässt „Answered"). Ältere
Antworten im Verlauf (thread) bleiben wie bisher schreibgeschützt.
Regressionstest + Answered-Mock-Ticket ergänzt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Ticket-Texte (Frage/Antwort/Nachricht/Changelog/Verlauf) rendern eine
Markdown-Teilmenge: **fett**, `code`, Aufzählungen, Links, Tier-/Ticket-IDs
- Offene Rückfrage (NeedsInfo): nicht bearbeitbar — die Züchterin soll antworten
- Beantwortet (Answered): Antwort nachträglich änderbar, bevor die KI übernimmt
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tickets können jetzt eine Rückfrage (Question) tragen und die Züchterin kann
in-app antworten. Feedback um FK-freie Felder Question/Answer/AnsweredAt erweitert
(überlebt Ingest-Wipe); Status-Lebenszyklus Open → NeedsInfo (Rückfrage gestellt)
→ Answered (beantwortet) → Resolved. Migration AddFeedbackQuestionAnswer.
PUT /feedback/{id}: question → NeedsInfo, answer → Answered+AnsweredAt; DTO gibt
die Felder zurück. Tickets-Seite (/hilfe/tickets) zeigt die Rückfrage hervorgehoben
und bietet ein Antwort-Feld + „Antworten"; Status-Badges Offen/Rückfrage offen/
Beantwortet/Gelöst.
Tests: FeedbackEndpointTests (Frage→NeedsInfo, Antwort→Answered, übersteht Ingest),
e2e tickets.spec.ts. dotnet/vitest/playwright grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die ~1095 geparsten Verträge werden jetzt als echte SaleContract-Records erzeugt,
sodass die Verträge-Seite gefüllt ist (statt 0). enrich_from_contracts() liefert
zusätzlich eine saleContracts-Liste (deterministische Id aus Dateiname, Käufer-
ContactId, Preis, Abgabe-/Vertragsdatum, FileName, gematchte Tier-Ids); main()
schreibt sie als Top-Level-Key in resolved_import.json. IngestResolvedService legt
SaleContract + SaleContractAnimal an (FK-sicher nach Kontakten/Tieren, unbekannte
Links übersprungen) — Tabelle wird wie gehabt gewischt und aus dem Payload neu
befüllt (idempotent).
Ergebnis: 1033 Verträge (611 mit ≥1 Tier, alle datiert; 54 Dateinamen-Dubletten
zusammengefasst, 7 datumlose + 1 ohne Käufer übersprungen). Kein Schema-Change
(datumlose übersprungen statt Spalten nullable → keine Migration).
DOCX-Download: importierte Verträge haben keine Word-Datei im contract-storage →
DTO.HasFile=false, Frontend blendet den Word-Button aus (Hinweis „Keine Word-
Datei"), PDF wird weiterhin generiert. Download-Endpoint liefert sauber 404 statt
500 bei fehlender Datei.
Tests: dotnet 213, vitest 129, playwright 16 (neuer vertraege.spec.ts), Python grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Herkunfts-Verlauf nennt jetzt auch, wenn ein Wert aus einer Quelle VERWORFEN
wurde — mit Grund und was stattdessen verwendet wurde (⚠-Markierung), z. B.:
- „⚠ Vater-Kandidat ‚Danielle' (*04.03.2020) verworfen — falsches Geschlecht für
die Vaterrolle; ‚Hagrid Rubeus' verwendet." (Molly)
- „⚠ Geschlecht weiblich aus ‚Wurfchronik.docx' verworfen — abweichend; männlich
aus ‚Alberto Kids.xlsx' verwendet (Mehrheit)." (Solice)
- „⚠ Vater ‚Jasper Jacob' (*2018) verworfen — unplausibel (7 Jahre älter); kein
Ersatz." / DOB-Remap: „⚠ Geburtsdatum 22.08.2018 verworfen — per manueller
Entscheidung korrigiert; 14.07.2019 verwendet." (Kazuya)
Import: generischer _format_discard()-Helfer; Verwerfungen aus Mehrheitsentscheid,
pick_parent_ref-Ablehnungen (explain_pick_rejections), Rollen-Normalisierung,
Eltern-Alter-Sanity-Check und DOB-Remaps werden über _discarded/_prov_args an die
Tiere gehängt; Gerbil-Provenance wird in einem finalen Pass gebaut (nachdem alle
Verwerfungen feststehen). Frontend hebt ⚠-Zeilen ab (+ Legende).
Tests grün (python merge/extract, vitest 129, playwright 8, dotnet 212).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Herkunft liest sich jetzt wie eine History: pro Feld wird genannt, aus
welcher Quelldatei der Wert kam, plus Merge-/Entscheidungs-/Wurfchronik-Schritte.
Beispiel: „In ‚X.xlsx' gefunden." → „Geburtsdatum (…) aus ‚X.xlsx'." → „Auch in
‚Y.docx' gefunden → zusammengeführt." → „Eltern über Position erkannt (Quelle …)."
Import: build_entity_provenance() um history[] erweitert; ein field_source-
Tracking im Gerbil-Dedup hält fest, welcher Datensatz/welche Datei jedes Feld
(DOB/Genotyp/Geschlecht/Farbschlag/Sterbedatum) lieferte — auch über Empty-Fill
und Mehrheitsentscheid hinweg. Kontakte und Würfe erhalten analoge, datei-
attribuierte Verläufe. Kein DB-Migration nötig (history liegt im Provenance-JSON).
Frontend: ProvenanceDialog rendert den Verlauf als nummerierte Timeline.
Tests: history wird erzeugt, nennt Dateien, überlebt Ingest (dotnet 212,
vitest 129, playwright 17, python merge/extract grün).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neuer „Datenherkunft"-Button in der Rennmausakte öffnet einen Dialog, der zeigt,
aus welchen Quellen der Eintrag erzeugt wurde: Quelldateien (Stammbäume +
Wurfchronik), Anzahl zusammengeführter Datensätze, Eltern-Herleitung
(Methode/Konfidenz) und Hinweise (z. B. „per manueller Entscheidung zugeordnet",
„Konflikt gelöst", „aus N Datensätzen zusammengeführt").
Import: build_provenance() in merge_and_resolve.py sammelt die Herkunft über alle
deduplizierten Datensätze und schreibt sie als Provenance-JSON je Tier in
resolved_import.json. Backend: Gerbil.Provenance (nullable text) + Migration
AddGerbilProvenance, gemappt im Ingest und im GerbilDto zurückgegeben. Frontend:
ProvenanceDialog + Typen + Strings.
Tests: Ingest-Round-trip (vorhanden/abwesend), e2e provenance.spec.ts.
dotnet(212)/vitest(129)/playwright/tsc/eslint grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rechtsklick auf eine Maus im Stammbaum öffnet ein Kontextmenü mit „ID kopieren"
(Clipboard + Toast) und „Fehler melden". Zusätzlich „Fehler melden"-Buttons in
der Rennmausakte und der Wurf-Ansicht. Der Dialog erfasst eine Beschreibung und
sendet sie samt Debug-Kontext (Ansicht, Tier-/Wurf-ID + Name, URL, Zeitstempel,
User-Agent) an POST /feedback; gespeichert in einer neuen Feedback-Tabelle.
Backend: Feedback-Entity (lose nullable GerbilId/LitterId ohne FK), Endpoints
POST/GET /feedback, EF-Migration AddFeedback. Die Tabelle wird vom Import-Ingest
NICHT geleert — Feedback überlebt Re-Ingests (Test deckt das ab).
Tests: FeedbackEndpointTests (persistiert, 400 bei leer, übersteht Ingest-Wipe);
e2e feedback.spec.ts. tsc/eslint/vitest(129)/playwright(6)/dotnet(212) grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vollgeschwister-Verpaarung (Vater & Mutter aus demselben Wurf) ist in der
Gerbil-Zucht häufig. Bisher zeigten Diagramm und Import sie nicht korrekt.
Import (merge_and_resolve.py):
- Wurf-Dedup gehärtet: asymmetrischer Merge nur bei kompatiblen Eltern-Namen;
bei Date=None nur mit positivem Namens-Match (verhindert blindes Verschmelzen
unverwandter datumloser Stubs). Falsch-Merges 112 -> 70.
- Parent-Resolver robust: Gender ist Präferenz statt hartem Filter, sodass
vertauschte Eltern (z. B. weibliches Tier in der Vater-Position) trotzdem
auflösen. Aufgelöste Eltern 200/206 -> 240/260.
- Neue Rollen-Normalisierung: weist jede Maus rollenrichtig nach Geschlecht zu,
entfernt Selbst-Verpaarungen und unmögliche Doppelrollen.
Selbst-Verpaarungen 16 -> 0, Gender-Rollen-Fehler 30 -> 0.
- Reine Helfer (litter_compatible/assign_parent_roles/names_*) auf Modulebene
extrahiert und in test_merge_resolve.py (26 Tests) abgesichert.
Frontend (pedigree):
- buildAnimal markiert den Mutter-Knoten, wenn beide Eltern dieselbe litterId
teilen (Vollgeschwister). toRawNodeDatum führt dessen Vorfahren-Ast zu einem
Verweis-Knoten zusammen ("Geschwister von <Vater>, Eltern siehe oben"); die
Vaterlinie zeigt die gemeinsamen Großeltern einmal.
- Druck-Ahnentafel bleibt vollständig (ignoriert die Marke).
- Build-Tests (+3) und e2e-Test (Inzucht-Kind-Fixture) ergänzt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Tiere-Liste (/rennmaeuse) filtert serverseitig auf isResident==true
(eigener Clan = Bestand) und blendet externe Ahnen (nur für den Stammbaum
erfasste Fremdtiere) standardmäßig aus — passend zu Julians Modell
(Kleine Chaoten = Bestand, extern = stammbaum-only).
- Neuer Schalter „Externe Ahnen einblenden“ (Default aus). An -> isResident-
Filter entfällt, externe Tiere erscheinen mit. Serverseitig (Gridify), damit
Paging und Trefferzahl korrekt bleiben. „Filter zurücksetzen“ setzt ihn zurück.
- Subtile „Extern“-Markierung (gestricheltes Badge) auf externen Tieren in
Liste UND Detailseite, mit Tooltip-Erläuterung.
- Gerbil-Typ um isResident (optional bool) ergänzt; CreateGerbil ebenso.
Deutsch via de.ts (LF), mobil + Laptop. e2e-Mock: gerbil()-Factory setzt
isResident=true; Hilde & Max als externe Ahnen (isResident=false). Neue
e2e bestand.spec (Default blendet aus; Toggle zeigt + Extern-Markierung).
Gate grün: build, eslint, vitest 78, e2e 84.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Neuer Bereich /webseite im Manager (Nav: "Mehr"-Blatt) zur Pflege der 6
Webseiten-Seiten gegen Pams WEB-0-Backend (LAN-only, keine Auth):
- Übersicht: alle Seiten mit Status (Entwurf/Veröffentlicht).
- Seiten-Editor: Titel + SEO-Beschreibung + Status speichern.
- Geordneter Block-Editor: Blöcke hinzufügen (Überschrift, Textabschnitt/
Markdown, Bild, Galerie, Kontaktangaben), bearbeiten, nach oben/unten
verschieben (PUT .../blocks/order), löschen. Die dynamische AbgabetiereList
ist nicht manuell anlegbar; nur ihr Einleitungstext ist editierbar.
Vertraege: GET /pages, GET /pages/{slug}, PUT /pages/{id},
POST /pages/{pageId}/blocks, PUT /blocks/{id}, DELETE /blocks/{id},
PUT /pages/{pageId}/blocks/order. Enums als Strings (Draft/Published, Heading...).
Deutsch via de.ts (LF beibehalten), mobile-first. e2e-Mock um die CMS-Routen
erweitert (zustandsbehaftet) + Smoke (Block anlegen/bearbeiten/verschieben,
bleibt erhalten). Gate gruen: build, eslint, vitest 69, e2e 68.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>