fix(import): Farbschlag aus Genotyp ableiten + Mamta-Eltern — Ticket-Triage

genotype.py: Python-Port von genotypeToFarbschlag (0 Abw. über 3402 Genotypen).
resolve_color_and_genotype: bei vorhandenem Genotyp gewinnt der berechnete Farbschlag
(Goldfuchs≠Gold, Dilute Agouti/Anthrazit, Blaufuchs statt -schimmel bei (schimmel),
spsp statt Schecke). Mamta Mini: Ee + Eltern Geely×Gaida am Wurf. Regressionstests je Fall.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 10:08:13 +02:00
parent 26977d73ff
commit 6b264a9b71
6 changed files with 532 additions and 31 deletions

View File

@@ -432,6 +432,61 @@ check("contracts: animal-less record has empty Animals list",
_sale3 and _sale3[0]["Animals"] == [])
# ── resolve_color_and_genotype + clean_color_name (genetics-farbschlag cluster) ──
# A tiny synthetic variety_map (name->id) with the keys these cases need.
_VM = {
"gold": "ID-gold", "goldfuchs": "ID-goldfuchs", "goldfuchsschimmel": "ID-gfs",
"agouti": "ID-agouti", "dilute agouti": "ID-dagouti",
"anthrazit": "ID-anthrazit", "dilute anthrazit": "ID-danthrazit",
"blaufuchs": "ID-blaufuchs", "blaufuchsschimmel": "ID-bfs",
"kohlfuchsschimmel": "ID-kfs", "marder": "ID-marder", "schwarz": "ID-schwarz",
"orangeschimmel": "ID-orange",
}
_VG = {}
def _rc(color, geno):
return m.resolve_color_and_genotype(color, geno, _VM, _VG)[0]
# Ticket 3f5942a2 — specificity: „Goldfuchs"-label must NOT collapse to „Gold".
check("3f5942a2 label: 'Goldfuchs' -> goldfuchs (not gold)",
m._match_color_label("goldfuchs", _VM) == "ID-goldfuchs")
# Genotype wins: ee fox genotype overrides a stale „Gold" label.
check("3f5942a2 genotype wins: ee -> Goldfuchs over 'Gold' label",
_rc("Gold", "AA CC DD ee GG pp spsp") == "ID-goldfuchs")
# Ticket 998087e2 — dd ignored by label: genotype gives Dilute Agouti.
check("998087e2: dd genotype -> Dilute Agouti over 'Agouti' label",
_rc("Agouti", "AA CC dd EE GG PP spsp") == "ID-dagouti")
# Ticket 06217eb3 — Dilute Anthrazit.
check("06217eb3: dd genotype -> Dilute Anthrazit over 'Anthrazit'",
_rc("Anthrazit", "aa CC dd Ee gg P- spsp") == "ID-danthrazit")
# Ticket 1aac054f — Kohlfuchsschimmel over a stale 'Gold' label.
check("1aac054f: ee[f] genotype -> Kohlfuchsschimmel over 'Gold'",
_rc("Gold", "aa Cc[chm] D- ee[f] Gg Pp Spsp") == "ID-kfs")
# Ticket e22764aa — „Blaufuchs(schimmel)" parenthetical is NOT definitive; the
# cleaned label is „blaufuchs" and the ee[-] genotype confirms Blaufuchs.
_cn, _sc = m.clean_color_name("Blaufuchs(schimmel)")
check("e22764aa: '(schimmel)' stripped, not promoted -> 'blaufuchs'", _cn == "blaufuchs")
check("e22764aa: ee[-] genotype -> Blaufuchs (not Blaufuchsschimmel)",
_rc("Blaufuchs(schimmel)", "aa C- D- ee[-] gg P- spsp") == "ID-blaufuchs")
# Ticket e09d6f22 — a Schecke-looking LABEL must not flip an explicit source spsp
# to Spsp (the source genotype is authoritative for the Sp-locus).
_, _g_spsp = m.resolve_color_and_genotype("Kohlfuchsschimmel, hell",
"aa Cc[chm] D- ee[f] Gg Pp spsp", _VM, _VG)
check("e09d6f22: explicit spsp kept (label-Schecke does not force Spsp)",
"Spsp" not in _g_spsp and "spsp" in _g_spsp)
# VORSICHTIG guard: a COMPACT-notation genotype (cchmcchm/efef) the parser can't
# read must fall back to the text label, NOT mis-recolour (e.g. Marder->Schwarz).
check("guard: compact 'cchmcchm' unparsable -> keep label 'Marder'",
_rc("Marder", "aa cchmcchm DD EE GG PP spsp rere") == "ID-marder")
check("guard: compact 'efef' unparsable -> keep label 'Orangeschimmel'",
_rc("Orangeschimmel", "AA CC DD efef GG PP spsp rere") == "ID-orange")
# A genuinely Schecke label with no Sp in the genotype still appends Spsp.
_, _g_add = m.resolve_color_and_genotype("Agouti Schecke", "AA CC DD EE GG PP", _VM, _VG)
check("schecke label + no Sp token -> appends Spsp", "Spsp" in _g_add)
# ── Integration: assert the resolved_import.json output reflects the ticket fixes ──
# (Only when the pipeline has already been run; tolerant if the file is absent.)
import os as _os, json as _json
@@ -534,6 +589,59 @@ if _os.path.exists(_resolved):
and "unbekannt" in (g.get("ExternalRef") or "").lower()]
check("Duplicate-merge: nameless buck *15.02.2024 deduped to one record",
len(_bucks) == 1)
# ── genetics-farbschlag cluster: the STORED colorVarietyId is now genotype-
# correct for the ticket animals. Build the id→name map from the authoritative
# ApplicationContext.cs catalog (same source the pipeline uses for the ids).
import re as _re
_app = _os.path.abspath(_os.path.join(_os.path.dirname(__file__),
"../../GerbilManagerWebAPI/ApplicationContext.cs"))
_idname = {}
if _os.path.exists(_app):
_cm = _re.search(r"catalog\s*=\s*\{(.*?)\};", open(_app, encoding="utf-8").read(), _re.DOTALL)
if _cm:
for _i, (_n, _g, _so) in enumerate(_re.findall(
r'\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*(\d+)\s*\)', _cm.group(1))):
_idname[f"00000000-0000-0000-0000-{_i + 1:012d}"] = _n
def _by_ref(ref):
return next((g for g in _d["gerbils"] if g.get("ExternalRef") == ref), None)
def _cv_name(g):
return _idname.get(g.get("ColorVarietyId")) if g else None
if _idname:
# Ticket 1aac054f — namenloses Weibchen *13.08.2025 -> Kohlfuchsschimmel.
_t1 = _by_ref("stammbaum-unbekannt-13082025-2")
check("1aac054f: nameless *13.08.2025 stored as Kohlfuchsschimmel",
_cv_name(_t1) == "Kohlfuchsschimmel")
# Ticket e09d6f22 — same litter, *-3: spsp (NOT Schecke) + Kohlfuchsschimmel.
_t2 = _by_ref("stammbaum-unbekannt-13082025-3")
check("e09d6f22: Sp-locus is spsp (no Schecke)",
_t2 is not None and "Spsp" not in (_t2.get("Genotype") or "")
and "spsp" in (_t2.get("Genotype") or ""))
check("e09d6f22: stored as Kohlfuchsschimmel", _cv_name(_t2) == "Kohlfuchsschimmel")
# Ticket 06217eb3 — Dilute Anthrazit (dd).
_t3 = _by_ref("stammbaum-unbekannt-27052025")
check("06217eb3: nameless dd-Weibchen stored as Dilute Anthrazit",
_cv_name(_t3) == "Dilute Anthrazit")
# Ticket e22764aa — Blaufuchs (NOT Blaufuchsschimmel).
_t4 = _by_ref("stammbaum-unbekannt-16012026")
check("e22764aa: '(schimmel)' animal stored as Blaufuchs",
_cv_name(_t4) == "Blaufuchs")
# Ticket 3f5942a2 — named fox animals are Goldfuchs (ee), not Gold (EE).
_banjo = _find("Banjo of Fiomi")
check("3f5942a2: Banjo of Fiomi stored as Goldfuchs",
_cv_name(_banjo) == "Goldfuchs")
# ── Mamta Mini (cc9ea3fe / 1a508c04): Ee[-] resolved to Ee + parents linked. ──
_mamta = _find("Mamta Mini")
check("Mamta Mini: E-locus resolved to Ee (no unknown [-])",
_mamta is not None and "Ee[-]" not in (_mamta.get("Genotype") or "")
and "Ee" in (_mamta.get("Genotype") or ""))
_mf, _mm = _parents(_mamta)
check("Mamta Mini: father Geely, mother Gaida linked at the litter",
(_mf or "").startswith("Geely") and (_mm or "").startswith("Gaida"))
else:
print("note: output/resolved_import.json not present — skipped integration assertions")