From bf074696b42a3b08efd873ca3e0a9b8c9cad8d6a Mon Sep 17 00:00:00 2001 From: Gulum Date: Wed, 19 Aug 2026 23:01:27 +0200 Subject: [PATCH] =?UTF-8?q?docs(triage):=20Triage-Lauf=202026-08-19=20?= =?UTF-8?q?=E2=80=94=2016=20offene=20Tickets=20in=2013=20Buendeln?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Je Bundel ein Triage-Agent gegen Prod-API + lokale Quellen (Stammbaum-xlsx, Wurfchronik, _rpro3.db, resolved_import.json). Ergebnis: alle 13 umsetzbar, keine Rueckfrage noetig. Uebergreifend: Prod ist auf dem aktuellen Importer-Stand, die beklagten Dubletten stecken schon in resolved_import.json — die Juli-Entscheidungen greifen nach dem Chart-Import vom 2026-08-18 nicht mehr (Wurfchronik-Elternstubs ohne Geburtsdatum, one-decision-per-animal). Co-Authored-By: Claude Opus 5 (1M context) --- docs/ticket-triage.md | 731 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 676 insertions(+), 55 deletions(-) diff --git a/docs/ticket-triage.md b/docs/ticket-triage.md index 30665b8..aac4a96 100644 --- a/docs/ticket-triage.md +++ b/docs/ticket-triage.md @@ -1,55 +1,676 @@ -# Ticket-Triage — Lauf gegen Prod (2026-07-21) - -Untersuchung: 11 Cluster-Agenten + 1 Hiro-Agent (read-only) gegen lokale Quellen (Stammbaum-xlsx, _rpro3.db, Wurfchronik) + Prod-API. Fotos vom Haupt-Loop ausgewertet. Datenfixes zentral in , lokal regeneriert + verifiziert. - -## A) Daten-Fixes — angewandt + lokal in resolved_import.json verifiziert (bereit für Prod-Re-Ingest) - -- **00715df9** Merle: Merles Farbe/Gencode habe ich aus ihrem eigenen Hinweis uebernommen (Aa chmchm D- ee GG P- Spsp), und ihre Eltern Akane und Bonaparte von den Schlossmaeusen sind bereits richtig eingetragen. Nur ihr genaues Geburtsdatum fehlt mir noch, weil der Stammbaum 'Pukas Kids' bei mir nicht auffindbar ist. -- **06b6734d** Jamie: Die zwei Jamie-Eintraege gehoeren zusammen. Ich fuehre sie zu einem zusammen (Geburtsdatum 27.12.2010, Sohn von Danny und Jana); dadurch landen auch alle seine Wuerfe und Nachzuchten beim richtigen Jamie. -- **1500f0cc** Targa / Druna: Targa hatte zwei Wuerfe, beide mit Marlin of Black Forest: einen am 14. Oktober 2020 und einen am 17. April 2021. Ihre Tochter Druna (Silberagouti) stammt aus dem Wurf vom 17. April 2021 und wird neu angelegt. Beide wurden zusammen an Nicole Nuzzo abgegeben, wo Druna am 29. Juli 2024 und Targa am 24. Oktober 2022 an Tumoren starben. -- **1be92139** Living Force's Idefix (*05.04.2016, 9eee3314): Idefix' Mutter ist Living Force's Vally. Ich habe sie zusammen mit seinem Vater (Living Force's Nando) im Stammbaum ergaenzt. -- **2c49eebf** Socke → Marty von den Kleinen Chaoten: Socke ist dasselbe Tier wie Marty von den Kleinen Chaoten und war versehentlich dreifach angelegt. Die Doppel-Eintraege werden zusammengefuehrt, der Name auf Marty von den Kleinen Chaoten gesetzt und seine richtigen Eltern Milow und Hina eingetragen (die falsche Zuordnung zu Taro faellt weg). -- **3269a722** Targa: Targas Geburtswurf vom 1. April 2019 wird jetzt korrekt als Wurf angelegt: Vater ist South Dakota von den Kleinen Chaoten (Silberschimmel), Mutter ist Chevrolet Corvette of Topolino. Ihre Wurfschwester Tennessee wird demselben Wurf zugeordnet, damit auch deren Eltern stimmen. -- **37cf2f40** Ichika von den Kleinen Chaoten: Bei Ichika waren die Eltern falsch. Richtig sind Jeremy von den Kleinen Chaoten (Vater) und Inory von den Kleinen Chaoten (Mutter) - das wird korrigiert. -- **44a9b677** Hiro: Hiro- und Mino-Doppeleinträge zusammengeführt (der vollständige mit Stammbaum bleibt); Würfe mit Skarlett und Mino×Misaki hängen jetzt am richtigen Tier; April-Wurf der richtigen Skarlett zugeordnet. -- **45576826** Blacky von Privatzucht Seligenstadt: Es gab Blacky versehentlich doppelt (einmal als "von PZ Seligenstadt", einmal als "von Privatzucht Seligenstadt"). Beide Eintraege sind zu einem zusammengefuehrt; der Name "Blacky von Privatzucht Seligenstadt" bleibt stehen. Geburts- und Todesdatum stimmen bei beiden ueberein, es gehen keine Daten verloren. -- **49548ac0** Jana: Jana und Jana of little longnoses sind dasselbe Tier. Ich fuehre die beiden zu einem Eintrag zusammen (Name: Jana of little longnoses, Herkunft Clan of little longnoses), mit ihren Eltern Milon und Darina gen. Talina. -- **5fe7c884** Ethan von den Kleinen Chaoten: Bei Ethan fehlte die Mutter. Laut Stammbaum sind seine Eltern Braden gen. Scotty (Vater, schon eingetragen) und Shika von den Kleinen Chaoten (Mutter). Shika wird angelegt und als Mutter ergaenzt. -- **71e3e6a1** Roni: Ronis Mutter ist Bonnie gen. Amika von den Kleinen Chaoten (sein Vater Kaleo gen. Halio ist bereits eingetragen). Sie wird als Mutter angelegt und bei Roni ergaenzt. Ihr eigenes Geburtsdatum und ihre Eltern stehen in den Unterlagen leider nicht. -- **7f9b3724** Sakura = Gwen gen. Sakura of little Angels: Sakura und 'Gwen gen. Sakura of little Angels' werden zu einem Tier zusammengefuehrt (Herkunft: Clan of little Angels), sodass ihr Geburts- und Sterbedatum, ihre Eltern und ihre Wuerfe an einem einzigen Eintrag haengen. -- **873aca5d** Jacky v.d. Kleinen Chaoten: Das ist derselbe Hinweis wie beim anderen Jacky-Ticket - Jackys Eltern werden auf Jack von Privatzucht Maintal (Vater) und Holly gen. Lilly of Ulmer Strolche (Mutter) korrigiert; dieses Ticket kann zusammen mit dem anderen geschlossen werden. -- **918a9a3f** Jacky v.d. Kleinen Chaoten: Jackys Eltern werden korrigiert: Vater ist Jack von Privatzucht Maintal und Mutter Holly gen. Lilly of Ulmer Strolche (bisher faelschlich Paul und Akina). Damit bekommen Jacky und ihr Bruder Jack Jr dieselben Eltern, genau wie du beschrieben hast. -- **9325e316** Domi (D-Wurf 2010): Bei Domi wird jetzt eingetragen, dass er zusammen mit seinem Wurfbruder Dastan an Susanne und Nina Thomas abgegeben wurde. -- **9ad13a73** Joghurt v. Privat (*06.09.2013, bab013c9): Joghurt kam aus privater Haltung; seine Eltern sind unbekannt. Bei uns war faelschlich ein erfundener Wurf mit BlackFire als Vater eingetragen -- das habe ich entfernt, seine Eltern bleiben jetzt korrekt unbekannt. -- **9bbfb6a0** Stacy: Wir uebernehmen Stacys Daten aus RennerPro: Sie ist die Gold-Schecke, geboren am 23.10.2012, Herkunft Gelnhausener Privatzucht. Ihre Farbe und ihr Erbbild werden ergaenzt und ihr Stammbaum mit den Eltern Vati (Goldfuchs) und Mutti (Agouti-Schecke) wird eingetragen. -- **a50116bf** Kuke: Kuke war doppelt angelegt; die beiden Eintraege werden zusammengefuehrt, sodass Farbe (Schwarz-Schecke), Genotyp und ihr Todestag (6.4.2011, gestorben bei einem Ausbruch-Unfall) an einem Tier stehen. Ihre Eltern sind laut RennerPro Marc und Sara. Ihre Nachzucht mit Blacky (Alvin, Flori, Eddy, Feivel) ist bereits richtig. -- **aa5238b6** Eliza: Es gab zwei Tiere namens Eliza. Die hier gezeigte Eliza (geboren 2010, gestorben 2011) war nie ein Zuchttier und ist NICHT die Mutter des J5-Wurfs — das war die spaetere Eliza (geboren 2014). Der J5-Wurf haengt bereits an der richtigen Eliza; nur der falsche Hinweis auf ihrer Seite wird entfernt. -- **bbf2898b** Malou von den Kleinen Chaoten (frueher 'Schlaganfall Sommer 2019', *10.09.2015): Malou war bei uns versehentlich zweimal angelegt. Ich habe die beiden Eintraege zusammengefuehrt; dabei bekommt Malou ihre richtigen Eltern Koji (Vater) und Zoe (Mutter), und der alte Zusatz 'frueher Schlaganfall Sommer 2019' faellt weg. -- **c092f8d2** Jamie: Die beiden Jamie-Eintraege werden zu einem zusammengefuehrt (Geburtsdatum 27.12.2010, aus dem J-Wurf, Sohn von Danny und Jana). Weitere Tiere mit Namen Jamie (eines von 2012, und Jamie Lannister von 2017) sind eigene Tiere und bleiben getrennt. -- **d02c2a1b** Blacky von Privatzucht Seligenstadt: Bei Blacky standen zwei falsche Jungtiere als Nachzucht: Antares of Ulmer Strolche und Chagan of Ulmer Strolche. Beide sind nicht von Blacky. Laut RennerPro stammt Antares von Grisu und Carlotta, Chagan von Akuma und Bekter. Sie werden von Blacky geloest; Blackys echte Nachzucht mit Kuke (Alvin, Flori, Eddy, Feivel) bleibt. -- **d8e6f1a2** Living Force's Idefix (*05.04.2016, 9eee3314): Bei Idefix war der Stammbaum kaputt: Idefix war irrtuemlich als eigener Elternteil eingetragen. Ich habe seine echten Eltern eingetragen: Vater Living Force's Nando und Mutter Living Force's Vally (beide aus der Zucht Living Force). -- **dc64ac0d** Wurf von Danny von PZ Maintal + Jana of little longnoses (J-Wurf): Es gibt keinen Wurf 'Hitze' — das war nur ein Tippfehler. Der Danny-und-Jana-Wurf steht momentan doppelt in der Liste. Sobald die beiden Jana-Eintraege zusammengefuehrt sind, verschmelzen die zwei Wuerfe automatisch zum J-Wurf, und Jamie steht dort zusammen mit seinen Geschwistern Jack, Chip und Jules. -- **e0a0c304** Kuke: Das Tier "Kruke" heisst in Wirklichkeit "Kuke" — der Name wird korrigiert. -- **e96db323** Jamie: Die beiden Jamie-Eintraege sind dasselbe Tier (Sohn von Jana und Danny) und werden zu einem zusammengefuehrt (Geburtsdatum 27.12.2010). -- **f8d55b7a** Stacy: Gleiche Sache wie beim anderen Stacy-Ticket: Wir tragen die Gold-Schecke Stacy mit Geburtsdatum 23.10.2012, Herkunft Gelnhausener Privatzucht, Farbe/Erbbild und ihren Eltern Vati und Mutti aus RennerPro ein. -- **ff8d6bd7** Blacky von Privatzucht Seligenstadt: Bei Blacky wird die "Extern"-Markierung entfernt. Er zaehlt als eigenes Bestandstier (zugekauftes Zuchttier), weil er bei dir mit Kuke Nachzucht hatte. - -## B) Bereits korrekt (nur bestätigen) - -- **8b1dbd19** Gaida von den Kleinen Chaoten: Alle Daten aus dem frueheren Gaida-Ticket sind uebernommen: ihr Todesdatum 09.10.2025, die Todesursache Lungenentzuendung mit Fieber, das Abgabedatum 05.01.2025, die Abnehmerin Caroline Emmert und ihre Eltern (eine Geschwisterverpaarung aus dem Wurf von Nisha und Zenon). Nur ihr Status stand faelschlich noch auf 'Zucht' statt 'verstorben' - das wird zusammen mit der anderen Korrektur behoben. -- **bd351aa2** Jana: Janas Herkunft steht bereits richtig als Clan of little longnoses. Ihr Sohn Feivel (geboren 25.12.2011) ist bereits als Nicht-Zuchttier gefuehrt — der Feivel, der in der Zucht war, ist ein anderer (der Sohn von Blacky und Kuke). Nur ihr Sohn Jamie war in der Zucht. Sobald die beiden Jana-Eintraege zusammengefuehrt sind, zeigt Janas Seite genau das. - -## C) Rückfrage nötig (→ NeedsInfo) - -- **5851bb94** Spike: Die Verpaarung Spike x Jacky habe ich in deiner Wurfchronik gefunden: dort steht der D7-Wurf (geboren am 14.08.2015, Jungtiere Smartie, Cookie und Cloe) mit den Eltern Spike und Jacky. Spike (geboren 03.04.2015) waere zu diesem Zeitpunkt aber erst gut 4 Monate alt gewesen. War der Vater dieses Wurfs ein anderes Tier - und wenn ja, wer war der richtige Vater von Smartie, Cookie und Cloe? Oder soll die Verpaarung mit Jacky bei Spike einfach entfernt werden? -- **a547be62** Eliza: Bei Eliza (geboren am 20.08.2010, Tochter von Blacky und Kuke, verstorben 6.4.2011) stimmt der Gencode nicht — der angezeigte Wert ist nur automatisch aus dem Farbschlag "Schwarz" abgeleitet. Wie lautet ihr korrekter Gencode? (Du hattest ihn frueher in deiner Liste aller Blacky-&-Kuke-Nachkommen genannt; magst du ihn fuer genau diese Eliza kurz wiederholen?) -- **f212b73c** Bonaparte von den Schlossmaeusen: Bonapartes Eltern (und Merles genaues Geburtsdatum) finde ich nur im Stammbaum 'Pukas Kids', den ich hier leider nicht oeffnen kann - die Datei ist bei mir nicht vorhanden. Magst du mir Bonapartes Vater und Mutter direkt mit Namen nennen? Und falls im selben Stammbaum vermerkt: an welchem Datum ist Merle geboren? Alternativ kannst du mir die Excel-Datei 'Stammbaum von Pukas Kids' nochmal schicken. -- **fbe53c6b** Fumi von den Kleinen Chaoten: Ich finde Fumi weder in den Stammbaum-Excel-Dateien noch in der Wurfchronik als Jungtier - dort steht sie nur als Mutter (zusammen mit Roni). Aus welchem Wurf stammt Fumi bzw. wer sind ihre Eltern (gern mit Geburtsdatum)? - -## D) Code-Fixes (Deploy nötig, KEIN Re-Ingest) - -- **37ab228a** Gaida von den Kleinen Chaoten [backend]: Gaida (04358c1f) hat in Prod dateOfDeath=2025-10-09, goHomeDate=2025-01-05, receiverContactId (Caroline Emmert) UND causeOfDeath gesetzt, aber Status=Breeding -> erscheint deshalb unter dem Default-Filter status=['Breedi -- **4b9f49fb** Ungeklärt-Wurf 03.03.2011 (= M-Wurf) [import-logic]: Der Wurf 2c545f8a ('Ungeklaert', 03.03.2011, Eltern Blacky von PZ Seligenstadt x Charlett of little longnoses) ist eine Fehl-Verschmelzung aus 3 Wurfchronik-Quellen mit demselben Datum: (1) Teil1 S.15 = der ECHTE M-Wurf -- **8d259edf** Gale von den Kleinen Chaoten (frueher Drake Jr, 7f09dd7b) [import-data]: Umbenennung Drake Jr -> 'Gale von den Kleinen Chaoten', Notiz und Eltern (Vater Drake von den Kleinen Chaoten, Mutter Holly gen Lilly of Ulmer Strolche) sind bereits korrekt gesetzt (conflict-decisions + Prod). OFFEN sin -- **9f92c834** CoCo [frontend]: CoCo (5e1931b5, *17.04.2010, Genotyp aa CC DD EE GG PP Spsp rere = Schwarz-Schecke) IST eine Schecke (Sp-Locus Spsp), aber ihr Feld SpottingType ist null. Die Tier-Akte (GerbilDetailPage.tsx Zeile 506: `{g.spottingType & -- **f89e95ad** A-Wurf (Blacky von PZ Seligenstadt x Kruke/Kuke, *18.02.2010) [genetics-engine]: Zwei Ursachen. (1) DATEN-Voraussetzung: Die Mutter des A-Wurfs (prod e4898ccc 'Kruke', = die Kuke aus Ticket a50116bf/e0a0c304, *18.09.2009) hat in prod genotype=null. Die erwarteten Farbschlaege werden nur berechnet, we \ No newline at end of file +# Ticket-Triage — Lauf gegen Prod (2026-08-19) + +16 offene Tickets (12 Open, 4 Answered) in 13 Bündeln, je ein Triage-Agent gegen Prod-API +(http://truenas:8090/api) + lokale Quellen (Stammbaum-xlsx, Wurfchronik, _rpro3.db, +tools/import/output/*.json). Ergebnis: **alle 13 Bündel sind umsetzbar, keine Rückfrage nötig.** + +Übergreifender Befund: Prod ist auf dem aktuellen Importer-Stand (2451 Tiere vs. lokal 2450), +die beklagten Dubletten stecken also schon in `resolved_import.json`. Mehrere Tickets von heute +sind Folge-Tickets aus dem Juli-Lauf, deren Entscheidungen die neuen Chart-Varianten nicht mehr +greifen (Wurfchronik-Elternstubs ohne Geburtsdatum). + +## Cluster-Matrix + +| Tickets | Thema | Layer | Cluster | Aufwand | Konfidenz | +|---|---|---|---|---|---| +| 2322c2a8 | Jay (*24.10.2021) — Farbschlag Zobel-Hell statt Zobel (C-Locus-Tippfehler c[hm]) | import-data | conflict-decisions-genotype-override | S | high | +| 8a51ac73 | Kathlin von den Kleinen Chaoten (*20.04.2021) — falsche Eltern (Grossmutter Izumi als Mutter, kein Vater) | import-data | conflict-decisions-parent-override | S | high | +| 65266679 | Bijou (*26.06.2022) — falsche Eltern: Arya Stark statt Louis × Nani of Black Forest | import-logic | extract-starless-dob-block | S | high | +| 5851bb94 | Spike (*03.04.2015) — Verpaarung mit Jacky bestätigt; Eltern seines Geburtswurfs (T5/TS-Wurf) fehlen | import-data | import-data-conflict-decisions-litterparents | S | high | +| 6c89082e | Fast Boy of Golden Lights (*31.03.2017) — beide Eltern im Stammbaum falsch (Großvater als Vater, Schwiegermutter als Mutter) | import-data | conflict-decisions-resolutions-parents | S | high | +| 24522f5f | JackJack (b6d8b3ef) — Abgabedatum-Zeile unter Abnehmer in der Tier-Akte (+ kaputtes Abgabedatum 1310-05-13) | frontend | gerbil-detail-abgabedatum-goHomeDate | S | high | +| a547be62 | Eliza *20.08.2010 (E-Wurf Blacky x Kuke) — Gencode + Scheckungsart Ansatzschecke | import-logic | decisions-genotype-spottingtype-override | M | high | +| 66ef9bdd, 3bbd6ab4 | Jamie (*27.12.2010, J-Wurf Danny x Jana) — Dublette: zwei weitere Wurfchronik-Elternstubs nie gemergt | import-data | conflict-decisions-mergeexternalrefs-wurfchronik-elternstubs | S | high | +| 6c9539d2 | Sakura / Gwen gen. Sakura of little Angels — Dublette nach Merge-Fix zurueck (Folge-Ticket zu 7f9b3724) | import-data | wurfchronik-md-stub-dubletten-mergeexternalrefs | S | high | +| 4f890829 | „Unbekannt" (be299aaf, *21.06.2014) — namenlose Q3-Dublette + falscher Vater des Q4-Wurfs | import-data | conflict-decisions-wurfchronik-page0009-platzhalter-eltern | S | high | +| 7037f5d8 | Merle (2023-06-18): Herkunft leer + noch als Zuchttier im Bestand (Abgabe 22.12.2024 fehlt) | import-data | conflict-decisions-resolutions-merge-and-resolve | M | medium | +| bde4ec70, f89e95ad | JackJack (b6d8b3ef) + A-Wurf 18.02.2010 (29f4bae9): Saphir wird als Platin errechnet — C-Locus-Zygotie fehlt im Farbkatalog | genetics-engine | genetik-c-locus-zygotie-saphir-platin | M | high | +| 24006ccd, ea803c3d | Kuke (prod e4898ccc): fehlender Zuchtname + alter Name „Kruke" in 5 Wurf-Notizen | import-logic | conflict-decisions+merge_and_resolve-rename | M | high | + +## Befund & Fix je Bündel + +### Jay (*24.10.2021) — Farbschlag Zobel-Hell statt Zobel (C-Locus-Tippfehler c[hm]) + +Tickets: `2322c2a8-a616-4967-88f6-031b4519a1ca` · Layer **import-data** · Cluster `conflict-decisions-genotype-override` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Jay steht in Prod auf ColorVarietyId 00000000-0000-0000-0000-000000000053 = "Zobel-Hell" mit Genotype "aa c[chm]c[hm] Dd Ee gg P- spsp". Die Quell-Charts "Stammbaum von Alberto Kids.xlsx" und "Stammbaum von Pukas Kids.xlsx" enthalten diesen String woertlich (sharedStrings geprueft) — das zweite C-Allel ist als "c[hm]" statt "c[chm]" geschrieben (fehlendes c, Tippfehler); das Farbschlag-Label beider Charts lautet korrekt "Zobel". tools/import/genotype.py akzeptiert "hm" in den C-Locus-Regexen (Zeilen 28 und 38), mappt es auf Allel 'c^hm' != 'cchm', dadurch ist in _colourpoint_name (Z. 383/389) both_cchm=False -> "Zobel-Hell". Die Regel "GENOTYPE WINS" in merge_and_resolve.resolve_color_and_genotype (ab Z. 530) laesst diesen falsch abgeleiteten Namen das richtige Quell-Label "Zobel" ueberschreiben. Zusatzschaden: der TS-Parser (gerbil-manager-web/src/genetics/genotype.ts, Allele nur C/cchm/ch) kennt "c[hm]" nicht -> Jays Genotyp ist in UI/Probeverpaarung ungueltig. Dass "c[hm]" = "c[chm]" gemeint ist, ist zweifach belegt: (1) alle anderen Zobel-Tiere der Zucht (Plasma, Smoke, Valentino Fireheart, Kalea "Zobel schecke") haben c[chm]c[chm], "Zobel-Hell" kommt in keinem Chart als Label vor; (2) Genetik-Ausschluss: Vater Hagrid Rubeus of Black Forest = "Aa Cc[chm] dd Ee gg Pp Spsp", Mutter Arya Stark von den Kleinen Chaoten = "aa c[chm]c[chm] DD Ee Gg PP spsp" — kein Elternteil traegt c[h], ein c[h] bei Jay ist also unmoeglich, waehrend c[chm] von beiden Eltern kommen kann (aa/Dd/Ee/gg/spsp passen ebenfalls). Zobel im Katalog = aa c[chm]c[chm] DD EE gg PP spsp (Id ...0004); Dd/Ee/P- sind mit dem Zobel-Phaenotyp vereinbar. + +**Fix:** + +Data-driven Fix in tools/import/conflict-decisions.json — neue Resolution im Array "resolutions" (Mechanismus "genotype"-Override, existiert dort bereits 36x, greift in extract.apply_conflict_decisions via canon(name)+dob): + +{ + "name": "Jay", + "dob": "24.10.2021", + "decision": "C-Locus = c[chm]c[chm] — die Quell-Charts schreiben 'c[hm]' (fehlendes c, Tippfehler); Farbschlag-Label beider Charts ist 'Zobel', und c[h] ist genetisch unmoeglich, da beide Eltern kein c[h] tragen (Hagrid Rubeus Cc[chm], Arya Stark c[chm]c[chm]). Ticket 2322c2a8", + "genotype": "aa c[chm]c[chm] Dd Ee gg P- spsp", + "source": "Zuechterin-Ticket 2322c2a8 (2026-08-19) — „Falsche Farbe. Er ist ein Zobel“" +} + +Danach: python tools/import/extract.py && python tools/import/merge_and_resolve.py, dann Upload-Ingest gegen Prod. Erwartetes Ergebnis fuer feebbbe2-c656-505e-a2c1-c0d7dd1a6ebc: Genotype = "aa c[chm]c[chm] Dd Ee gg P- spsp", ColorVarietyId = 00000000-0000-0000-0000-000000000004 (Zobel), Genotyp in der UI wieder parsebar. Kollisionsrisiko geprueft: die zwei weiteren Roh-"Jay"-Saetze (jay, jay-2) haben leeres dob, Key ("jay","") matcht die Resolution nicht. + +Optionale Haertung (separat, weil Genetik-kritisch -> nur mit Tests): in tools/import/genotype.py den Token "hm" aus den C-Locus-Regexen (Zeile 28: "C": re.compile(r"^(C|c)(\[(?:chm|chl|ch|h|hm|e|-)\])?…") und Zeile 38) entfernen ODER "c[hm]" explizit als Alias auf 'c^chm' normalisieren, plus Regressionstest in tools/import/test_genotype.py ("aa c[chm]c[hm] Dd Ee gg P- spsp" darf nicht Zobel-Hell ergeben). Aktuell ist Jay der einzige Datensatz in resolved_import.json mit "hm]"-Token; in den Charts steht "aa Cc[hm] …" noch 2x (Stammbaum von Fire Kids.xlsx, Stammbaum von Stella Kids.xlsx) — dort phaenotypisch irrelevant (C dominant), aber der c[chm]-Carrier-Status fehlt der Probeverpaarung. + +**Changelog-Entwurf für die Züchterin:** Jay wird jetzt richtig als Zobel geführt. In seinen beiden Stammbaum-Dateien hatte sich beim Gencode ein kleiner Schreibfehler eingeschlichen (bei einem der beiden Farb-Bausteine fehlte ein Buchstabe), dadurch hat das Programm ihn als „Zobel-Hell" einsortiert. Sein Gencode lautet nun „aa c[chm]c[chm] Dd Ee gg P- spsp" und passt damit auch zu seinen Eltern Hagrid Rubeus und Arya Stark — die helle Variante wäre bei diesen Eltern gar nicht möglich gewesen. Nebeneffekt: sein Gencode lässt sich jetzt auch wieder für Probeverpaarungen verwenden. + +**Betroffen:** tools/import/conflict-decisions.json; tools/import/genotype.py; tools/import/merge_and_resolve.py; tools/import/test_genotype.py + +--- + +### Kathlin von den Kleinen Chaoten (*20.04.2021) — falsche Eltern (Grossmutter Izumi als Mutter, kein Vater) + +Tickets: `8a51ac73-c891-4c51-9b09-959856c8c46c` · Layer **import-data** · Cluster `conflict-decisions-parent-override` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Kathlins Eltern wurden per chart-position rekonstruiert und dabei um eine Zeile verrechnet (gleicher Offset-Fehler wie im Akane-Fall 88389f8e: in diesen Charts sitzt das Elternpaar eine Zeile unter dem 3-Zeilen-Kindblock). Prod: LitterId ecff0b8a "Wurf von Izumi + Ella von den Kleinen Chaoten", FatherId=NULL, MotherId=2cb68a8f = Izumi (*07.02.2018) — Izumi ist real Kathlins GROSSMUTTER (Mutter von Yuki). Provenance belegt das Chaos: Vater-Kandidaten Vestra/Unit/Izumi wegen falschem Geschlecht verworfen, Mutter-Kandidat Yuki verworfen, dann Ella ("ein Wurf hat nur einen Vater und eine Mutter") verworfen. Die Stammbaum-xlsx sagen einheitlich in 6 Charts: Kathlin = Vater Yuki von den Kleinen Chaoten (*03.02.2020, maennlich) x Mutter Ella (*10.06.2019) — belegt in "Stammbaum von CP-Fuchs, CP-Sa Sp von Unity.xlsx" und "Stammbaum von Sa von Johnny Kids.xlsx" (Kathlin K79-K81, Eltern N80/N81, Grosseltern Q79 Chevrolet Camaro of Topolino + Q80 Izumi bzw. Q81 Louis + Q82 Roswitha, Urgrosseltern R80 "Nduga & Benjiro", R81 "Hannah & Petri", R82 "Jin & Sokrates"), ebenso Blitzis Kids (N67 -> Q67/Q68), Valentino Firehearts Kids (N81 -> Q81/Q82), CP Maedels von Grisu und CP-Polarfuchs von Grisu (K35 -> N36/N37). Zusatzbeleg: die handschriftliche Notiz der Zuechterin direkt unter Kathlins Zelle ("Woher kommt das ch?? Louis? Izumi?") verfolgt genau diese Linie. Genetisch/altersmaessig plausibel (Kathlin AA ee[f] spsp aus Yuki Aa Ee Spsp x Ella Aa ee[f] spsp). Die Vorfahren oberhalb sind in Prod bereits korrekt (Yuki 55fba1b1 -> Wurf fd9310db "Camaro + Izumi"; Ella ebc87c8c -> Wurf d7c4e38c "Louis + Roswitha") — nach dem Fix stimmt die ganze Ahnentafel. Kein Rueckfall: fuer Kathlin existiert bisher kein Eintrag in conflict-decisions.json. + +**Fix:** + +Owner-Regel, data-driven, re-ingest-stabil: EINEN Eintrag in tools/import/conflict-decisions.json -> "resolutions" ergaenzen (kein Live-DB-Patch), danach extract.py + merge_and_resolve.py + Upload-Ingest gegen Prod: + +{ + "name": "Kathlin von den Kleinen Chaoten", + "dob": "20.04.2021", + "decision": "Eltern sind Yuki von den Kleinen Chaoten (*03.02.2020, Vater) x Ella (*10.06.2019, Mutter) — so in allen 6 Stammbaum-Charts (z. B. 'Stammbaum von CP-Fuchs, CP-Sa Sp von Unity.xlsx' K79 -> N80/N81, Grosseltern Q79 Camaro + Q80 Izumi / Q81 Louis + Q82 Roswitha). Chart-Position hatte die Grossmutter Izumi als Mutter gesetzt und keinen Vater (Zeilen-Offset wie im Akane-Fall).", + "father": "Yuki von den Kleinen Chaoten", + "fatherDob": "03.02.2020", + "mother": "Ella", + "motherDob": "10.06.2019", + "source": "Ticket 8a51ac73 — Stammbaum-xlsx (Zuechterin verweist ausdruecklich darauf)" +} + +Wirkung: extract.apply_conflict_decisions ersetzt Kathlins parentRefs komplett (method=decision, confidence=high), merge_and_resolve legt den virtuellen Wurf "Wurf von Yuki von den Kleinen Chaoten + Ella" (20.04.2021) an; der alte Wurf ecff0b8a (einziges Kind = Kathlin) wird beim Upsert-Ingest stale und entfernt. Namensschluessel "Ella" + motherDob ist erprobt (Danielle-Resolution -> Wurf e1a14122 "Makoto + Ella", motherId ebc87c8c); "Yuki von den Kleinen Chaoten" ist mehrfach vergeben, daher fatherDob zwingend. + +Verifikation nach Ingest: GET /api/gerbils/5a8e3035-... -> litterId zeigt auf "Wurf von Yuki von den Kleinen Chaoten + Ella" mit fatherId 55fba1b1-f59e-5117-92bf-85e950c953f9 und motherId ebc87c8c-0528-5b70-a251-9d32858dbc68; Stammbaum-Ansicht muss darueber Camaro/Izumi und Louis/Roswitha zeigen. Zusaetzlich python test_merge_resolve.py + test_extract.py gruen. + +Getrennt zu ticketn (NICHT Teil dieses Fixes): "Stammbaum von Valentino Firehearts Kids.xlsx" fuehrt K79 "Ophelie von den Kleinen Chaoten" mit *20.04.2021 (Kathlins DOB) und macht Baxter x Kathlin zu ihren Eltern -> dadurch existiert in Prod eine zweite Ophelie b981d41d (*20.04.2021) neben der echten b413df65 (*06.05.2023); vermutlich DOB-Verschreiber/Dublette im neuen Chart. + +**Changelog-Entwurf für die Züchterin:** Kathlins Stammbaum ist korrigiert: Als Eltern stehen jetzt Yuki von den Kleinen Chaoten (*03.02.2020) als Vater und Ella (*10.06.2019) als Mutter — so wie es in Deinen Excel-Stammbäumen steht. Vorher war fälschlich Izumi als Mutter eingetragen (das ist Yukis Mutter, also Kathlins Großmutter) und es fehlte der Vater. Damit stimmen auch die weiteren Vorfahren: über Yuki kommen Chevrolet Camaro of Topolino und Izumi dazu, über Ella Louis und Roswitha. + +**Betroffen:** tools/import/conflict-decisions.json; tools/import/extract.py (_reconstruct_parents / apply_conflict_decisions); tools/import/merge_and_resolve.py (virtuelle Wuerfe aus Decision-parentRefs); Prod-Daten: Gerbil 5a8e3035-bf60-5af8-9275-7d40352a27b5, Litter ecff0b8a-ac39-52c1-ac85-2cd442a901c5 + +--- + +### Bijou (*26.06.2022) — falsche Eltern: Arya Stark statt Louis × Nani of Black Forest + +Tickets: `65266679-9bc3-43da-8280-cf8e7bb5e576` · Layer **import-logic** · Cluster `extract-starless-dob-block` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Prod zeigt Bijou (4b60e1fe) im virtuellen Wurf 237ed36d mit motherId=b4f59085 (Arya Stark von den Kleinen Chaoten) und ohne Vater; Provenance: "Eltern über Position im Stammbaum erkannt (Stammbaum von Alberto Kids.xlsx)" + "⚠ Elternteil Nani of Black Forest verworfen — ein Wurf hat nur einen Vater und eine Mutter". Laut Chart (identisch in "Stammbaum von Alberto Kids.xlsx" und "Stammbaum von Pukas Kids.xlsx", Gen-2-Band Spalte K): Vater = Louis of Black Forest (*22.08.2019, †09.04.2024, Block K44–K46, "Aa CC dd EE Gg P- spsp", blaue Box = männlich), Mutter = Nani of Black Forest (*15.10.2021, †27.11.2025, K48–K50). Arya Stark ist genetisch unmöglich als Mutter (Arya "aa c[chm]c[chm] …" kann Bijou "AA CC …" nicht vererben) — sie ist real die Mutter von Jay, also Bijous Partner, d. h. der Nachbar-Ast im Chart. URSACHE im Importer: tools/import/extract.py, extract_stammbaum → die Blockerkennung verlangt ein Sternchen vor dem Geburtsdatum (`is_block_dob = re.match(r"^\*\s?\d", t)`, ebenso DOB-Regex Zeile 33). In beiden Charts fehlt der Stern bei Louis (Zelle K45 = "22.08.2019/+09.04.2024") → der gesamte Block wird übersprungen, Louis existiert im Chart-Extrakt nicht, `_reconstruct_parents` nimmt als "nächsten Block oberhalb" Arya Stark (K35) aus Jays Ast und Nani als zweite Mutter, die anschließend als überzählige Mutter verworfen wird. Repo-weiter Scan aller 55 Charts: genau 3 solche sternlosen DOB-Blöcke (Louis 2×, Jiminy of Black Forest in "Stammbaum von Stella Kids.xlsx"), also ein klar begrenzter, generischer Importer-Bug — kein Rückfall einer alten conflict-decisions-Entscheidung (zu Bijou/Louis existiert kein Eintrag). + +**Fix:** + +Code-Fix im Importer (kein Live-DB-Patch, kein conflict-decisions-Eintrag nötig, da Louis/Jiminy bereits als deduplizierte Tiere existieren): + +1) tools/import/extract.py, in `extract_stammbaum` direkt nach `cells = xu.read_cells(z, sheets[0], ss)` (ca. Zeile 231, vor `fillsex = …`) einfügen: + +```python + # FIX (Ticket 65266679 Bijou): einige Charts vergessen den Stern vor dem Geburtsdatum + # ("22.08.2019/+09.04.2024" statt "*22.08.2019/+09.04.2024"). Ohne Stern erkennt die + # Blockheuristik das Tier gar nicht (Louis of Black Forest) und _reconstruct_parents + # greift in den Nachbar-Ast (Bijou bekam Arya Stark). Nur normalisieren, wenn die Zelle + # wirklich in einem Block sitzt: Genotyp <=3 Zeilen darunter UND Namenszelle <=3 darüber. + _bare_dob = re.compile(r"^\s*\d{1,2}\.\s?\d{1,2}\.\d{4}(\s*/\s*\+.*)?$") + for (c, r), t in list(cells.items()): + if not _bare_dob.match(t): + continue + has_geno = any(gt.looks_like_genotype(cells.get((c, rr), "")) for rr in range(r + 1, r + 4)) + has_name = any(cells.get((c, rr)) and not re.match(r"^\*?\s?\d", cells[(c, rr)]) + and not gt.looks_like_genotype(cells[(c, rr)]) for rr in range(r - 3, r)) + if has_geno and has_name: + cells[(c, r)] = "*" + t.strip() +``` + +2) Regressionstest in tools/import/test_extract.py mit dem vorhandenen `_make_xlsx`-Helper, z. B.: +```python +tmp = os.path.join(tempfile.gettempdir(), "starless_dob.xlsx") +_make_xlsx(tmp, { + "K44": "Louis of Black Forest", + "K45": "22.08.2019/+09.04.2024", # Stern fehlt (Quelldatei-Tippfehler) + "K46": "Aa CC dd EE Gg P- spsp", + "N44": "Ignoriertes Datum 01.01.2020", # ohne Genotyp/Name-Kontext -> kein Block +}) +animals = e.extract_stammbaum(tmp) +by_name = {a["name"]: a for a in animals} +check("sternloses DOB wird als Block erkannt", "Louis of Black Forest" in by_name) +check("DOB/Todesdatum trotzdem geparst", + by_name["Louis of Black Forest"]["dob"] == "22.08.2019" + and by_name["Louis of Black Forest"]["death"] == "09.04.2024") +``` + +3) Verifikation: `python test_extract.py test_extract_docx.py test_genotype.py test_merge_resolve.py test_extract_contracts.py` grün, dann `python tools/import/extract.py` + `extract_contracts.py` + `merge_and_resolve.py`; in resolved_import.json prüfen: Bijou (*26.06.2022) → Vater "Louis of Black Forest" (louisblackforest-22082019 / Prod b7f0a7bd-6420-58ea-bf59-5ff96979b9d5), Mutter "Nani of Black Forest"; Velvet (*23.03.2022) → Vater Jiminy of Black Forest. Danach Upload-Ingest gegen Prod (`curl -X POST http://truenas:8090/api/import/ingest-resolved/upload -F "resolved=@tools/import/output/resolved_import.json"`). + +Verifizierter Prototyp-Diff (Monkeypatch über alle 55 Charts, verglichen wurden Tier-Set und alle parentRefs): nur 3 Dateien ändern sich, rein additiv — neu erkannt Louis of Black Forest (Alberto Kids, Pukas Kids) und Jiminy of Black Forest (Stella Kids); geänderte Elternlinks nur Bijou (Arya Stark → Louis of Black Forest) und Velvet (Tomomi → Jiminy of Black Forest, deckt sich mit Wurfchronik S20-Wurf 23.03.2022 "Chelsea × Jiminy [Black Forest]"; Velvet hängt in Prod fälschlich am R20-Wurf 21.03.2022 Belica/Zac, Litter e92f2afe). Kein Tier verloren, keine weiteren parentRefs berührt. Der falsche virtuelle Wurf 237ed36d hat nur Bijou als Kind und wird beim Upsert-Ingest als stale entfernt. + +Separat (nicht Teil dieses Fixes, eigenes Ticket sinnvoll): in denselben Charts sind tiefere Ahnen-Paare um eine Zeile versetzt (Akane-Muster) — Arya Stark erhält Vance × Milka statt Vance × Sansa, Nani erhält Udo × Milka statt Udo × Hedwig. + +**Changelog-Entwurf für die Züchterin:** Bijous Eltern sind jetzt richtig: Vater Louis of Black Forest (*22.08.2019), Mutter Nani of Black Forest (*15.10.2021) — genau so, wie es im Stammbaum von Albertos Kids und im Stammbaum von Pukas Kids steht. Vorher stand dort Arya Stark als Mutter; das war der Nachbar-Ast im Stammbaum (Arya ist die Mutter von Jay, Bijous Partner) und konnte auch von der Farbvererbung her nicht passen. + +Ursache: In den Stammbaum-Tabellen fehlte bei Louis das Sternchen vor dem Geburtsdatum ("22.08.2019" statt "*22.08.2019"). Das Programm erkannte das Kästchen deshalb gar nicht als Tier und hat als Vater/Mutter versehentlich die Tiere aus dem darüberliegenden Ast genommen. Das Einlesen versteht solche Datumsangaben jetzt auch ohne Sternchen — du musst in den Excel-Dateien nichts korrigieren. + +Nebenbei mit korrigiert: Bei Velvet (*23.03.2022) ist nun ebenfalls der richtige Vater eingetragen, Jiminy of Black Forest — passend zum S-Wurf von Chelsea und Jiminy aus der Wurfchronik. + +**Betroffen:** tools/import/extract.py; tools/import/test_extract.py; Stammbaum von Alberto Kids.xlsx; Stammbaum von Pukas Kids.xlsx; Stammbaum von Stella Kids.xlsx; Gerbil Bijou 4b60e1fe-8b57-505b-9683-5e1536670b99; Litter 237ed36d-1d65-5e4d-bf38-1ed32542b530; Gerbil Velvet f5ffda6d-458c-570d-9cf6-67ac0c5e37a5 + +--- + +### Spike (*03.04.2015) — Verpaarung mit Jacky bestätigt; Eltern seines Geburtswurfs (T5/TS-Wurf) fehlen + +Tickets: `5851bb94-582e-4d83-846a-faa3e6df7049` · Layer **import-data** · Cluster `import-data-conflict-decisions-litterparents` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Zwei getrennte Befunde. (1) Die Verpaarung Spike x Jacky ist KORREKT und quellenbelegt: die handschriftliche Detail-Chronik "Wurfchronik Teil 1" S.52 (PNG geprüft) schreibt "Spike + Jacky *14.08.15 => D7-Wurf, WS:3" mit genau den drei Jungtieren Smartie/Cookie/Cloe; die Wurfchronik-xlsx (sheet1 Zeile 197) nennt dieselbe Verpaarung als "C7 | *14.08.2015 | Mutter Jacky | Vater 'Spike /Jack Jr.?'". Die frühere Rückfrage beruhte auf einem Denkfehler von mir: 4 Monate sind für einen Bock KEIN Hindernis (zuchtreif ab 9-12 Wochen, Zeugung ~20.07.2015 => Spike ~15 Wochen). Ein alternativer, älterer "Spike" existiert in keiner Quelle (1b6a76d1 *31.03.2017 = nach dem Wurf geboren, e185e7fa "Spike of colourful furry gerbils" *27.04.2015 = fremde Zucht und jünger, 0d381af5 "Spikey" *13.01.2012 = anderes Tier/anderer Wurf). Die Antwort "OK, dieser Spike existiert tatsächlich" bestätigt damit den Eintrag => nichts zu ändern am Wurf aa48fc4a. (2) Der eigentliche Grund für ihre Frage "Woher hast du dieses Böckchen?" ist ein echter Datenmangel: Spikes Geburtswurf 1e49ad0e ("TS-Wurf", 03.04.2015) hat in prod FatherId=MotherId=NULL, seine Akte zeigt also keine Herkunft. Ursache: die Seiten-Extraktion "Wurfchronik Teil 1_page_0044.md" liefert für ALLE 9 Würfe dieser Seite fatherId/motherId=null (Eltern stehen dort nur im Overview-Fließtext "TS-Wurf (Jack Jr + Lila)"). Die xlsx belegt sie eindeutig: Zeile 162 "T5 | *03.04.2015 | Mutter Lila [Kleine Chaoten] | Vater Jack Jr. [Kleine Chaoten] | 4/6". Beide Elterntiere existieren bereits mit plausibler Lebenszeit (Jack Jr. von den Kleinen Chaoten 595361b4 *19.06.2013; Lila dacb12a3 *13.02.2014, +15.05.2015). Konsistenzcheck: Wurfschwester Viven (*03.04.2015) passt zur bekannten Geschwisterverpaarung Spike x Viven (H9-Wurf 29.01.2016, xlsx Z.256). Kein Rückfall durch die 12 neuen Charts vom 2026-08-18 — der Wurf stammt komplett aus der Wurfchronik, keine frühere Entscheidung betroffen. + +**Fix:** + +Kein Eingriff am D7-Wurf (aa48fc4a) — Spike bleibt Vater. Ein Datenfix, re-ingest-stabil über den bestehenden litterParents-Mechanismus (merge_and_resolve.py ~Z.4098, Match litterName+date, Auflösung via name+dob): neuer Eintrag im Array "litterParents" in C:\Users\gulum\dev\GerbilManager\tools\import\conflict-decisions.json: + +{ + "litterName": "TS-Wurf", + "date": "03.04.2015", + "father": "Jack Jr. von den Kleinen Chaoten", + "fatherDob": "19.06.2013", + "mother": "Lila", + "motherDob": "13.02.2014", + "source": "Ticket 5851bb94: Wurfchronik-xlsx sheet1 Z.162 (T5-Wurf *03.04.2015, Lila x Jack Jr.) + Overview 'Wurfchronik Teil 1_page_0044.md' ('TS-Wurf (Jack Jr + Lila)'); die Seiten-Extraktion 0044 liefert für alle Würfe fatherId/motherId=null" +} + +Danach: python tools/import/merge_and_resolve.py, Stichprobe (Wurf 1e49ad0e hat FatherId 595361b4 / MotherId dacb12a3, Spike 73d087f8 unverändert an diesem Wurf), dann Upload-Ingest gegen prod und Ticket mit fixNoteDraft schließen. + +NICHT in diesem Ticket fixen (eigenes Ticket wert): auf Seite 0044 sind die Wurfbuchstaben OCR-verlesen ("5" als "S": TS=T5, SS=S5, US=U5, VS=V5), und die Buchstaben der Seite 0052 sind gegenüber der xlsx um einen verschoben (xlsx C7 *14.08.2015 = App "D7-Wurf"). Rein kosmetisch, betrifft keine Abstammung, aber verwirrend für die Züchterin. + +**Changelog-Entwurf für die Züchterin:** Der Eintrag ist richtig: In deiner Wurfchronik steht auf der Seite mit den August-2015-Würfen von Hand "Spike + Jacky *14.08.15" mit genau den drei Jungtieren Smartie, Cookie und Cloe - und in deiner Wurfchronik-Tabelle dieselbe Verpaarung (dort mit dem Zusatz "Spike / Jack Jr.?"). Mein Einwand, Spike sei mit gut 4 Monaten zu jung gewesen, war falsch: Böcke können schon ab etwa 2-3 Monaten Vater werden. Die Verpaarung Spike x Jacky bleibt also so stehen. Zusätzlich habe ich ergänzt, woher Spike selbst stammt: aus deinem Wurf vom 03.04.2015 von Lila und Jack Jr. von den Kleinen Chaoten - das stand bisher nirgends bei ihm, weil bei diesem Wurf die Elterntiere fehlten. Damit siehst du auf Spikes Seite jetzt auch seine Eltern und seine Wurfschwester Viven, mit der er später den Wurf vom 29.01.2016 hatte. Falls du sicher weißt, dass doch Jack Jr. der Vater von Smartie, Cookie und Cloe war, sag Bescheid - dann tausche ich den Vater dort. + +**Betroffen:** tools/import/conflict-decisions.json (litterParents); Wurf 1e49ad0e TS-Wurf 03.04.2015; Gerbil 73d087f8 Spike; Wurf aa48fc4a D7-Wurf (unverändert) + +--- + +### Fast Boy of Golden Lights (*31.03.2017) — beide Eltern im Stammbaum falsch (Großvater als Vater, Schwiegermutter als Mutter) + +Tickets: `6c89082e-4060-4ec8-8ebc-c287bdd6f31d` · Layer **import-data** · Cluster `conflict-decisions-resolutions-parents` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Beide Elternteile sind falsch importiert. Prod zeigt über den virtuellen Wurf 4d3afe0a ("Wurf von Danjo of Golden Lights + Xtra Bounty von den Schlossmäusen", 31.03.2017, einziges Kind = Fast Boy) Vater = Danjo of Golden Lights (cbac1d65, ohne DOB/Geschlecht) und Mutter = Xtra Bounty von den Schlossmäusen (354efa96). Belegt richtig sind Vater = Charly of Golden Lights (*05.04.2016, prod 80987264, male) und Mutter = Ziwa of Golden Lights (*29.04.2016, prod 26986f3a, female): (1) `python tools/import/rpro3_lookup.py tools/import/_rpro3.db "Fast Boy"` → "eigenes Tier, *2017-03-31 … Eltern: Vater Charly · Mutter Ziwa", Nachzucht Erestor *05.04.2018 mit Partner Addison; (2) Paar-Zelle R35 in C:/Users/gulum/dev/Sttammbäume/"Stammbaum von CP-Fuchs, CP-Sa Sp von Unity.xlsx" = "Charly of Golden Lights & Ziwa of Golden Lights", auf derselben Zeile wie Fast Boy (Q35); (3) "Stammbaum von Kid von Cassy & Blaster.xlsx": N75 Fast Boy, Q74 Charly *05.04.2016, Q75 Ziwa *29.04.2016. Danjo ist laut rpro3 der Vater von Ziwa, also Fast Boys GROSSVATER (Ziwa *29.04.2016 → Vater Danjo, Mutter Isiri); Xtra Bounty (*20.03.2014, eigenes Tier, Schlossmäuse, keine Nachzucht in rpro3) ist die Mutterlinie von Zuleika, Fast Boys Partnerin, die im Chart einen Block darunter steht (N62/N76 Zuleika mit Q62/Q76 Xtra Bounty + Q63/Q77 Ruby). URSACHE: die Chart-Positions-Heuristik. tools/import/output/animals.json führt für Fast Boy drei widersprüchliche parentRefs (Danjo/father, Xtra Bounty/mother, Ziwa/father) — der echte Vater Charly wurde NIE als parentRef erfasst, weil er im Chart eine Zeile ÜBER dem Kind steht (Q60/Q74), während die Großeltern-Paarzelle R61 "Isiri of Golden Lights & Danjo of Golden Lights" (gehört zu Ziwas Zeile 61) auf Fast Boys Zeile fällt. Anschließend verwarf `pick_parent_ref` Ziwa für die Vaterrolle ("falsches Geschlecht für die Vaterrolle" — steht wörtlich in der Provenance des Tiers) und fiel auf Danjo zurück. Die Zeilen-Offsets sind im xlsx nicht durch mergeCells gestützt (geprüft: im Bereich N/Q Zeilen 56–68 von "Stammbaum von Akio Kids.xlsx" existiert KEIN einziger Merge) und variieren von Block zu Block — ein Code-Fix der Heuristik ist deshalb nicht sicher möglich. Kein Rückfall durch die 12 neuen Charts vom 2026-08-18: es gab für dieses Tier bisher überhaupt keine Entscheidung (kein "Fast Boy"/"Ziwa"/"Charly of Golden" in conflict-decisions.json, kein Eintrag in rpro3-decisions.json). + +**Fix:** + +Owner-Regel: Datenfix im Importer, kein Live-Patch. In tools/import/conflict-decisions.json → Array "resolutions" die folgenden ZWEI Einträge anhängen (Muster identisch zu den 41 bestehenden father/mother-Einträgen; `extract.apply_conflict_decisions` ersetzt damit die kompletten parentRefs mit method "decision"/confidence "high"). fatherDob/motherDob sind zwingend mitzugeben, weil animals.json je zwei Charly-/Ziwa-Datensätze kennt (datenloser Paarzellen-Platzhalter ohne dob + datiertes Tier): + + { + "name": "Fast Boy of Golden Lights", + "dob": "31.03.2017", + "decision": "parents are Charly of Golden Lights x Ziwa of Golden Lights, NOT Danjo (= Ziwa's father, i.e. the grandfather) + Xtra Bounty (= Zuleika's dam line) - chart-position misread", + "father": "Charly of Golden Lights", + "fatherDob": "05.04.2016", + "mother": "Ziwa of Golden Lights", + "motherDob": "29.04.2016", + "source": "Ticket 6c89082e - RennmausPro III (_rpro3.db: Vater Charly / Mutter Ziwa) + Paarzelle R35 in 'Stammbaum von CP-Fuchs, CP-Sa Sp von Unity.xlsx' + Q74/Q75 in 'Stammbaum von Kid von Cassy & Blaster.xlsx'" + }, + { + "name": "Ziwa of Golden Lights", + "dob": "29.04.2016", + "decision": "father = Danjo of Golden Lights, mother = Isiri of Golden Lights (rpro3; die Paarzelle R61 'Isiri of Golden Lights & Danjo of Golden Lights' in 'Stammbaum von Akio Kids.xlsx' ist rollenvertauscht)", + "father": "Danjo of Golden Lights", + "mother": "Isiri of Golden Lights", + "source": "Ticket 6c89082e - RennmausPro III (_rpro3.db)" + } + +Der zweite Eintrag ist optional-aber-empfohlen: er hängt Danjo (+ Isiri) dort ein, wo sie hingehören (als Ziwas Eltern = Fast Boys Großeltern), sodass der Stammbaum eine Generation tiefer korrekt wird statt bei Ziwa abzubrechen. Beide Elternteile haben in prod unknown gender/kein DOB, die explizite Rollenangabe ist also nötig, weil die Paar-Reihenfolge im Chart hier vertauscht ist. + +Danach: `python tools/import/extract.py` → `python tools/import/merge_and_resolve.py`, `python test_merge_resolve.py` grün, und in tools/import/output/resolved_import.json prüfen, dass Fast Boy (externalRef `stammbaum-fastboygoldenlights-31032017`) an einem Wurf mit FatherId = Charly of Golden Lights *05.04.2016 und MotherId = Ziwa *29.04.2016 hängt; dann Upload-Ingest gegen Prod (`curl -X POST http://truenas:8090/api/import/ingest-resolved/upload -F "resolved=@tools/import/output/resolved_import.json"`). Erwarteter Effekt: der virtuelle Wurf 4d3afe0a (Danjo × Xtra Bounty) verschwindet, weil er dadurch kinderlos und virtuell ist; ein neuer virtueller Wurf Charly × Ziwa vom 31.03.2017 mit Kind Fast Boy entsteht. Altersplausibilität ok (beide Eltern ~11 Monate bei Geburt, `parent_age_plausible` greift nicht), Geschlechter passen (Charly male → Vaterrolle, Ziwa female → Mutterrolle), Xtra Bounty bleibt unverändert als Elternteil von Zuleika erhalten. + +**Changelog-Entwurf für die Züchterin:** Bei Fast Boy of Golden Lights waren im Stammbaum beide Eltern falsch: als Vater stand Danjo of Golden Lights, der aber in Wirklichkeit sein Großvater ist (Danjo ist der Vater von Ziwa), und als Mutter stand Xtra Bounty von den Schlossmäusen, die zur Familie seiner Partnerin Zuleika gehört. Ursache war ein Verrutschen um eine Zeile beim Auslesen der Stammbaum-Tabellen — dort steht der Vater je nach Block manchmal eine Zeile höher, und dadurch wurde die Elternangabe der Großeltern-Zeile auf Fast Boy bezogen. Korrigiert nach deinen Angaben aus RennmausPro und den Stammbaum-Dateien: Vater ist jetzt Charly of Golden Lights (geb. 05.04.2016), Mutter ist Ziwa of Golden Lights (geb. 29.04.2016). Zusätzlich sind Danjo und Isiri of Golden Lights jetzt als Ziwas Eltern eingetragen, also als Fast Boys Großeltern — der Stammbaum reicht damit eine Generation weiter und stimmt wieder. + +**Betroffen:** tools/import/conflict-decisions.json (resolutions); tools/import/extract.py (_reconstruct_parents / Chart-Positions-Heuristik, nur als Ursache — kein Code-Fix); tools/import/merge_and_resolve.py (pick_parent_ref, virtuelle Wuerfe); Stammbaum-Ansicht / Tierakte Fast Boy of Golden Lights; virtueller Wurf 4d3afe0a-1d78-5c55-bd99-4f216bd84afc + +--- + +### JackJack (b6d8b3ef) — Abgabedatum-Zeile unter Abnehmer in der Tier-Akte (+ kaputtes Abgabedatum 1310-05-13) + +Tickets: `24522f5f-8c31-46c7-8dd0-d9a88870febb` · Layer **frontend** · Cluster `gerbil-detail-abgabedatum-goHomeDate` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Zwei Ursachen. (1) FRONTEND: gerbil-manager-web/src/pages/GerbilDetailPage.tsx Z.493-495 rendert die Abgabedatum-Zeile nur bei `g.status === 'GivenAway'`, waehrend die Abnehmer-Zeile (Z.552-554) schon bei `g.receiverContactId || g.status !== 'Deceased'` erscheint. JackJack (prod b6d8b3ef, status=Deceased, receiverContactId=3192070e = "Larissa Draeger + Nicolas", goHomeDate gesetzt) zeigt daher den Abnehmer, aber kein Abgabedatum — genau die fehlende Zeile. (2) DATEN (blockiert 1): goHomeDate von JackJack ist "1310-05-13". Quelle ist die Transkription C:\Users\gulum\dev\Wurfchronik_Bilder\Wurfchronik Teil 1_page_0013.md, Eintrag g1000000-...-0002: `"givenAwayDate": "1310-05-13"`, obwohl die Notiz derselben Zeile "→ Larissa Draeger + Nicolas 13.5.10" = 13.05.2010 sagt (Wurfbruder Bodo auf derselben Seite: korrekt "2010-06-06"). parse_date() (tools/import/merge_and_resolve.py Z.595ff) mappt nur 1900{formatDate(g.goHomeDate)} + )} + +A2) Z.552-554 ersetzen durch (Abgabedatum direkt UNTER Abnehmer): + {/* Ticket 24522f5f (JackJack): Abgabedatum gehoert direkt unter den Abnehmer und + darf nicht am Status haengen — Tiere, die abgegeben wurden und spaeter beim + Abnehmer starben (status=Deceased), haben Abnehmer UND Abgabedatum. */} + {(g.receiverContactId || g.goHomeDate || g.status !== 'Deceased') && ( + <> + {lookup(contactName, g.receiverContactId)} + {formatDate(g.goHomeDate)} + + )} +Keine neuen Strings noetig (de.ts Z.94 `goHomeDate: 'Abgabedatum'`); formatDate(null) -> '—'. + +A3) Optionaler Regressionsschutz: in gerbil-manager-web/e2e/mock-data.ts bei 'willi' (Z.180, Deceased) `receiverContactId: 'con-huber', goHomeDate: '2022-01-15'` ergaenzen und in e2e/tiere.spec.ts assert, dass die Akte de.pages.gerbils.fields.goHomeDate zeigt. + +B) DATEN/IMPORT (zwingend zusammen mit A, sonst zeigt die neue Zeile "13.05.1310") + +B1) tools/import/merge_and_resolve.py — neuen Resolution-Key `goHomeDate` zulassen. Im has_ovr-Tuple (Z.3211-3214) ergaenzen: + or d.get("goHomeDate") +und im Apply-Block direkt NACH dem dateOfDeath-Zweig (nach Z.3294) einfuegen: + # goHomeDate: setzt/korrigiert das Abgabedatum autoritativ (Ticket 24522f5f). + # Nötig, weil Wurfchronik-Transkriptionen das Datum falsch zusammenbauen + # können (JackJack: "1310-05-13" statt 13.05.2010). + if d.get("goHomeDate"): + _gh = parse_date(d["goHomeDate"]) + if _gh and g.get("GoHomeDate") != _gh: + g["GoHomeDate"] = _gh + if g.get("Status") not in ("Deceased",) and not g.get("ReceiverContactId"): + pass # Status bewusst unangetastet + applied = True + +B2) tools/import/conflict-decisions.json — bestehenden JackSack-Block (Z.1087-1099) um eine Zeile erweitern (Match ueber name+dob greift, das Tier heisst im resolved noch "JackSack"): + { + "name": "JackSack", + "dob": "18.02.2010", + "mergeExternalRefs": [ + [ + "Wurfchronik Teil 1_page_0013.md-g1000000-0000-0000-0000-000000000002", + "Wurfchronik Teil 1_page_0001.md-3d8c071c-f172-44f6-aff5-ca392c284bf3" + ] + ], + "renameTo": "JackJack", + "goHomeDate": "13.05.2010", + "decision": "Dublette JackSack/JackJack = dasselbe Tier (A-Wurf, *18.02.2010). Zusammenfuehren, richtiger Name JackJack. Abgabedatum aus der Wurfchronik-Notiz '-> Larissa Draeger + Nicolas 13.5.10' = 13.05.2010 (Quelle transkribiert faelschlich 1310-05-13).", + "source": "Zuechterin - Tickets 0cf6b838, 24522f5f" + }, + +B3) tools/import/merge_and_resolve.py parse_date() — Guard gegen implausible Jahre, damit kuenftige Fehl-Transkriptionen kein 14.-Jahrhundert-Datum importieren. Im ISO-Zweig (Z.601-610) vor `return d`: + if year < 1900 or year > datetime.now().year + 1: + return None +(analog im DD.MM.YYYY-Zweig nach der Jahres-Normalisierung) + Regressionstest in tools/import/test_merge_resolve.py: parse_date("1310-05-13") is None, parse_date("13.5.10") == "2010-05-13". + +C) VERIFIKATION: npx tsc --noEmit, npx vitest run, npx eslint auf GerbilDetailPage.tsx, betroffene playwright-Specs; python test_merge_resolve.py; merge_and_resolve.py neu laufen lassen und pruefen, dass b6d8b3ef GoHomeDate == "2010-05-13" hat; dann Upload-Ingest gegen Prod (POST /api/import/ingest-resolved/upload) und Akte pruefen: "Abnehmer: Larissa Draeger + Nicolas" + "Abgabedatum: 13.05.2010". + +**Changelog-Entwurf für die Züchterin:** In der Tier-Akte steht das Abgabedatum jetzt direkt unter dem Abnehmer — und zwar immer, nicht nur bei Tieren mit Status „abgegeben". Bei JackJack war ausserdem das Abgabedatum aus der Wurfchronik falsch uebertragen worden (es stand ein Datum aus dem Jahr 1310); richtig ist der 13.05.2010, wie es in deiner Notiz „→ Larissa Draeger + Nicolas 13.5.10" steht. Das ist korrigiert, und der Importer verwirft solche unmoeglichen Jahreszahlen künftig, statt sie zu uebernehmen. + +**Betroffen:** gerbil-manager-web/src/pages/GerbilDetailPage.tsx; gerbil-manager-web/e2e/mock-data.ts; tools/import/merge_and_resolve.py; tools/import/conflict-decisions.json; tools/import/test_merge_resolve.py + +--- + +### Eliza *20.08.2010 (E-Wurf Blacky x Kuke) — Gencode + Scheckungsart Ansatzschecke + +Tickets: `a547be62-3611-43b8-b72b-446978d545bf` · Layer **import-logic** · Cluster `decisions-genotype-spottingtype-override` · Aufwand M · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Eliza (29cbd5e6, Quelle: Wurfchronik-md „Wurfchronik Teil 1_page_0013.md", Roh-Id g1000000-…-011) hat in der Quelle NUR colorDescription „Schwarz-Sp", keinen Gencode. Der angezeigte „aa CC DD EE GG PP Spsp rere" ist reiner Kanon-Fill: resolve_color_and_genotype() nimmt den Katalog-Genotyp der Variety „Schwarz" (ColorVariety 007) und hängt Spsp an, weil das Label scheckig klingt. Die Antwort der Züchterin („aa C- DD E- G- P- Spsp", Ansatzschecke) ist damit echte Neuinformation; genotype.parse() nimmt den String sauber an (unmappedTokens leer) und liefert Farbschlag „Schwarz" → ColorVarietyId 007 bleibt korrekt. Ein Nachtragen scheitert aktuell an ZWEI Lücken: (1) tools/import/merge_and_resolve.py ~Z.3302 wendet einen decision-`genotype` nur an, wenn Genotype LEER ist (`if d.get("genotype") and not g.get("Genotype")`) — genau dieser Guard hat bereits die vorhandene Entscheidung für CHRIS (externalRef „Wurfchronik Teil 1_page_0013.md-g1000000-0000-0000-0000-000000000007", soll „aa Cc[-] DD Ee gg P- Spsp", ist „aa CC DD EE gg PP Spsp rere") stillschweigend verschluckt; nur Tiere ohne Quell-Genotyp (Jana, Kyara) bekamen ihren Override. (2) `spottingType` existiert im Importer-Pfad gar nicht, und GerbilManagerWebAPI/Import/IngestResolvedService.cs (Upsert-Block Z.126–152) kopiert Gerbil.SpottingType NICHT — ein Payload-Wert würde ignoriert. Eliza hat zwar einen GerbilOverride, der friert aber nur „causeOfDeath" ein, blockiert also nichts. + +**Fix:** + +1) DATEN — tools/import/conflict-decisions.json: die BESTEHENDE Eliza-Resolution (Key externalRef …-011, Ticket aa5238b6) IN PLACE erweitern, KEINEN zweiten Eintrag mit derselben externalRef anlegen (`_ovr_by_extref` ist ein dict, der letzte gewinnt → isResident/notes gingen verloren): +{"externalRef": "Wurfchronik Teil 1_page_0013.md-g1000000-0000-0000-0000-000000000011", "isResident": false, "genotype": "aa C- DD E- G- P- Spsp", "spottingType": "Ansatzschecke", "notes": "Schwarz-Schecke; Tochter von Blacky x Kuke (E-Wurf 2010); verstorben 06.04.2011 (Ausbruch-Unfall). War nie Zuchttier. NICHT die Mutter des J5-Wurfs — das ist die spätere Eliza (*13.02.2014).", "decision": "Zweite/2010er Eliza von der J5-Mutter getrennt; irreführende 'Mutter des J5-Wurfs'-Notiz entfernt. Gencode + Scheckungsart von der Züchterin: aa C- DD E- G- P- Spsp, Ansatzschecke (bisheriger Wert war nur Kanon-Fill aus dem Farbschlag 'Schwarz-Sp').", "source": "Ticket aa5238b6 + Ticket a547be62"} + +2) IMPORTER — tools/import/merge_and_resolve.py, Override-Block: +(a) has_ovr-Guard (~Z.3211-3216): `or d.get("originBreeder"))` → `or d.get("originBreeder") or d.get("spottingType"))`. +(b) Match-Schleife (~Z.3228-3238): Präzision des Matches merken. Vor `if er and er in _ovr_by_extref:` ein `_precise = False` setzen; in den beiden externalRef-Zweigen `_precise = True`; im Zweig `elif (ck, iso) in _ovr_by_namedob:` zusätzlich `_precise = bool(iso)`; der name-only-Zweig `(ck, "")` bleibt unpräzise. +(c) Z.3302-3304 ersetzen: + # Ein expliziter Gencode aus einer Züchterin-Entscheidung ist AUTORITATIV und + # überschreibt auch einen vorhandenen Wert — bei Wurfchronik-Tieren ist der meist + # nur der Kanon-Fill des Farbschlags (resolve_color_and_genotype), keine echte + # Gencode-Quelle. Nur bei PRÄZISEM Match (externalRef oder name+dob), damit eine + # name-only-Entscheidung nicht den Gencode eines gleichnamigen fremden Tieres + # überschreibt. (Ticket a547be62 Eliza; repariert auch Chris/page_0013.) + if d.get("genotype"): + _geno_ovr = d["genotype"].strip() + if (_precise or not g.get("Genotype")) and g.get("Genotype") != _geno_ovr: + g["Genotype"] = _geno_ovr + applied = True + # Scheckungsart (freier Text, Werte wie in de.ts `spottingTypes`, z. B. „Ansatzschecke"). + # Der Importer leitet sie aus keiner Quelle ab — nur per Entscheidung setzbar. + if d.get("spottingType"): + _sp_ovr = d["spottingType"].strip() + if g.get("SpottingType") != _sp_ovr: + g["SpottingType"] = _sp_ovr + applied = True + +3) BACKEND — GerbilManagerWebAPI/Import/IngestResolvedService.cs (Upsert der Gerbils): +im Update-Zweig hinter `eg.GoHomeDate = g.GoHomeDate; eg.Genotype = g.Genotype; eg.Notes = g.Notes;` einfügen: + // SpottingType nur übernehmen, wenn der Payload einen Wert liefert (der Importer + // kennt die Scheckungsart nur über conflict-decisions) — sonst würde eine von Hand + // erfasste Scheckungsart bei jedem Ingest genullt. + if (g.SpottingType is not null) eg.SpottingType = g.SpottingType; +und im `new Gerbil { … }`-Initializer `SpottingType = g.SpottingType,` ergänzen. (Keine Migration nötig, Spalte existiert; `ResolvedImportData.Gerbils` ist List, „SpottingType" deserialisiert automatisch.) + +4) TESTS: tools/import/test_merge_resolve.py — Regression: Resolution mit genotype+spottingType per externalRef überschreibt einen NICHT-leeren (kanon-gefüllten) Genotyp und setzt SpottingType; name-only-Resolution überschreibt einen vorhandenen Genotyp NICHT. GerbilManager.Tests/IngestResolvedServiceTests.cs — SpottingType-Roundtrip (Payload-Wert wird geschrieben; null im Payload lässt einen bestehenden Wert stehen). + +5) AUSFÜHREN/PRÜFEN: nur `python tools/import/merge_and_resolve.py` nötig (animals.json/extract unverändert, weil die Entscheidung ein Wurfchronik-md-Tier trifft), dann Upload-Ingest gegen Prod. Erwartet: Eliza → Genotype „aa C- DD E- G- P- Spsp", SpottingType „Ansatzschecke", ColorVarietyId weiter 00000000-0000-0000-0000-000000000007; Chris → „aa Cc[-] DD Ee gg P- Spsp". Genotype-Diff des gesamten Payloads gegen den alten Stand sichten — es dürfen nur Tiere mit eigener Entscheidung wechseln. + +**Changelog-Entwurf für die Züchterin:** Elizas Gencode (*20.08.2010, Tochter von Blacky und Kuke) steht jetzt so in der Akte, wie du ihn angegeben hast: aa C- DD E- G- P- Spsp, Scheckungsart „Ansatzschecke". Vorher stand dort ein automatisch aus dem Farbschlag „Schwarz-Schecke" errechneter Wert, weil die Wurfchronik zu ihr keinen Gencode nennt. Dabei ist außerdem aufgefallen, dass deine Gencode-Angaben bisher nur dann übernommen wurden, wenn noch gar kein Wert vorhanden war — deshalb war z. B. auch bei Chris (gleicher Wurf) noch der automatische Wert zu sehen. Das ist behoben: dein Wert hat jetzt immer Vorrang, und die Scheckungsart lässt sich ebenfalls dauerhaft mitgeben. Beides bleibt bei künftigen Daten-Aktualisierungen erhalten. + +**Betroffen:** tools/import/conflict-decisions.json; tools/import/merge_and_resolve.py (Override-Block ~Z.3211/3228/3302); GerbilManagerWebAPI/Import/IngestResolvedService.cs (Gerbil-Upsert Z.126-152); tools/import/test_merge_resolve.py; GerbilManager.Tests/IngestResolvedServiceTests.cs + +--- + +### Jamie (*27.12.2010, J-Wurf Danny x Jana) — Dublette: zwei weitere Wurfchronik-Elternstubs nie gemergt + +Tickets: `66ef9bdd-1a92-46cc-a268-8c1ce1bc3aaa, 3bbd6ab4-d9e4-4f02-a4de-952ff0dd3738` · Layer **import-data** · Cluster `conflict-decisions-mergeexternalrefs-wurfchronik-elternstubs` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Kein Rueckfall durch die 12 neuen Charts vom 18.08. (Wurfchronik-md-Dateien in C:\Users\gulum\dev\Wurfchronik_Bilder sind seit Juni unveraendert; Ticket 3bbd6ab4 stammt vom 13.08., also VOR dem Chart-Import). Ursache ist eine unvollstaendige alte Entscheidung: Jamie hat pro Wurfchronik-Seite, auf der er als Elternteil steht, einen eigenen dobless "empty shell"-Record. Der Kanon ist 8ddf3717 "Jamie von den kleinen Chaoten" *27.12.2010 (ExternalRef stammbaum-jamiekleinenchaoten-27122010). Die Resolution aus 06b6734d/e96db323/c092f8d2 enthaelt nur EIN mergeExternalRefs-Paar (Drop "Wurfchronik Teil 1_page_0022.md-page_0022_parent_jamie") — das greift korrekt (page_0022-Stub ist verschwunden). Die Stubs von page_0017 (id f651e1cf-54bc-4d8b-9be0-4b6298e7ab48, "Parent listed on page 0017") und page_0023 (id 7cbb62c5-7a46-4cb4-a3ad-bf7f94080e14, "Father of H2-Wurf") blieben uebrig und wurden vom Namens-Dedup untereinander zur gemeldeten Dublette 4e4e507f verschmolzen (Provenance: mergedRecordCount 2, sourceFiles Wurfchronik + page_0017); gegen den Full-Name-Kanon mergen sie nicht (Call-Name "Jamie" ohne DOB). Auswirkung: 9 Wuerfe haengen am falschen Jamie (U 13.12.2011, X 10.01.2012, E1 26.03.2012, M1 08.07.2012, H2 13.04.2013, L2 20.05.2013, N2 14.06.2013, V2 27.07.2013, W2 29.07.2013), der Kanon hat nur A2, D2 und den Chart-Wurf. Derselbe Fehler bei der H2-Mutter: Stub 336ad972 "Sakura" (page_0023 ...080e15) neben Kanon 4be212ef "Gwen gen. Sakura of little Angels" *17.12.2011 — die Sakura-Resolution (Index 147, Ticket 7f9b3724) nennt im Decision-Text ausdruecklich A2/D2/H2, hat aber nur page_0022_parent_sakura als Drop-Ref. Ohne diesen zweiten Merge bleiben nach dem Jamie-Merge zwei Wuerfe vom 13.04.2013 am Kanon stehen, weil deduped2 gleiches Datum UND beide Eltern identisch verlangt. mergeExternalRefs laeuft vor dem Gerbil-Dedup (merge_and_resolve.py L2743 auf all_processed_gerbils ab L2209) und remappt Litter-Father/MotherId (L3443-3448) — beide Stub-Records existieren zu diesem Zeitpunkt noch getrennt, daher sind beide Drop-Paare noetig. + +**Fix:** + +Nur tools/import/conflict-decisions.json (Owner-Regel, re-ingest-stabil, deterministische Refs). + +(1) In der bestehenden Resolution {"name":"Jamie von den kleinen Chaoten","dob":"27.12.2010", ...} das Feld mergeExternalRefs komplett ersetzen durch: +"mergeExternalRefs": [ + ["stammbaum-jamiekleinenchaoten-27122010", "Wurfchronik Teil 1_page_0022.md-page_0022_parent_jamie"], + ["stammbaum-jamiekleinenchaoten-27122010", "Wurfchronik Teil 1_page_0017.md-f651e1cf-54bc-4d8b-9be0-4b6298e7ab48"], + ["stammbaum-jamiekleinenchaoten-27122010", "Wurfchronik Teil 1_page_0023.md-7cbb62c5-7a46-4cb4-a3ad-bf7f94080e14"] +] +und im "source"-Feld ergaenzen: " + Tickets 66ef9bdd/3bbd6ab4 (restliche Wurfchronik-Elternstubs page_0017/page_0023)". + +(2) In der Sakura-Resolution (resolutions-Index 147, {"externalRef":"stammbaum-gwengensakuralittleangels-17122011", ...}) mergeExternalRefs ersetzen durch: +"mergeExternalRefs": [ + ["gwengensakuralittleangels-17122011", "page_0022_parent_sakura"], + ["gwengensakuralittleangels-17122011", "Wurfchronik Teil 1_page_0023.md-7cbb62c5-7a46-4cb4-a3ad-bf7f94080e15"] +] +(Pflicht, sonst zeigt Jamie zwei Wuerfe vom 13.04.2013.) + +(3) Optional (mittlere Sicherheit, Haupt-Loop entscheidet — sonst separates Ticket): neue Resolution anhaengen, damit im gemergten Wurf nicht Akina und Alkina doppelt stehen: +{ + "externalRef": "stammbaum-akinakleinenchaoten-13042013", + "mergeExternalRefs": [["stammbaum-akinakleinenchaoten-13042013", "Wurfchronik Teil 1_page_0023.md-a2867c29-3733-4f9e-a4b5-bf6e44b9d031"]], + "decision": "'Alkina' (Wurfchronik H2-Wurf 13.04.2013, behalten, +01.12.2017) und 'Akina von den Kleinen Chaoten' (*13.04.2013, Mutter R3/G5) sind dasselbe Tier: H2 hatte laut Wurfchronik genau 5 Junge (Enzo, Schmidti, Lilly, Anthrazit, Alkina), Akina kann kein 6. Jungtier sein; Buchstabendreher blockiert den Auto-Merge.", + "source": "Tickets 66ef9bdd/3bbd6ab4" +} + +Danach: python tools/import/merge_and_resolve.py, python test_merge_resolve.py, Stichprobe (Kanon 8ddf3717 muss U/X/E1/M1/H2+Chart-Wurf/L2/N2/V2/W2 + A2/D2 tragen; 4e4e507f und 336ad972 duerfen in resolved_import.json nicht mehr vorkommen; genau ein Wurf am 13.04.2013), dann Upload-Ingest gegen Prod (POST /api/import/ingest-resolved/upload). Keine Live-DB-Patches. + +**Changelog-Entwurf für die Züchterin:** Die beiden Jamie-Einträge sind jetzt endgültig zusammengeführt: Es bleibt nur der Jamie aus dem J-Wurf von Danny und Jana (geboren 27.12.2010). Beim letzten Mal war nur eine von drei Fundstellen aus der Wurfchronik zusammengeführt worden, deshalb tauchte er wieder doppelt auf. Jetzt landen alle seine Würfe bei ihm: U, X, E1, M1, H2, L2, N2, V2 und W2 zusätzlich zu A2 und D2. Gleichzeitig war seine Partnerin Sakura doppelt vorhanden — auch sie ist jetzt ein Tier (Gwen gen. Sakura of little Angels, geboren 17.12.2011), wodurch der Wurf vom 13.04.2013 nur noch einmal in der Liste steht und alle Jungtiere beisammen sind. + +**Betroffen:** tools/import/conflict-decisions.json; tools/import/merge_and_resolve.py (mergeExternalRefs L2743-2786, Litter-Remap L3443-3448, deduped2 L3732); Gerbil 8ddf3717 Jamie von den kleinen Chaoten; Gerbil 4e4e507f Jamie (Dublette); Gerbil 336ad972 Sakura (Dublette); Litter 8f043703 H2-Wurf + Litter 27ab49ae (13.04.2013) + +--- + +### Sakura / Gwen gen. Sakura of little Angels — Dublette nach Merge-Fix zurueck (Folge-Ticket zu 7f9b3724) + +Tickets: `6c9539d2-91e2-4323-a76a-c8a1962dbca9` · Layer **import-data** · Cluster `wurfchronik-md-stub-dubletten-mergeexternalrefs` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Kein Effekt der 12 neuen Stammbaum-Charts, sondern eine unvollstaendige alte Entscheidung. Die Wurfchronik-Quelle (LLM-Markdown-Seiten in C:\Users\gulum\dev\Wurfchronik_Bilder, DIR_PATH in tools/import/merge_and_resolve.py) enthaelt ZWEI Sakura-Stubs: "Wurfchronik Teil 1_page_0022.md" (lokale id "page_0022_parent_sakura") und "Wurfchronik Teil 1_page_0023.md" (lokale id 7cbb62c5-7a46-4cb4-a3ad-bf7f94080e15). Die Entscheidung zu Ticket 7f9b3724 (tools/import/conflict-decisions.json, Eintrag externalRef "stammbaum-gwengensakuralittleangels-17122011", Zeilen ~1281-1293) listet in mergeExternalRefs nur den page_0022-Stub. Vor dem Fix wurden beide Stubs vom Call-Name-Dedup zu EINEM Tier verschmolzen (alte Id f9ba065c-dfb7-5c0f-bbf6-0d3c2e4afed9, in Prod jetzt 404). Da mergeExternalRefs VOR dem Dedup laeuft (merge_and_resolve.py Z.2743-2785) und den page_0022-Record entfernt, bleibt der page_0023-Stub als eigenes Cluster uebrig -> neue deterministische Id 336ad972-4e5f-50cb-92c9-71ce266d36a6 = uuid5(NAMESPACE_DNS, "Wurfchronik Teil 1_page_0023.md-7cbb62c5-7a46-4cb4-a3ad-bf7f94080e15") (nachgerechnet, stimmt exakt). Nicht gefundene Drop-Refs bzw. uebrig gebliebene Zwillinge werden still uebergangen (`continue`, keine Warnung). Identisches Muster bei JAMIE: Wurfchronik-Stub 4e4e507f-1ede-51e7-9079-ce6e7a4cb4af (ref "…page_0023.md-7cbb62c5-…e14") vs. kanonisches "Jamie von den kleinen Chaoten" 8ddf3717-5608-50d1-8b3d-78b8b198327a; die Jamie-Entscheidung (Z.~"stammbaum-jamiekleinenchaoten-27122010") merged ebenfalls nur page_0022_parent_jamie. Prod-Folgeschaden: H2-Wurf 8f043703 (13.04.2013) haengt an den beiden Stubs, waehrend Gwen 4be212ef zusaetzlich den chart-basierten Wurf 27ab49ae gleichen Datums traegt; L2-Wurf 66d5831a hat Mutter Gwen, aber Vater = Jamie-Stub. + +**Fix:** + +DATENFIX in tools/import/conflict-decisions.json (re-ingest-stabil, deterministisch), zwei Eintraege ergaenzen: + +1) Eintrag mit "externalRef": "stammbaum-gwengensakuralittleangels-17122011" (Ticket 7f9b3724) — mergeExternalRefs erweitern auf: +"mergeExternalRefs": [ + ["gwengensakuralittleangels-17122011", "page_0022_parent_sakura"], + ["gwengensakuralittleangels-17122011", "Wurfchronik Teil 1_page_0023.md-7cbb62c5-7a46-4cb4-a3ad-bf7f94080e15"] +] +(Quelle-Feld ergaenzen: "source": "… + Ticket 6c9539d2 (zweiter Wurfchronik-Stub page_0023)") + +2) Eintrag {"name": "Jamie von den kleinen Chaoten", "dob": "27.12.2010", …} — mergeExternalRefs erweitern auf: +"mergeExternalRefs": [ + ["stammbaum-jamiekleinenchaoten-27122010", "Wurfchronik Teil 1_page_0022.md-page_0022_parent_jamie"], + ["stammbaum-jamiekleinenchaoten-27122010", "Wurfchronik Teil 1_page_0023.md-7cbb62c5-7a46-4cb4-a3ad-bf7f94080e14"] +] +WICHTIG: immer den VOLLEN Ref inkl. Dateiname verwenden — die page_0023-UUIDs unterscheiden sich nur im letzten Zeichen (…e13 = JD, …e14 = Jamie, …e15 = Sakura), Suffix-Matching allein ist zu unscharf. + +Danach: python tools/import/merge_and_resolve.py + Upload-Ingest gegen Prod. Erwartung (verifizieren): "Sakura" 336ad972 und "Jamie" 4e4e507f verschwinden; H2-Wurf und Chart-Wurf 27ab49ae haben nach dem _premerged_ids-Remap (merge_and_resolve.py Z.3445-3448) identische Eltern + Datum und werden vom Litter-Dedup Stage 2 zu einem Wurf kollabiert; L2-Wurf hat dann Vater "Jamie von den kleinen Chaoten". + +OPTIONALE HAERTUNG (verhindert weitere Rueckfaelle, merge_and_resolve.py direkt nach dem mergeExternalRefs-Block ~Z.2782): pro angewandtem Paar pruefen, ob noch ein Record mit ImportSource "Wurfchronik" ohne DateOfBirth und gleichem normalisierten Call-Name wie der Keeper in all_processed_gerbils steht; wenn ja, Zeile in review-report.md/Konsole ausgeben ("Merge-Entscheidung evtl. unvollstaendig: — weiterer Wurfchronik-Stub "). Zusaetzlich beim stillen `continue` (Drop-Ref nicht gefunden) warnen. Systemische Kandidatenliste fuer eine eigene Aufraeum-Aufgabe: 67 DOB-lose Wurfchronik-Stubs, davon 14 mit Call-Name-Kollision zu einem Stammbaum-Tier (Raya, Gale von den Kleinen Chaoten, Jamie, Tai, Katsu, Willow, Qamikaze Queen, Eddard Stark, Xtra Bounty, SA, Joghurt, Maxi King, Fast Boy, Obelix). + +**Changelog-Entwurf für die Züchterin:** Sakura war tatsaechlich noch ein zweites Mal vorhanden: In der Wurfchronik ist sie auf zwei verschiedenen Seiten als Elterntier notiert, und beim letzten Zusammenfuehren wurde nur der eine der beiden Eintraege mit "Gwen gen. Sakura of little Angels" verschmolzen. Jetzt sind beide Eintraege zusammengefuehrt — dasselbe galt fuer Jamie, der ebenfalls doppelt geführt war. Damit haengen der H2-Wurf und der L2-Wurf vom 13.04. bzw. 20.05.2013 wieder bei den richtigen Eltern, und der doppelte Wurf vom 13.04.2013 ist zu einem Eintrag zusammengefasst. + +**Betroffen:** tools/import/conflict-decisions.json; tools/import/merge_and_resolve.py (mergeExternalRefs-Block Z.2743-2785, Litter-Remap Z.3445-3448); C:\Users\gulum\dev\Wurfchronik_Bilder (Wurfchronik Teil 1_page_0022.md / _page_0023.md) + +--- + +### „Unbekannt" (be299aaf, *21.06.2014) — namenlose Q3-Dublette + falscher Vater des Q4-Wurfs + +Tickets: `4f890829-c2ad-4709-8d45-399eb077c12f` · Layer **import-data** · Cluster `conflict-decisions-wurfchronik-page0009-platzhalter-eltern` · Aufwand S · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Zwei zusammenhängende Import-Defekte. (1) DUBLETTE: Das Tier be299aaf „Unbekannt" (m, *21.06.2014, ExternalRef „Wurfchronik Teil 1_page_0009.md-90e23e20-3051-4048-b4b1-e2bc00b21736", Notes „Color: Silberagouti. Note: Ni. ? bleibt.") ist ein namenloses Jungtier des Q3-Wurfs (7d5c5218, 21.06.2014, Jacky × Gale/Drake Jr, WS 6). Die Quelle C:/Users/gulum/dev/Wurfchronik_Bilder/„Wurfchronik Teil 1_page_0009.md" listet die 6 Q3-Jungtiere NUR per Farbe ohne Namen; „…page_0028.md" listet dieselben 6 MIT Namen (Pünktchen/Agouti, Quebec/dd-Anthrazit, Kai-Uwe/Blau + 3 namenlose AWP-Tote). Da Platzhalter-Namen im Dedup absichtlich nie gemergt werden (tools/import/merge_and_resolve.py ~Z.2975 „is_placeholder → keep all separate"), bleiben 6 Seite-0009-Dubletten stehen (be299aaf, 911516bb, 1230a554, 089d5d1b, 11bbdb9e, 8dcb9e1f) — genau das hat die Züchterin erkannt („Hast du auch Kai-Uwe oder Pünktchen?"). (2) FALSCHE VERPAARUNG: Der Wurf 751f3dcc „Q4-Wurf" (10.12.2014, Kinder Sweet + Vee rich) trägt in der md-Quelle Seite 0038 die Elternangabe „Eltern: [Unbekannt] + Nelly". Die Eltern-Namensauflösung (merge_and_resolve.py Z.3600-3611, _resolve_name) hat keinen Platzhalter-Guard und löste den „Namen" Unbekannt auf das namenlose Jungtier be299aaf auf → FatherId=be299aaf, MotherId=None. parent_age_plausible greift nicht (Abstand 5,7 Monate = formal plausibel). Scan über alle 965 Würfe: nur dieser eine Wurf betroffen. Echte Eltern belegt: tools/import/output/litters.json Zeile „sheet1-Q4-10.12.2014" (Wurfchronik-xlsx, sheet1 row 131) nennt sireName „Earl [Kleine Chaoten]" + damName „Nelly [Kleine Chaoten]" → Earl d676e74d (m, *22.04.2013), Nelly b3826f4b (w, *12.07.2013); tools/import/_rpro3.db bestätigt das Paar (Earl × Nelly → Wade, *14.01.2015). Warum die xlsx-Angabe nicht griff: (a) `_father_name` war mit „Unbekannt" schon belegt, die Anreicherung füllt nur Lücken (Z.3575-3578), und (b) die Kandidaten-Verengung Z.3568 vergleicht litters.json-`litterId` („Q4") gegen `LitterLetter` („Q") → kein Treffer, 2 gleich-datierte Kandidaten (Q4+R4) → `continue`. Kein Rückfall durch die 12 neuen Charts vom 2026-08-18 — der Fall stammt komplett aus den Wurfchronik-md-Quellen. + +**Fix:** + +Datenfix in tools/import/conflict-decisions.json (re-ingest-stabil, deterministisch), zwei Einträge: + +1) `suppressExternalRefs` — Dubletten-Tier „Unbekannt" entfernen (die Züchterin: „Bitte lösche Unbekannt!"). An das bestehende Array anhängen: + "Wurfchronik Teil 1_page_0009.md-90e23e20-3051-4048-b4b1-e2bc00b21736" +(be299aaf hat keine Fotos, keine Verträge, keine Kinder und keinen Geburtswurf; die einzige Referenz ist der Q4-Vaterlink. Ein page_0009-Suppress-Block existiert bereits — gleiche Musterstelle.) Optional den `_doc_suppress`-Text ergänzen: „… sowie das namenlose Q3-Jungtier von Seite 0009 (Ticket 4f890829), das als 'Unbekannt'-Vater des Q4-Wurfs aufgelöst wurde." + +2) `litterParents` — echte Eltern des Q4-Wurfs setzen (läuft Z.4098 VOR der Suppression Z.4423, daher kein Dangling-Link): +{ + "litterName": "Q4-Wurf", + "date": "10.12.2014", + "father": "Earl", + "fatherDob": "22.04.2013", + "mother": "Nelly", + "motherDob": "12.07.2013", + "source": "Ticket 4f890829: Wurfchronik-xlsx sheet1 row 131 (Earl [Kleine Chaoten] x Nelly [Kleine Chaoten]); rpro3 bestaetigt das Paar (Wade *14.01.2015). Ersetzt den faelschlich aufgeloesten Platzhalter-Vater 'Unbekannt'." +} + +Danach: `python tools/import/extract.py` (optional, unverändert) → `python tools/import/merge_and_resolve.py` → prüfen: (a) kein Gerbil mit Id be299aaf mehr im Payload, (b) Q4-Wurf 751f3dcc hat FatherId d676e74d / MotherId b3826f4b, (c) `suppressExternalRefs: 1 Gerbil(s) … entfernt` in der Konsole → dann Upload-Ingest gegen Prod (`POST /api/import/ingest-resolved/upload`). Der Upsert-Ingest löscht die nun fehlende importierte Zeile automatisch als „stale"; das Ticket behält seine lose gerbilId (FK-frei) und bleibt lesbar. + +OPTIONALE Härtung (separat verifizieren, nicht für dieses Ticket nötig): +- merge_and_resolve.py direkt vor der xlsx-Anreicherungs-Schleife (Z.3565): Platzhalter-Elternnamen leeren, damit sie nie auf ein Tier auflösen und die xlsx-Angabe greifen kann — + for l in resolved_litters: + for _k in ("_father_name", "_mother_name"): + _n = "".join(c for c in (l.get(_k) or "").lower() if c.isalnum()) + if _n in ("unbekannt", "unknown", "unbenannt", "nn", "keineangabe", "ka", "na"): + l[_k] = "" +- merge_and_resolve.py Z.3568: Verengung auf das Wurf-Namens-Präfix statt `LitterLetter` (z. B. `(l.get("Name") or "").split("-")[0].upper()`), damit gleich-datierte Würfe (Q4/R4) ihre xlsx-Eltern bekommen — würde u. a. auch R4-Wurf dbba6874 (Arrow × Kia) automatisch füllen; größerer Blast-Radius, eigener Verifikationslauf. +- Die 5 restlichen namenlosen page_0009-Q3-Dubletten (911516bb, 1230a554, 089d5d1b, 11bbdb9e, 8dcb9e1f) sind derselbe Fehler, wurden aber von der Züchterin nicht explizit benannt → separat anfragen, nicht mit durchlöschen. +Restkosmetik: Die Wurf-Notiz „Eltern: [Unbekannt] + Nelly" bleibt als Quelltext-Zitat stehen (kein Override-Mechanismus für Wurf-Notizen). + +**Changelog-Entwurf für die Züchterin:** Du hattest recht: das Tier „Unbekannt" (geboren 21.06.2014) war eine Dublette — es ist eines der Jungtiere aus dem Q3-Wurf vom 21.06.2014, das in deiner Wurfchronik auf einer Seite nur mit Farbe („Silberagouti, bleibt, Ni.?") und ohne Namen steht, während dieselben Jungtiere auf einer anderen Seite mit Namen aufgeführt sind — darunter genau Kai-Uwe und Pünktchen (und Quebec). Der Eintrag „Unbekannt" ist jetzt gelöscht. Er war außerdem versehentlich als Vater des Q4-Wurfs vom 10.12.2014 eingetragen, weil dort in der Quelle nur „Unbekannt + Nelly" stand und das Programm diesen Platzhalter für einen echten Tiernamen gehalten hat. Laut deiner Wurfchronik sind die Eltern dieses Wurfs Earl und Nelly — das ist jetzt so eingetragen, Sweet und Vee rich haben damit ihre richtigen Eltern. + +**Betroffen:** tools/import/conflict-decisions.json; tools/import/merge_and_resolve.py; Wurf Q4-Wurf 751f3dcc (10.12.2014, Sweet + Vee rich); Tier be299aaf 'Unbekannt'; Q3-Wurf 7d5c5218 (21.06.2014) + +--- + +### Merle (2023-06-18): Herkunft leer + noch als Zuchttier im Bestand (Abgabe 22.12.2024 fehlt) + +Tickets: `7037f5d8-e759-47f2-9f1c-778614f10f06` · Layer **import-data** · Cluster `conflict-decisions-resolutions-merge-and-resolve` · Aufwand M · Konfidenz medium · Aktion handover-to-implementation + +**Ursache:** Zwei Ursachen, beide in conflict-decisions.json / merge_and_resolve.py. (1) BESTAND: Merle (Prod-Id 9df4890f-4959-548f-aa96-c9ee8c759e03, dob 2023-06-18, extref stammbaum-merle-18062023) hat Status "Breeding", ReceiverContactId=null, GoHomeDate=null. Die Abgabe, die die Züchterin bereits in Ticket a8f11ac0 (2026-07-13) gemeldet hat ("abgegeben an Swen Pulinckx am 22.12.24, dort in Schneewittchen umbenannt"), wurde nie in conflict-decisions.json übernommen. Die Bestandsansicht (gerbil-manager-web/src/pages/GerbilsPage.tsx, Z.60 + Z.158) filtert genau auf isResident==true AND status=='Breeding' → Merle steht dort zu Recht drin, solange kein Abnehmer/Abgabedatum gesetzt ist. isResident=true ist regelkonform (eigener Wurf "Wurf von Smoke + Merle" 12.07.2024, ShowInChronicle=true) → nicht die Residenz, sondern der Status ist falsch. (2) HERKUNFT: Merle.OriginBreeder ist null, die Akte zeigt "Herkunft: —" (GerbilDetailPage.tsx Z.545-551). Regression durch den Chart-Import vom 2026-08-18: der Override-Block in tools/import/merge_and_resolve.py (Z.3198-3325) wendet pro Tier NUR EINE Decision an (first match: exakter externalRef → endswith → (name,dob) → (name,"")). Der addAnimals-Stub "decision-merle" ist mit dem neuen Chart-Datensatz "Stammbaum von Pukas Kids.xlsx" verschmolzen (daher auch die neue Id; die Ticket-gerbilId ce05fad7 liefert 404) → Merle matcht jetzt resolutions[150] (name+dob) und resolutions[136] (name-only, originBreeder "Clan of Black Forest") fällt still weg. Zusätzlich leakt der tote name-only-Eintrag über den (call-name,"")-Branch auf den Namensvetter "Merle of Samsimar" (43603fcc, dob 2012-09-01), der in Prod deshalb fälschlich originBreeder "Clan of Black Forest" trägt. (3) resolutions unterstützen kein goHomeDate → das Abgabedatum 22.12.2024 lässt sich derzeit gar nicht data-driven setzen. + +**Fix:** + +A) tools/import/conflict-decisions.json — resolutions[136] (der name-only "Merle"-Eintrag mit "externalRef": "decision-merle") KOMPLETT LÖSCHEN: er greift bei Merle nicht mehr und setzt stattdessen fälschlich die Herkunft von "Merle of Samsimar". + +B) tools/import/conflict-decisions.json — resolutions[150] durch diesen konsolidierten Eintrag ERSETZEN (eine Decision pro Tier, gekeyt auf name+dob UND aktuellen externalRef): +{ + "name": "Merle", + "dob": "18.06.2023", + "externalRef": "stammbaum-merle-18062023", + "isResident": true, + "originBreeder": "Zucht der Kleinen Chaoten", + "receiver": "Swen Pulinckx", + "goHomeDate": "22.12.2024", + "notes": "Tochter von Akane von den Kleinen Chaoten × Bonaparte von den Schlossmäusen, geboren bei Clan of Black Forest (Akane war dort nur im Zuchttier-Austausch). Herkunft laut Züchterin: Zucht der Kleinen Chaoten. Am 01.04.2024 zurück zu den Kleinen Chaoten (Gewicht 62,1 g), Zuchttier mit Smoke (Würfe 12.07.2024 und 19.08.2024). Am 22.12.2024 zusammen mit ihrer Tochter Speedy an Swen Pulinckx abgegeben und dort in „Schneewittchen“ umbenannt. Weitere Daten siehe Stammbaum Pukas Kids.", + "decision": "Ticket 7037f5d8: Herkunft = Zucht der Kleinen Chaoten (Züchterin-Angabe, ersetzt die frühere Angabe „Clan of Black Forest“). Merle bleibt Zuchttier (isResident=true, eigener Wurf mit Smoke in der Wurfchronik), ist aber seit 22.12.2024 abgegeben → Status GivenAway + Abnehmer + Abgabedatum, damit sie nicht mehr in der Bestandsansicht (isResident && status=Breeding) auftaucht. Konsolidiert die alte name-only-Decision, die nach dem Chart-Import auf „Merle of Samsimar“ geleakt hat.", + "source": "Ticket 7037f5d8 (Züchterin 2026-07-31) + Ticket a8f11ac0 + Ticket 00715df9 + Ticket 36a3fcde" +} +Hinweis: "genotype" ist nicht nötig — der Chart liefert schon "Aa c[chm]c[chm] D- ee[-] GG P- Spsp" (deckt sich mit ihrer Angabe), und der genotype-Override greift ohnehin nur bei leerem Feld. Der Kontakt "Swen Pulinckx" existiert noch nicht und wird von _resolve_contact_id_by_name automatisch als Receiver-Kontakt erzeugt. + +C) tools/import/merge_and_resolve.py — goHomeDate-Override ergänzen (bisher nicht unterstützt): + 1. in der has_ovr-Bedingung (~Z.3211-3215) ergänzen: ... or d.get("dateOfDeath") or d.get("goHomeDate") ... + 2. direkt NACH dem dateOfDeath-Block (~Z.3290, vor "if d.get(\"correctDob\")") einfügen: + # goHomeDate: Abgabedatum aus einem Züchterin-Ticket (nur setzen, wenn leer); + # zieht Status GivenAway nach, sofern das Tier nicht verstorben ist. + if d.get("goHomeDate") and not g.get("GoHomeDate"): + _gh = parse_date(d["goHomeDate"]) + if _gh: + g["GoHomeDate"] = _gh + if g.get("Status") != "Deceased": + g["Status"] = "GivenAway" + applied = True + (GoHomeDate wird von IngestResolvedService.cs Z.130/146 mit-upserted, "GivenAway" verhindert außerdem, dass die 6-Jahres-Sterbe-Heuristik greift.) + +D) Danach: python tools/import/merge_and_resolve.py, plausibilisieren (Merle 9df4890f: OriginBreeder "Zucht der Kleinen Chaoten", Status GivenAway, GoHomeDate 2024-12-22, ReceiverContactId gesetzt, IsResident true; "Merle of Samsimar" 43603fcc: OriginBreeder wieder null), python test_merge_resolve.py, dann Upload-Ingest gegen Prod. + +Optionale Nacharbeit (kein Blocker): die drei Merle-Tickets (00715df9, a8f11ac0, 7037f5d8) zeigen auf die tote gerbilId ce05fad7 → Links in der Ticketliste laufen ins Leere; Umhängen auf 9df4890f wäre ein reiner Feedback-Update (keine Importdaten). + +**Changelog-Entwurf für die Züchterin:** Merle ist jetzt richtig eingetragen: Als Herkunft steht „Zucht der Kleinen Chaoten“ in ihrer Akte, und ihre Abgabe ist erfasst — abgegeben am 22.12.2024 an Swen Pulinckx (dort „Schneewittchen“). Dadurch verschwindet sie aus der Bestandsliste der Zuchttiere und taucht nur noch unter den abgegebenen Tieren auf; ihr Wurf mit Smoke bleibt natürlich in ihrer Akte und in der Wurfchronik. Warum es beim letzten Mal nicht gehalten hat: durch die neu eingelesenen Stammbäume (u. a. „Pukas Kids“) hat Merle einen Datensatz mit Geburtsdatum bekommen, und meine alte Notiz zu ihr passte danach nicht mehr auf sie — sie landete versehentlich bei der viel älteren „Merle of Samsimar“, die deshalb eine falsche Herkunft trug. Beides ist jetzt zusammengeführt und dauerhaft an Namen + Geburtsdatum festgemacht. Offen ist noch ihr zweiter Wurf mit Smoke vom 19.08.2024 (mit Tochter Speedy) — dazu warte ich noch auf deine Antwort im älteren Ticket. + +**Betroffen:** tools/import/conflict-decisions.json; tools/import/merge_and_resolve.py; Prod-Daten: Merle 9df4890f-4959-548f-aa96-c9ee8c759e03; Prod-Daten: Merle of Samsimar 43603fcc-9384-5637-a0cb-48dfe36cea5a; gerbil-manager-web/src/pages/GerbilsPage.tsx (nur Analyse, keine Änderung) + +--- + +### JackJack (b6d8b3ef) + A-Wurf 18.02.2010 (29f4bae9): Saphir wird als Platin errechnet — C-Locus-Zygotie fehlt im Farbkatalog + +Tickets: `bde4ec70-d082-43ad-8343-ce82ed9eb39a, f89e95ad-de02-4d35-b2ec-7fa150146458` · Layer **genetics-engine** · Cluster `genetik-c-locus-zygotie-saphir-platin` · Aufwand M · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Katalog-Kollision in der Genetik-Engine: `gerbil-manager-web/src/genetics/catalog.ts` Z.67 (Platin), Z.81 (Saphir) und Z.86 (Platin-Hell) tragen ALLE die identischen Tokens {A:'a',C:'C',D:'D',E:'E',G:'G',P:'p'}. `locusToken()` reduziert den C-Locus auf das dominante Allel, d.h. CC, Cc[chm] und Cc[h] liefern alle den Token 'C' — also gewinnt beim "first match wins" immer die erste Zeile (Platin), Saphir und Platin-Hell sind unerreichbare Synonyme. Reproduziert lokal: `genotype.farbschlag_from_genotype_string('aa Cc[chm] DD E- G- pp Spsp')` == 'Platin' (UI hängt den Sp-Modifier an → "Platin Schecke"). Die Züchterin hat autoritativ geklärt (Ticket f89e95ad, Status Answered): "Saphir ist aa Cc[chm] D- E- G- pp und Platin ist aa CC D- E- G- pp. Es ist nicht dasselbe!" — der C-Locus muss also ZYGOTIE-genau matchen. Belege: Prod JackJack (b6d8b3ef) hat Genotyp 'aa Cc[chm] DD E- G- pp Spsp', gespeicherte ColorVariety ...000025 = Saphir (also schon korrekt) und Notiz "Saphir-Sp" — nur die errechnete Farbe lügt; GerbilOverride vorhanden (protectedFields name/spottingType/genotype/isDeaf, 2026-07-21), der Importer-Stand heißt noch "JackSack" mit Gencode 'aa CC DD EE GG pp Spsp rere'. Zweite, unabhängige Ursache für f89e95ad: die Mutter des A-Wurfs (Kuke, e4898ccc) hat in Prod weiterhin `genotype = null`, deshalb rechnet die WurfDetailPage überhaupt keine erwarteten Farbschläge; rpro3 belegt Kuke = Anthrazit-Schecke, 'aa CC DD Ee gg Pp Spsp'. Verschärfend (Ursache der Fehl-Gencodes im Import): merge_and_resolve.resolve_color_and_genotype schreibt bei einem Farb-LABEL "…Schecke" ohne Gencode den CanonicalGenotype der ColorVariety aus colorVarietySeed.backend.json — solange Saphirs Canonical 'aa CC …' lautet, bekommt ein "Saphir-Sp"-Tier den Platin-Gencode. + +**Fix:** + +CODE-FIX (Engine, kein Live-DB-Patch; nutzt die bereits vorhandene '/'-Paar-Token-Konvention von Siam/Zobel-Hell, daher KEINE Umsortierung des Katalogs → sortOrder und ColorVariety-GUIDs bleiben stabil): + +(1) `gerbil-manager-web/src/genetics/catalog.ts` — `matches()` (direkt nach `locusToken`) ersetzen durch: +```ts +/** + * GEN-6 (Tickets bde4ec70 / f89e95ad): ein Token darf ein EXAKTES Allelpaar + * "x/y" (ungeordnet) verlangen statt des exprimierten dominanten Allels. + * Nötig, weil sich Platin / Saphir / Platin-Hell NUR in der C-Zygotie + * unterscheiden (Züchterin, autoritativ: "Saphir ist aa Cc[chm] D- E- G- pp + * und Platin ist aa CC D- E- G- pp. Es ist nicht dasselbe!"): + * aa CC D- E- G- pp -> Platin + * aa C/c[chm] D- E- G- pp -> Saphir + * aa C/c[h] D- E- G- pp -> Platin-Hell (GEN-3g: "-Hell" == c[h]-Allel) + * representativeGenotype() versteht die "x/y"-Form bereits (GEN-3f). + */ +function pairMatches(g: Genotype, locus: LocusKey, token: string): boolean { + const [a, b] = resolvedPair(g, locus) + const [x, y] = token.split('/') as [string, string] + return (a === x && b === y) || (a === y && b === x) +} + +function matches(g: Genotype, entry: FarbschlagEntry): boolean { + return (Object.keys(entry.tokens) as LocusKey[]).every((locus) => { + const token = entry.tokens[locus]! + return token.includes('/') ? pairMatches(g, locus, token) : locusToken(g, locus) === token + }) +} +``` +und die drei Einträge (nur der C-Token ändert sich, Reihenfolge bleibt): +- Z.67: `{ name: 'Platin', english: 'Lilac', tokens: { A: 'a', C: 'C/C', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'platin.JPG' },` +- Z.81: `{ name: 'Saphir', tokens: { A: 'a', C: 'C/cchm', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'saphir.jpg' },` +- Z.86: `{ name: 'Platin-Hell', tokens: { A: 'a', C: 'C/ch', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'platin-hell.jpg' },` +(Platin-Hell ist die konsistente Anwendung der schon dokumentierten breeder-Regel GEN-3g und trifft aktuell 0 Tiere; wenn der Haupt-Loop maximal konservativ sein will, kann Schritt für Platin-Hell entfallen — dann bei Platin `C: 'C'` lassen und nur Saphir auf `'C/cchm'` setzen, Saphir muss dann aber VOR Platin stehen.) + +(2) `cd gerbil-manager-web && npm run gen:catalog` — regeneriert colorVarietySeed.generated.json (Saphir → `aa Cc[chm] DD EE GG pp spsp rere`) und colorVarietySeed.backend.json (`aa Ccchm DD EE GG pp spsp rere`). Ohne das ist `src/genetics/__tests__/catalog-drift.test.ts` rot. + +(3) `tools/import/genotype.py` — Mirror; `_matches` ersetzen: +```python +def _pair_matches(mapped, locus, token): + a, b = _resolve_allele_pair(locus, mapped.get(locus, ["?", "?"])) + x, y = token.split("/") + return (a == x and b == y) or (a == y and b == x) + + +def _matches(mapped, tokens): + return all( + _pair_matches(mapped, locus, tok) if "/" in tok else _locus_token(mapped, locus) == tok + for locus, tok in tokens.items() + ) +``` +und in `_BASE_COLORS`: `("Platin", {"A":"a","C":"C/C","D":"D","E":"E","G":"G","P":"p"})`, `("Saphir", {"A":"a","C":"C/cchm","D":"D","E":"E","G":"G","P":"p"})`, `("Platin-Hell", {"A":"a","C":"C/ch","D":"D","E":"E","G":"G","P":"p"})`. + +(4) `GerbilManagerWebAPI/ApplicationContext.cs` → `SeedColorVarieties`: Zeile `("Saphir", "aa CC DD EE GG pp spsp rere", 24)` → `("Saphir", "aa Ccchm DD EE GG pp spsp rere", 24)` und `("Platin-Hell", "aa CC DD EE GG pp spsp rere", 27)` → `("Platin-Hell", "aa Cch DD EE GG pp spsp rere", 27)`; Platin bleibt. Dann `dotnet ef migrations add ReseedColorVarietiesGen6Saphir --project GerbilManagerWebAPI` (erzeugt UpdateData für Id `00000000-0000-0000-0000-000000000025` und `...000000000028`, Muster wie ReseedColorVarietiesAR5). Kein C#-Genetik-Mirror vorhanden (Rpro3ImportService verknüpft nur über Farbnamen) → nichts weiter im Backend. + +(5) Regressionstests PFLICHT (Genetik = korrektheitskritisch): +- `src/genetics/__tests__/genetics.test.ts`: `name('aa CC DD EE GG pp spsp rere') === 'Platin'` (Z.164/236 bleiben grün), `name('aa Cc[chm] DD EE GG pp spsp rere') === 'Saphir'`, `name('aa Cc[chm] DD E- G- pp Spsp') === 'Saphir Schecke'` (JackJack), `name('aa Cc[h] DD EE GG pp spsp rere') === 'Platin-Hell'`, plus breed(Blacky `aa Cc[chm] DD EE GG Pp spsp` × Kuke `aa CC DD Ee gg Pp Spsp`) enthält 'Saphir' UND 'Saphir Schecke'. +- `tools/import/test_genotype.py`: `farbschlag_from_genotype_string('aa Cc[chm] DD EE GG pp') == 'Saphir'`, `('aa CC DD EE GG pp') == 'Platin'`, `('aa Cc[h] DD EE GG pp') == 'Platin-Hell'`. + +DATEN-FIX (nur für Ticket f89e95ad, damit der Wurf überhaupt Erwartungen rechnet) — die BESTEHENDE Resolution in `tools/import/conflict-decisions.json` (`externalRef: "decision-kruke"`) um zwei Felder ergänzen; sie sagt selbst, dass Farbe/Genotyp auf diese Klärung wartet: +```json +{ + "name": "Kruke", + "isResident": true, + "decision": "'Kruke' war ein Wurfchronik-Tippfehler; korrekt 'Kuke' (rpro3). Bestandstier. Todesdatum 06.04.2011 (Ausbruch-Unfall, rpro3). Farbe/Genotyp jetzt gesetzt: rpro3 'Kuke' (M.Knoss) = Anthrazit-Schecke, Gencode aa CC DD Ee gg Pp Spsp; die Zuechterin hat Saphir/Platin geklaert (Ticket f89e95ad, 2026-08). KEIN Merge mit dem Ahnen-Record 'Kuke von Marion Knoss' — der Merge nullte die Mutter-Verknuepfung aller Blacky-x-Kuke-Wuerfe.", + "source": "KI-Triage 2026-07-12 — Ticket 34220307-23bc-420c-80f8-d273615caffd + b7016e5f-9985-4f17-a3f6-2048289958aa + Tickets e0a0c304/a50116bf; Gencode-Nachtrag KI-Triage 2026-08-19 (Ticket f89e95ad, rpro3 _rpro3.db)", + "externalRef": "decision-kruke", + "dateOfDeath": "06.04.2011", + "renameTo": "Kuke", + "genotype": "aa CC DD Ee gg Pp Spsp", + "farbschlag": "Anthrazit" +} +``` +Hinweise: `merge_and_resolve.py` Z.3302/3305 setzen `genotype`/`farbschlag` nur, wenn das Feld leer ist → re-ingest-stabil (Kuke hat beides null). `farbschlag` matcht EXAKT einen Katalognamen via `variety_map`, deshalb "Anthrazit" ohne "-Schecke" — die Scheckung steckt im Sp-Locus des Gencodes. Danach `python tools/import/extract.py` + `extract_contracts.py` + `merge_and_resolve.py`, dann Upload-Ingest gegen Prod (`POST /api/import/ingest-resolved/upload`). Der Engine-Fix selbst braucht KEINEN Re-Ingest (Anzeige wird client-seitig gerechnet), aber ein Re-Ingest nach (2) korrigiert zusätzlich die 5 heute als Platin geführten C-het-Tiere in der gespeicherten ColorVariety. + +Verifikation vor "fertig": `npx tsc --noEmit`, `npx vitest run`, `npx eslint src/genetics`, `python tools/import/test_genotype.py`, `dotnet build && dotnet test`; Stichprobe: JackJack zeigt "Saphir Schecke", A-Wurf listet Saphir + Saphir Schecke. + +**Changelog-Entwurf für die Züchterin:** Saphir und Platin sind jetzt zwei verschiedene Farben — genau wie du gesagt hast. Bisher hat das Programm beide gleich behandelt und deshalb bei JackJack "Platin Schecke" errechnet, obwohl er mit Cc[chm] eine Saphir-Schecke ist. Ab jetzt gilt: mit zwei vollen C ist es Platin, mit einem c[chm] daneben ist es Saphir (und mit einem c[h] daneben Platin-Hell). JackJack wird deshalb wieder als Saphir Schecke angezeigt, und bei fünf weiteren Tieren (Ina, Tommy, Stich, Sansa, Fenris) steht nun ebenfalls Saphir statt Platin. Beim A-Wurf vom 18.02.2010 fehlte außerdem der Gencode von Kuke — ich habe ihn aus deinen RennerPro-Unterlagen ergänzt (Anthrazit-Schecke, aa CC DD Ee gg Pp Spsp). Damit tauchen bei den erwarteten Farbschlägen des Wurfs jetzt auch Saphir und Saphir Schecke auf. + +**Betroffen:** gerbil-manager-web/src/genetics/catalog.ts; gerbil-manager-web/src/genetics/colorVarietySeed.generated.json; gerbil-manager-web/src/genetics/colorVarietySeed.backend.json; gerbil-manager-web/src/genetics/__tests__/genetics.test.ts; gerbil-manager-web/src/genetics/__tests__/catalog-drift.test.ts; tools/import/genotype.py; tools/import/test_genotype.py; tools/import/conflict-decisions.json; GerbilManagerWebAPI/ApplicationContext.cs; GerbilManagerWebAPI/Migrations (neu: ReseedColorVarietiesGen6Saphir) + +--- + +### Kuke (prod e4898ccc): fehlender Zuchtname + alter Name „Kruke" in 5 Wurf-Notizen + +Tickets: `24006ccd-c07c-4617-a016-cfa7587ac2aa, ea803c3d-33f5-4d3b-b05a-fb2bca8bb893` · Layer **import-logic** · Cluster `conflict-decisions+merge_and_resolve-rename` · Aufwand M · Konfidenz high · Aktion handover-to-implementation + +**Ursache:** Zwei getrennte Ursachen, beide im Importer. + +(1) Ticket ea803c3d („überall Kuke"): `renameTo` GREIFT — prod-Name ist „Kuke". Der alte Name steht nur noch in den aus der Wurfchronik übernommenen Wurf-Notizen: B-Wurf 2010 (f0b1fd6f) „Blacky + Kruke v. 17.04.2010; WS=2", D-Wurf 2010 (1eb3e1fb), E-Wurf 2010 (53b55c8c), F-Wurf 2010 (10ddf880), I-Wurf (f4b2b9be) „Eltern: Blacky + Kruke". Grund: `renameTo` (merge_and_resolve.py ~Z.3272-3282) schreibt nur `g["Name"]`, es gibt keine Propagation in Freitext-Notizen. Prod-weiter Scan (2451 Gerbils, 966 Litters, 886 Contacts) + resolved_import.json: kein weiteres sichtbares „Kruke"; intern bleiben nur ExternalRef „decision-kruke" und NameSearch „kruke" (NameSearch wird von keinem Endpoint abgefragt → irrelevant). + +(2) Ticket 24006ccd („Zuchtname fehlt"): Das Tier existiert ZWEIMAL. (a) resident e4898ccc = addAnimals-Stub „Kruke" → renameTo „Kuke", ohne OriginContact, Genotype/ColorVariety null; (b) Chart-Ahnenrecord 6d9f9d66 „Kuke von Marion Knoss" (ExternalRef stammbaum-kukemarionknoss-18092009, gleiches DOB 18.09.2009, gleicher Todestag 06.04.2011, OriginContact 84628989 Marion Knoss, Genotype „aa CC DD Ee Uwuw[d] Pp Spsp" = aa CC DD Ee Gg Pp Spsp, ColorVariety …0007 Schwarz). Der Zuchtname steht also nur am Zwilling, und die Frontend-Regel (gerbil-manager-web/src/format/gerbilName.ts: Suffix nur für eigengezüchtete Tiere) hängt fremden Tieren nichts an → der Zuchtname muss Teil des Namens sein. Ihr eigener Chart-Name lautet exakt „Kuke von Marion Knoss" (tools/import/output/animals.json; Charts Danako/Hana/Jin/Unit/Uriana/Wildfire), rpro3-Herkunft „M.Knoss GG", Eltern Marc x Sara. + +KEIN Rückfall durch die 12 neuen Charts vom 2026-08-18: der Zwilling existierte schon (die neuen Charts haben nur weitere sourceFiles ergänzt). Der Merge war 2026-07-12 bewusst ausgelassen worden („der Merge nullte die Mutter-Verknüpfung aller Blacky-x-Kuke-Würfe", conflict-decisions.json Z.986) — mit keep=decision-kruke ist genau dieser Fehlermodus heute abgedeckt. + +**Fix:** + +A) DATEN — tools/import/conflict-decisions.json, `resolutions`[114] (der Eintrag mit "name": "Kruke", Z.983-991) komplett ersetzen durch: + + { + "name": "Kruke", + "isResident": true, + "decision": "'Kruke' war ein Wurfchronik-Tippfehler; korrekt 'Kuke von Marion Knoss' (so heisst sie in ihren eigenen Stammbaeumen Danako/Hana/Jin/Unit/Uriana/Wildfire; rpro3-Herkunft 'M.Knoss GG', Eltern Marc x Sara). Bestandstier, Todesdatum 06.04.2011 (Ausbruch-Unfall, rpro3). Der Chart-Ahnenrecord wird jetzt HIER hineingemergt (keep = decision-kruke): der Keeper behaelt bis zum Parent-Resolver den Quellnamen 'Kruke', renameTo setzt danach _pre_rename_name (Namensindex) und die Chart-Wuerfe des gedroppten Zwillings werden ueber _premerged_ids umgebogen -> die Mutter-Verknuepfung der Blacky-x-Kuke-Wuerfe bleibt erhalten (das war 2026-07-12 der Grund, den Merge auszulassen).", + "source": "KI-Triage 2026-08-19 - Tickets 24006ccd (Zuchtname) + ea803c3d (ueberall 'Kuke'); zuvor e0a0c304 / a50116bf / f89e95ad", + "externalRef": "decision-kruke", + "dateOfDeath": "06.04.2011", + "originBreeder": "Marion Knoss", + "renameTo": "Kuke von Marion Knoss", + "mergeExternalRefs": [["decision-kruke", "stammbaum-kukemarionknoss-18092009"]] + }, + +(mergeExternalRefs wird aus `resolutions` gelesen — merge_and_resolve.py Z.2760; Match per ExternalRef-Suffix (endswith), keep = pair[0].) + +B) CODE — tools/import/merge_and_resolve.py: finaler Notiz-Sweep direkt VOR `# 6. Save final output JSON payload` (aktuell ~Z.4462, hinter der Herkunft-Normalisierung) einfügen: + + # ── Umbenennungs-Sweep in Freitext-Notizen (Tickets ea803c3d/24006ccd, Kruke→Kuke) ── + # `renameTo` korrigiert nur das Namensfeld; die aus der Wurfchronik übernommenen + # Wurf-Notizen ("Blacky + Kruke v. 21.09.2010; WS=4") tragen den alten Namen weiter, + # und die Züchterin sieht ihn dort. Finaler Anzeige-Sweep NACH aller Logik (Notes + # werden vorher fürs Dedup-Scoring gelesen): Quell-Name → Rufname des neuen Namens. + _rename_pairs = [] + for g in resolved_gerbils: + _old = (g.get("_pre_rename_name") or "").strip() + _new = (g.get("Name") or "").strip() + if _old and _new and _old != _new: + _new_call = get_call_name(_new) or _new + if normalize_name(_old) != normalize_name(_new_call): + _rename_pairs.append((_old, _new_call)) + _notes_fixed = 0 + for _old, _new_call in _rename_pairs: + _rx = re.compile(r"\b" + re.escape(_old) + r"\b") + for _rec in list(resolved_litters) + list(resolved_gerbils): + _val = _rec.get("Notes") + if _val and _rx.search(_val): + _rec["Notes"] = _rx.sub(_new_call, _val) + _notes_fixed += 1 + if _notes_fixed: + print(f"renameTo-Notiz-Sweep: {_notes_fixed} Notiz(en) auf den korrigierten Namen gesetzt") + +Erwartetes Ergebnis: „Blacky + Kuke v. 21.09.2010; WS=4" usw. (Rufname, nicht der volle Zuchtname — Notizen bleiben lesbar). + +C) TEST — tools/import/test_merge_resolve.py: Regressionstest, dass (i) ein Gerbil mit `_pre_rename_name` den alten Namen nicht mehr in Litter.Notes/Gerbil.Notes stehen lässt, (ii) wortgenau ersetzt wird (kein Treffer in „Krukelinde"/Teilwörtern), (iii) der Rufname (nicht das Zucht-Suffix) eingesetzt wird. + +D) NACH dem Ingest verifizieren (prod, read-only): GET /api/gerbils/e4898ccc-1896-5258-b1a9-4c89d30cf428 → name „Kuke von Marion Knoss", originContactId 84628989-4725-54c5-a113-4e5ad03dc1e2; GET /api/gerbils?filter=name=*Kuke → nur noch EIN Treffer; die 5 Würfe (f0b1fd6f, 1eb3e1fb, 53b55c8c, 10ddf880, f4b2b9be) haben motherId=e4898ccc UND Notizen ohne „Kruke". + +E) NEBENWIRKUNG (bitte an Ticket f89e95ad weiterreichen, NICHT hier entscheiden): Der Merge füllt Genotype („aa CC DD Ee Uwuw[d] Pp Spsp"; Uw/uw[d] ist laut genotype.py/genotype.ts ein Alias von G/g → aa CC DD Ee Gg Pp Spsp) und ColorVarietyId (…0007 „Schwarz") aus dem Chart. Positiv: der Datenblocker von f89e95ad (Mutter-Genotyp null → keine erwarteten Farbschläge im A-Wurf) fällt weg. Aber rpro3 sagt „Anthrazit-Schecke / aa CC DD Ee gg Pp Spsp" (G-Locus gg statt Gg), und „Schwarz" passt nicht zu Ee/Spsp. ACHTUNG Ordering: `genotype`/`farbschlag` aus conflict-decisions greifen nur bei LEEREM Feld (merge_and_resolve.py ~Z.3302-3308) — nach dem Merge also No-Op. Wenn f89e95ad einen Wert festnageln soll, muss dieser Override gewinnen dürfen (bzw. der Merge-Gap-Fill die per Decision gesetzten Felder überspringen). + +**Changelog-Entwurf für die Züchterin:** Kuke heißt jetzt überall richtig — und mit vollem Namen: „Kuke von Marion Knoss", genau wie in deinen Stammbäumen. Zwei Dinge waren schuld: (1) Kuke war noch zweimal angelegt — einmal als dein Zuchttier (ohne Zuchtnamen) und einmal als Ahnin mit dem vollen Namen aus den Stammbäumen. Die beiden sind jetzt zu einem Tier zusammengeführt; dabei bleiben ihre Würfe mit Blacky und ihre Verknüpfungen erhalten, und ihre Herkunft (Marion Knoss) hängt jetzt direkt am Tier. (2) In fünf Wurf-Notizen stand noch der alte Schreibfehler „Kruke" (B-, D-, E-, F- und I-Wurf 2010) — dort steht jetzt „Kuke". Damit das nie wieder passiert, übernimmt der Import eine Namenskorrektur ab sofort automatisch auch in alle Notizen. Ihre Farbe und ihr Erbbild klären wir noch im separaten Punkt zum A-Wurf (Anthrazit-Schecke bzw. Saphir/Platin). + +**Betroffen:** tools/import/conflict-decisions.json (resolutions[114] 'Kruke'); tools/import/merge_and_resolve.py (Notiz-Sweep vor dem Payload-Dump; mergeExternalRefs); tools/import/test_merge_resolve.py (Regressionstest); Prod-Daten: Gerbil e4898ccc + Dublette 6d9f9d66, Würfe f0b1fd6f/1eb3e1fb/53b55c8c/10ddf880/f4b2b9be; Folgeticket f89e95ad (Genotyp/Farbschlag der Mutter) + +---