Die Züchterin hat 24 neue/aktualisierte Stammbaum-xlsx geliefert (Ordner
"neuestammbäume"); sie liegen jetzt im kanonischen Quellverzeichnis
Sttammbäume (12 neue Charts, 7 aktualisierte, 3 identisch, das inhaltsgleiche
"Picus Son (2)" ausgelassen). Prod ist per Upload-Ingest aktualisiert:
2372 -> 2451 Tiere, 916 -> 965 Würfe, 432 -> 507 Fotos, 2198 -> 2275 Tiere
mit Geburtsdatum. Overrides/verified-Zeilen, manuelle Tiere und Tickets
haben den Ingest unverändert überlebt.
Zwei Datenfehler, die die neuen Charts aufgedeckt haben — datengetrieben und
re-ingest-stabil gefixt statt an der globalen Heuristik zu drehen:
- litterChildren kennt jetzt `add` [Name | {name, dob}] als Gegenstück zu
`keep`: hängt ein Jungtier an DIESEN Wurf und entfernt den alten Wurf, wenn
er dadurch kinderlos UND virtuell ist. Nötig, weil "Pukas Kids" Akanes
Eltern komplett UNTER ihren Block setzt (N80 Roni = Vater, N81 Fumi =
Mutter) — _reconstruct_parents griff eine Zeile zu hoch, paarte Irish
Coffee (Bonapartes Mutter) mit Roni und riss Akane aus dem Z21-Wurf in
einen Phantom-Wurf, der in der Wurfchronik auftauchte (Ticket 88389f8e).
- Merle: durch das neue Geburtsdatum (18.06.2023) mergt der addAnimals-Stub
in den Chart-Datensatz und verliert dabei sein isResident -> expliziter
resolutions-Override (Ticket 36a3fcde/a8f11ac0, Züchterin: Zuchttier).
Außerdem: Excel legt neben Fotos teils EMF/WMF-Vektorvorschauen ab, die
Browser nicht darstellen können (kaputte Bildkachel in der Tier-Akte) ->
extract._attach_photos überspringt .emf/.wmf (5 Fotos betroffen).
Regressionstests für alle drei Punkte; alle Python-Suites grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Daten-Fixes (conflict-decisions.json, re-ingest-stabil) für ~30 Tickets:
Merges (Jamie/Hiro/Mino/Jana/Blacky/Sakura/Malou/Socke→Marty), Eltern-Korrekturen
(Jacky/Idefix/Ichika/Roni/Ethan), Kruke→Kuke (+ Todesdatum), Targa-Wurf R14 + Druna,
Stacy/Merle/Domi/Eliza; Joghurt-Phantomwurf entfernt.
Code-Fixes:
- Gaida & alle Verstorbenen: Status wird aus Todesdatum/Abgabe abgeleitet
(Program.cs Startup-Sweep heilt Altfälle; IngestResolved re-derived nach Freeze).
- CoCo: Scheckungsart wird bei jeder Schecke angezeigt (Platzhalter wenn leer).
- M-Wurf/Gale: über-gemergte Fremdtiere via neuem litterChildren-Override entfernt.
- renameTo eltern-verknüpfungssicher (Quell-Name im Index); dateOfDeath als Override.
Prod-fähige Triage (API):
- GET /feedback/{id} + GET /feedback?status= (kein 2-MB-Dump).
- POST /import/ingest-resolved/upload (multipart) → Ingest gegen Prod ohne SSH.
Tests: 280 Backend, 149 Frontend, alle Python, betroffene Playwright grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Neuer isResident-Override im Import (merge_and_resolve, NACH dem Dedup, damit die
Zusammenführung ihn nicht überschreibt). Matcht über externalRef (präzise, auch für
namenlose Tiere) oder normalize(call-name)+dob. Ein Nicht-Bestandstier verliert den
eigenen Zucht-Breeder.
- conflict-decisions.json: Bentley, Alexandria, Bugatti = nur Vorfahren (isResident=false),
bestätigt von der Züchterin (Tickets 09bcac78 / 45cc501b).
- Regressionstests ergänzt/aktualisiert (Bentley/Alexandria/Bugatti isResident; Danielle
Vater Makoto; Catelyn Eddard×Milena; Gaida Zhuāngzǐ×Zaibunissa). Alle python test_*.py grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
genotype.py: Python-Port von genotypeToFarbschlag (0 Abw. über 3402 Genotypen).
resolve_color_and_genotype: bei vorhandenem Genotyp gewinnt der berechnete Farbschlag
(Goldfuchs≠Gold, Dilute Agouti/Anthrazit, Blaufuchs statt -schimmel bei (schimmel),
spsp statt Schecke). Mamta Mini: Ee + Eltern Geely×Gaida am Wurf. Regressionstests je Fall.
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>
Neuer Parser extract_contracts.py liest die ~1,4k Abgabevertrags-DOCX
(\truenas\…\Verträge): er extrahiert aus dem Dokument-Body (zuverlässiger als
die Dateinamen) Käufer, Tier(e), Farbschlag, Abgabedatum und Preis — robust
gegen Word-Run-Splits (z. B. „F r au"/„3 0,00"); überspringt Vorlage,
Abstammungsnachweise und als .docx getarnte .doc.
enrich_from_contracts() in merge_and_resolve.py: Käufer werden als Kontakte
(IsReceiver) angelegt/zusammengeführt; Tiere werden KONSERVATIV per Rufname
(+ DOB-Jahr bei Mehrdeutigkeit) auf eigene Bestandstiere gematcht und erhalten
ReceiverContactId, GoHomeDate und Status „abgegeben" — nur wo nicht bereits
gesetzt; Konflikte werden geloggt, nicht überschrieben. Jede Übernahme bekommt
eine Herkunfts-Zeile („Abgabe an … aus Vertrag … übernommen.").
Ergebnis: 1095 Verträge → 783 Tier-Treffer (400 mehrdeutige übersprungen),
274 neue Abnehmer-Kontakte, 153 Tiere mit Abnehmer, 49 mit Abgabedatum,
23 neu „abgegeben". Keine Backend-/Frontend-Änderung nötig (Akte zeigt Abnehmer/
Abgabedatum/Herkunft bereits). SaleContract-Records bewusst nicht erzeugt
(bräuchte Migration + ingest-sichere Id — späterer Schritt).
Tests: test_extract_contracts.py (Dateiname/Body/Run-Split/Skip-Regeln) + alle
bestehenden grün; dotnet 212.
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>
Die in 03ad9e6 eingeführte DOB-Bevorzugung war zu grob: ein datierter, aber
geschlechts-falscher Ref (z. B. weibliche „Danielle") gewann den Vater-Slot
gegen einen undatieren männlichen/unbekannten (z. B. „Hagrid Rubeus") — wodurch
Mollys korrekte Mutter Arya Stark in der Rollen-Normalisierung verloren ging.
pick_parent_ref wertet jetzt zuerst das (aus den Tierdaten ermittelte) Geschlecht
für die Rolle: ein klar falsches Geschlecht wird stark abgewertet, erst danach
zählt plausibles DOB > kein DOB > unplausibles DOB. Fixt Molly (Hagrid × Arya)
UND behält Solice (Lui × Molly = Geschwister von Silvain). Keine Abdeckungs-
Regression (0 Selbst-Verpaarungen / 0 Gender-Fehler / 0 Alter-Verletzungen).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eine Rennmaus lebt max ~6 Jahre, kann also nicht Elternteil eines Tiers sein,
das nach ihrem Tod (oder vor ihrer Geburt) geboren wurde. Bisher fehlte diese
Prüfung: z. B. war Jayjay (*2013) als Vater von Solice (*2022) eingetragen, und
Solice/Silvain (Vollgeschwister, beide *27.03.2022) landeten in getrennten
Würfen mit verschiedenen Eltern.
- parent_age_plausible(): Elternteil muss vor dem Kind UND innerhalb der
Lebensspanne (≤6 J.) geboren sein. Genutzt im Namens-Resolver (Kandidaten-
filter) und als finaler Sanity-Pass, der unmögliche FatherId/MotherId verwirft
und im Log auflistet.
- pick_parent_ref(): bei mehreren widersprüchlichen Chart-parentRefs wird nicht
mehr blind der erste genommen, sondern ein alters-plausibler bevorzugt und das
Tier der jeweils anderen Elternrolle gemieden (Vollgeschwister-Charts nennen
denselben Namen in beiden Slots).
Ergebnis: Solice = Lui × Molly = Geschwister von Silvain (gemeinsamer Wurf);
0 Selbst-Verpaarungen, 0 Gender-Rollen-Fehler, 0 Eltern-Alter-Verletzungen über
alle 977 Würfe. Tests in test_merge_resolve.py ergänzt.
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>