fix(genetics): Genotyp-/Farbschlag-Engine-Bugs aus Ticket-Triage
- uw/Uwuw[d] (dichtes Underwhite) wird vor dem uw→G-Locus-Alias korrekt geparst (Vance: nicht mehr „Unbekannter Farbschlag", zeigt G-Locus statt international uw). [#34,#32] - ee[-] = sichtbarer Fuchs (rezessiv homozygot) → korrekt als Fuchs; bare „e-" (rezessiv + unbekannt) ist genetisch unmöglich → genotypeInvalid statt still falsch. [#42] - aa-Colourpoint-Zweig berücksichtigt jetzt D (Dilute) und E (Fuchs), statt fix Zobel/Marder/Siam zu erzwingen. [#3] - Wurf-Farbprognose: unbekanntes Allel (?) wird nicht mehr über die volle Dominanz expandiert → keine unmöglichen Dilute-/Schimmel-/„Unbekannt"-Nachkommen. [#37,#39,#40,#41,#38] Tests: test_genotype.py erweitert; genetics.test.ts +5 Blöcke (135 vitest grün, tsc/eslint sauber). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -126,7 +126,15 @@ def parse(raw):
|
||||
for locus in LOCI:
|
||||
pat = _LOCUS_TOKEN.get(locus)
|
||||
if pat and pat.match(t):
|
||||
mapped.setdefault(locus, _alleles_for(locus, t)) # first occurrence wins
|
||||
alleles = _alleles_for(locus, t)
|
||||
# #42 (E-locus e-dash): Fuchs (e) is RECESSIVE — a visible fox MUST be
|
||||
# homozygous "ee" (written "ee[-]", which already parses to [e, e]).
|
||||
# A lone recessive "e" with an unknown partner ("e-"/"e[-]" -> [e, ?])
|
||||
# is genetically impossible; mirror the frontend by REJECTING it
|
||||
# (leave it unmapped/invalid) instead of mapping a wrong colour.
|
||||
if locus == "E" and alleles in (["e", "?"], ["?", "e"]):
|
||||
break # invalid -> falls through to unmappedTokens below
|
||||
mapped.setdefault(locus, alleles) # first occurrence wins
|
||||
matched = True
|
||||
break
|
||||
if matched:
|
||||
|
||||
@@ -61,6 +61,27 @@ check("DP -> tag", g.parse("[DP]")["tags"] == ["DP"])
|
||||
check("tag not in genotype loci", g.parse("WFNZ")["mapped8locus"] == {})
|
||||
check("tag not in unmapped", g.parse("aa WFNZ")["unmappedTokens"] == [])
|
||||
|
||||
# --- #32/#34: dense-underwhite [d] annotation stripped, maps to G locus (never uw) ---
|
||||
r = g.parse("Uwuw[d]")
|
||||
check("Uwuw[d] -> Gg (dense underwhite, mixed case)", r["mapped8locus"].get("G") == ["G", "g"])
|
||||
check("Uwuw[d] leaves nothing unmapped (no stray uw token)", r["unmappedTokens"] == [])
|
||||
r = g.parse("aa Cc[chm] D- ee Uwuw[d] PP spsp")
|
||||
check("Vance genotype: G locus = Gg (display via G, not uw)", r["mapped8locus"].get("G") == ["G", "g"])
|
||||
check("Vance genotype: E locus = ee (Fuchs)", r["mapped8locus"].get("E") == ["e", "e"])
|
||||
check("Vance genotype: no unmapped tokens", r["unmappedTokens"] == [])
|
||||
|
||||
# --- #42: E-locus e-dash semantics ---
|
||||
# ee[-] = visible Fuchs -> ee (recessive phenotype implies homozygosity)
|
||||
check("ee[-] -> ee (visible Fuchs)", g.parse("ee[-]")["mapped8locus"].get("E") == ["e", "e"])
|
||||
check("E- stays [E, ?] (valid unknown partner)", g.parse("E-")["mapped8locus"].get("E") == ["E", "?"])
|
||||
# bare e- / e[-] is genetically impossible -> invalid (not mapped, surfaced as unmapped)
|
||||
check("e- invalid -> not mapped at E", g.parse("e-")["mapped8locus"].get("E") is None)
|
||||
check("e- invalid -> in unmappedTokens", "e-" in g.parse("e-")["unmappedTokens"])
|
||||
check("e[-] invalid -> not mapped at E", g.parse("e[-]")["mapped8locus"].get("E") is None)
|
||||
r = g.parse("Aa CC D- ee[-] Gg Pp spsp")
|
||||
check("Algierfuchs genotype: E = ee (Fuchs)", r["mapped8locus"].get("E") == ["e", "e"])
|
||||
check("Algierfuchs genotype: no unmapped tokens", r["unmappedTokens"] == [])
|
||||
|
||||
# --- looks_like_genotype recognizes Uw-bearing cells ---
|
||||
check("looks_like_genotype sees Uw as G",
|
||||
g.looks_like_genotype("aa Cc Uwuw") is True)
|
||||
|
||||
Reference in New Issue
Block a user