- uw/Uwuw[d] (dichtes Underwhite) wird vor dem uw→G-Locus-Alias korrekt geparst
(Vance: nicht mehr „Unbekannter Farbschlag", zeigt G-Locus statt international uw). [#34,#32]
- ee[-] = sichtbarer Fuchs (rezessiv homozygot) → korrekt als Fuchs; bare „e-"
(rezessiv + unbekannt) ist genetisch unmöglich → genotypeInvalid statt still falsch. [#42]
- aa-Colourpoint-Zweig berücksichtigt jetzt D (Dilute) und E (Fuchs), statt fix
Zobel/Marder/Siam zu erzwingen. [#3]
- Wurf-Farbprognose: unbekanntes Allel (?) wird nicht mehr über die volle Dominanz
expandiert → keine unmöglichen Dilute-/Schimmel-/„Unbekannt"-Nachkommen. [#37,#39,#40,#41,#38]
Tests: test_genotype.py erweitert; genetics.test.ts +5 Blöcke (135 vitest grün, tsc/eslint sauber).
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>
Fotos liegen in den Stammbaum-xlsx je nach Datei links, rechts oder auf der
Namenszelle. Die alte Seiten-Erkennung hing an festen Spalten (col 1 / col 4):
Blätter ohne solche Anker wurden als Rechts-Layout fehlgedeutet, wodurch jedes
Foto eine Generation zum Probanden hin verrutschte. Folge: Kazuya trug das Foto
seines Vaters (Wilbur), Wilbur das seines Großvaters (Elay), Elay hatte keins.
Fix (extract._attach_photos): beide Interpretationen (links/rechts) durchrechnen
und die wählen, die jedes Foto am nächsten an die Namensspalte seines Tiers legt
(minimale horizontale Fehlausrichtung). Diagnose über alle 42 Dateien: keine ist
echt rechts-seitig; ~21 waren fehlklassifiziert. Selbstjustierend statt fester
Spalten-Annahme.
Regressionstest in test_extract.py: Kazuya ohne Foto, Wilbur/Elay mit ihren
eigenen (gated auf vorhandene Quelldatei).
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>
Dakota of sweet little mouse (DOB 30.01.2015):
- Genotype: Aa CC Dd Ee Gg pp Spsp
- A-Locus=Aa, P-Locus=pp, Sp-Locus=Spsp confirmed by Julian
Banjo of Fiomi (DOB 06.07.2015, Dakota's son):
- E-Locus correction: E- → ee (Julian: "Goldfuchs Starkschecke, nicht Gold Starkschecke")
- Corrected genotype: AA CC DD ee Gg pp Spsp
- ColorVariety: Goldfuchs Starkschecke
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Julian's final answer: 'She became 4 years old, no exact date.' Birth: 14.07.2013.
Calculated: 2013 + 4 = 2017. Applied year-only death convention: 01.01.2017.
Previous entry (17.04.2016) from HUMANQUESTION D6 was incorrect.
Max von Privat (01.02.2013): genotype aa c[chm]c[chm] D- EE GG PP spsp.
Tod-Jahr 2014 (Jahr-only) als _deathYear_pending kommentiert — Konvention offen.
Isa of Golden Lights (24.12.2014): dateOfDeath 21.07.2018.
Jack II von den Kleinen Chaoten (14.02.2016): dateOfDeath 06.10.2019.
Milon von den Kleinen Chaoten (27.11.2014): genotype Aa Cc[chm] D- ee[f] gg Pp spsp
(A-Locus war der einzige Konflikt; Züchterin löst auf Aa).
Sunny von PZ Karl (10.04.2014): dateOfDeath 30.04.2018.
Dry-run delta: Konflikte 7→2 (resolved 15→20). Verbleibend: Skarlett + Dakota.
conflict-decisions.json jetzt 16 Eintraege, alle D-Konflikte entschieden. Re-Import nicht mehr auf D6 gated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2018 war falsch (leakte als '/ +2018' in den Gencode; in IMPORT-POLISH gefixt). D6 noch offen: Hanami, Kazu (2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aa Cc[chm] Dd Ee gg P- spsp (c[hm]→c[chm] normalisiert). D6 noch offen: Hanami, Kazu, Skarlett (3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aa Cc[chm] DD Ee Gg Pp Spsp. Noch offen in D6: Hanami, Vance Jr., Kazu, Skarlett (4).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CR-9 (major): gidByNameDob TryGetValue + ExternalRef-Fallback
Verhindert KeyNotFoundException wenn Name/DOB zwischen zwei Laeufen driftet
(z.B. correctDob-Remap oder UI-Umbenennung). Fallback: ExternalRef-Dict-Lookup;
bei Miss: sauberes Ueberspringen + Note statt 500. +Test CR9_NameDOB_drift.
CR-11 (major): Farbschlag aus Genotyp ableiten (fill-NULL-only)
Deep-Band-Tiere (gen>=2, kein Farbschlag-Feld) landen nicht laenger mit null
ColorVariety. GenotypePotentiallyMatches() vergleicht locus-pair-weise (??=wildcard,
case-insensitive). Nur vollstaendige Genotypen (8 Loci, kein ??) loesen Ableitung aus.
Plan-Loop: fuellt colorVarietyId bei null + vollstaendigem Genotyp.
Post-Sweep: bestehende DB-Tiere mit null ColorVarietyId werden nachgefuellt.
AnimalSummary.FarbschlagDerivedFromGenotype = Zaehler. +Test CR11_ColorVariety_from_geno.
CR-10 (major, Python): malformed Override-Genotyp wird nicht angewendet
apply_conflict_decisions validiert mapped8locus nach gt.parse(). Leeres Ergebnis
= Genotyp unveraendert + decisionWarning statt stillem Blanken. Konflikt wird
trotzdem aufgeloest (Entscheidung gilt, nur Genotyp-Override ausgelassen).
+5 Python-Tests (CR-10-Block in test_extract.py).
DB-1 (high): filtered unique index auf Gerbil.ExternalRef
WHERE ExternalRef IS NOT NULL — verhindert doppelten Import bei Race-Conditions
oder Lauf-Ueberschneidungen. Migration UniqueExternalRef. SQLite-Testhost:
HasFilter() wird via EnsureCreated appliziert (SQLite unterstuetzt Partial-Indexes).
PartialUpdateTests externalRef-Assertion auf NotNull geaendert (name-hash unique).
GATE: 139/139 C# + Python ALL PASS, ef has-pending=No.
1) TOLERANTER KC-MATCHER (extract.py norm_zucht): trailing \b nach '.' greift nicht
zwischen two non-word-chars (z.B. nach '.' vor ' '). Fix: r\bv\.\s?d\. (kein \b
am Ende). Alle Schreibvarianten ('Zucht der Kleinen Chaoten', 'kleinen Chaoten',
'v.d. Kleinen Chaoten', '[ZdkC]') -> canon 'kleinechaote'. +is_clan_zucht() helper.
+11 Python-Tests (Regression + neue Varianten). FIX-1-Entscheidungs-Matching
unberuehrt (canon_pair loest v.d. bereits via split_name_zucht).
2) HERKUNFT-BACKFILL (ImportService.cs, fill-NULL-only): POST-Sweep ueber alle
DB-Tiere mit OriginBreeder==null && IsResident==true. Leitet OriginBreeder aus
dem Elterntier (FatherId/MotherId -> OriginBreeder) ab; Fallback 'Zucht der
Kleinen Chaoten'. NIEMALS ueberschreibt nicht-leeren OriginBreeder. Trocken-Lauf
zaehlt HerkunftBackfilled; Execute schreibt. Fixt Tier 'C' (OriginBreeder null).
3) FARBSCHLAG-WOULD-REBACKFILL (no-op Zaehler): zaehlt bereits-importierte Tiere,
bei denen der aktuelle Extraktor einen anderen Farbschlag liefert als in der DB.
Kein Overwrite (Sicherheitsmechanismus ausstehend auf god/Julian-Freigabe);
Optionen A/B/C an god gemeldet, Empfehlung: Option B (Timestamp-basiert).
4) ResidencySummary um HerkunftBackfilled + FarbschlagWouldRebackfill erweitert.
ImportReport-Notizen fuer beide neuen Zaehler.
GATE: 133/133 C#-Tests, 57 Python-Tests, ef has-pending=No.
FIX-1 ZUCHT: apply_conflict_decisions/apply_dob_remaps matchen jetzt
auf das VOLLE canon_pair-Tupel (nameCanon, zuchtCanon, dob) wenn die
Decision eine Zucht traegt; Fallback name-only wenn keine Zucht.
C3-Regel gewahrt: gleicher Name+DOB, andere Zucht -> kein Hit.
Neuer Regression-Test: Luna ZdkC-Decision trifft nur luna-kc,
nicht luna-bf (andere Zucht).
FIX-2 MERGE: dedup() waehlt das spezifischste Genotyp (fewest '?'
alleles) als sekundaeren Tiebreaker nach locus-count. CC schlaegt C-,
Gg schlaegt G- unabhaengig von der Reihenfolge. 3 neue Merge-Tests
(C- first/CC first/G-vsGg).
FIX-3 BACKFILL allDbNormToGid: ResolveParentForBackfill prueft jetzt
BEIDE Quellen: (a) createdAnimalByName (aktiver Lauf) und
(b) allDbNormToGid (alle DB-Tiere). Decktt den kritischen Fall:
Elterntier in fruehrem Lauf geladen, in diesem Lauf absent vom
Extract. Neuer 3-Lauf-SQLite-Test: Lauf 1 null-Vater, Lauf 2 laedt
Vater, Lauf 3 backfillt via allDbNormToGid.
Gate: 125/125 C#-Tests, Python ALL PASS, has-pending-model-changes=No.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
FIX-1 decision-matching: apply_conflict_decisions/apply_dob_remaps
nutzen jetzt canon_pair(name)[0] als Match-Key (Dedup-Identitaet:
call-name ohne Zucht, v.d.<->von den gefaltet). Workaround-Spelling
v.d. in Victoria Welbys Decision bleibt erhalten; beide Formen
matchen jetzt. Kommentar im decision-Eintrag aktualisiert.
FIX-2 specific-wins: _alleles_compatible aendert '? vs x = False'
-> '? vs x = True' (spezifischer Wert gewinnt). C- vs CC, G- vs Gg,
P? vs PP sind kein Konflikt mehr. Echte Wert-Widersprueche (DD vs Dd,
Ee vs ee, PP vs Pp) bleiben Konflikte. Loest Enya, Ella, Zac
automatisch (Konflikte 8->5 erwartet). 2 bestehende Tests angepasst,
7 neue Tests.
FIX-3 parent-FK backfill: nach dem Wurfchronik-Rueckverknuepfungs-
Block iteriert ImportService.RunAsync ueber bereits importierte
Wuerfe mit null Father/MotherId und setzt fehlende FKs wenn das
Elterntier jetzt ladbar ist. Trockenlauf zaehlt, Execute schreibt.
LitterSummary.ParentFksBackfilled + 2 neue C#-Tests (SQLite).
FIX-4 Skarlett-Artefakt: parse_detail() strippt trailing / +YEAR
aus dem Genotyp-Tail (re.sub). Sterbejahr bleibt als death-Date
erhalten -> Skarlett erscheint als reiner Sterbedatum-Konflikt.
2 neue Python-Tests.
Gate: 124/124 C#-Tests, Python test_extract/test_genotype ALL PASS,
has-pending-model-changes = No.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
god added a `correctDob` (DD.MM.YYYY) decisions field: the matched (name+dob)
record is a DUPLICATE with a wrong birthdate → remap its DOB to correctDob so
dedup MERGES it into the canonical same-named animal. apply_dob_remaps runs
BEFORE dedup (it changes the dedup identity); tolerates a missing file; logged
as "DOB-Remaps: N". First use: Chelsea *15.10.2021 → *02.04.2021 (merges into
the canonical record). test_extract covers the remap + that both records then
share one name+dob identity.
Extractor-only. python + dotnet 121/121 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>