Manuelle "gleich/verschieden?"-Entscheidungen aus den rpro3-import-Tickets
fließen jetzt in den RennmausPro-III-Import ein:
- Rpro3Decisions (Modell + JSON-Loader): same (Force-Merge), different
(Force-Split), fields (Farbe/DOB/Herkunft/resident/Notiz). Schlüssel = rid.
- Rpro3Dedup.Run(animals, decisions): honoriert die Overrides nach dem
automatischen Dedup (eine rid zieht ihren Cluster mit).
- Rpro3ImportService: lädt Import/Rpro3/rpro3-decisions.json, wendet
Feld-Overrides im Plan-Builder an.
- rpro3-decisions.json: 14 bestätigte Entscheidungen (Eiji, Momo, Samuel,
Female, Kennedy, Fegur, Tuli, Bura, Mister X, Zoey, Max, Akiro, Merlin,
Snickers).
- tools/import/rpro3_lookup.py: Triage-Helfer (Farbe/Gencode/Eltern/
Nachzucht+Partner aus _rpro3.db) für die Rückfragen an die Züchterin.
- 4 neue Tests (Force-Merge/Force-Split/Loader); Suite 268 grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hiro of Golden Lights erschien fälschlich als Gründertier (keine Eltern im Import).
Aus dem RennmausPro-Backup sicher zugeordnet (gleiches Geburtsdatum 13.02.2012):
Eltern Wynn × Amidala — als nicht-residente Ahnen-Stubs angelegt und verknüpft.
Bewusst nur EINE Generation: tiefere Ahnen würden über addAnimals-Stubs nicht
verkettet (der Eltern-Override greift nur auf echte Stammbaum-/Wurfchronik-Tiere,
nicht auf Stubs) — und mehrere Großeltern-Namen (Harry/Shy/Jack/Speedy) kollidieren
mit bestehenden, anderen Tieren. Voller mehrstufiger RennmausPro-Ahnen-Import =
eigenes Feature (Import-Code-Änderung + robustes Matching), nicht blind machbar.
python test_merge_resolve.py + test_extract.py grün.
Co-Authored-By: Claude Opus 4.8 <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>
- Danielle: Vater Makoto ergänzt (Mutter Ella war schon gesetzt) — Geschwisterverpaarung.
- Catelyn: Eltern Eddard Stark of Sunset Glow × Milena.
- Gaida: Eltern über Geburtsdatum gefunden — Zhuāngzǐ × Zaibunissa (beide *21.02.2020,
Geschwisterverpaarung, Kinder von Nisha × Zenon).
Per conflict-decisions.json (re-ingest verifiziert: Eltern live korrekt). Tickets
4692fd5c / 7bbc045c / ba63325a geschlossen.
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>
- 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>
Utility script to clean up import data from the database after testing
/import/dry-run. Useful for re-running the import process during development
and QA. Deletes all animals and litters created by the import (identified
by ImportSource='docx-export' and ImportSource='derived-litter'), resets
sequences, and leaves the database ready for fresh import execution.
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>