fix(tickets): 16 Tickets der Züchterin — Genetik, Importer, Daten, Akte
All checks were successful
CI / Frontend Tests (Node/Vite) (push) Successful in 9m38s
CI / Backend Tests (.NET) (push) Successful in 1m14s
CI / Docker Build & Push (push) Successful in 1m33s
CI / Deploy auf TrueNAS (Custom App) (push) Successful in 8s

Genetik-Engine (bde4ec70, f89e95ad, 2322c2a8):
- Saphir/Platin/Platin-Hell unterscheiden sich NUR in der C-Zygotie und waren im
  Katalog identisch (locusToken reduziert auf das dominante Allel) → Saphir war
  unerreichbar. Token darf jetzt ein exaktes, ungeordnetes Allelpaar "x/y"
  verlangen: Platin C/C, Saphir C/cchm, Platin-Hell C/ch. TS- und Python-Mirror
  identisch, Seeds regeneriert, Migration ReseedColorVarietiesGen6Saphir.
- c[hm] wird als Alias auf c[chm] normalisiert (Tippfehler in zwei Charts) —
  vorher war Jays Gencode unparsebar und ergab "Zobel-Hell".
- 6 Tiere wechseln den errechneten Farbschlag (5x Platin→Saphir, Jay→Zobel).

Importer-Logik:
- renameTo-Notiz-Sweep: der alte Name blieb in Wurf-Notizen stehen ("Blacky +
  Kruke") — jetzt wortgenau ersetzt, mit vier Guards gegen Kollateralschaden.
- Neuer Resolution-Schlüssel goHomeDate (autoritativ, zieht GivenAway nach) und
  spottingType; Gencode-Overrides gewinnen jetzt bei präzisem Match (vorher
  wurden Entscheidungen stumm verschluckt, z. B. Eliza und Chris).
- parse_date verwirft implausible Jahre (JackJack hatte 1310-05-13).
- extract.py erkennt Stammbaum-Blöcke auch ohne Stern vor dem Geburtsdatum
  (Bijou bekam dadurch den Nachbar-Ast als Mutter).
- Backend-Ingest überträgt SpottingType (nur bei Payload-Wert).

Daten (conflict-decisions.json, re-ingest-stabil): Kuke-Merge + Zuchtname,
Jamie- und Sakura-Dubletten, Phantom-Tier "Unbekannt", Eltern von Kathlin,
Fast Boy/Ziwa und dem Q4-/TS-Wurf, Eliza-Gencode, Merle-Abgabe, JackJack.

Frontend (24522f5f): Abgabedatum steht in der Tier-Akte jetzt direkt unter dem
Abnehmer und hängt nicht mehr am Status.

Tests: +25 Checks test_merge_resolve, +7 test_genotype, +8 genetics.test.ts,
neuer Extract- und Ingest-Test, e2e GOHOME-ROW. Alles grün.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-19 23:31:48 +02:00
parent bf074696b4
commit 5a6d1c2541
22 changed files with 2832 additions and 182 deletions

View File

@@ -222,6 +222,39 @@ def parse_detail(text):
geno)
# Sternloses Geburtsdatum: "22.08.2019/+09.04.2024" statt "*22.08.2019/+09.04.2024".
# Erlaubt ist optional ein angehaengtes Todesdatum nach "/+" (auch leer, "30.01.2020/+").
# Kein Whitespace innerhalb des Datums, damit die DOB-Regex nach dem Praefixen sicher greift.
BARE_DOB = re.compile(r"^\s*\d{1,2}\.\d{1,2}\.\d{4}(\s*/\s*\+.*)?$")
def _normalise_starless_dobs(cells):
"""Setzt den fehlenden Stern vor sternlose Geburtsdaten in echten Bloecken.
WARUM (Ticket 65266679 Bijou): Einige Charts vergessen den Stern vor dem
Geburtsdatum (Louis of Black Forest in "Alberto Kids"/"Pukas Kids", Jiminy of
Black Forest in "Stella Kids"). Ohne Stern erkennt die Blockheuristik das Tier
gar nicht -> das Tier fehlt im Extrakt und `_reconstruct_parents` greift in den
Nachbar-Ast (Bijou bekam Arya Stark als Mutter statt Louis x Nani).
Damit nicht jede beliebige Datumszelle (Notizen, Abgabedaten) zum Tier wird,
normalisieren wir nur MIT Block-Kontext: ein Genotyp <= 3 Zeilen darunter UND
eine Namenszelle <= 3 Zeilen darueber -- genau die Nachbarschaft, die die
Blockerkennung anschliessend auch auswertet.
"""
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()
def extract_stammbaum(path):
"""Return list of animal dicts for one chart file."""
fname = os.path.basename(path)
@@ -229,6 +262,7 @@ def extract_stammbaum(path):
ss = xu.shared_strings(z)
sheets = xu.sheet_paths(z)
cells = xu.read_cells(z, sheets[0], ss)
_normalise_starless_dobs(cells)
fillsex = xu.cell_fill_sex(z, sheets[0]) # box colour -> sex (blue=male, white=female)
has_col2 = any(c == 2 for (c, r) in cells)