fix(genetics): Engine-Fixes Ticket-Triage (Goldfuchsschimmel, Dilute CP-Blaufuchs, Eltern-Inferenz, Punnett, uw[d])

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 09:05:35 +02:00
parent 5e14124322
commit 8c1ee75b81
15 changed files with 2126 additions and 87 deletions

View File

@@ -7,7 +7,14 @@ import { listColorVarieties, listContacts, listEnclosures, listLitters } from '.
import { useApi, useMutation } from '../hooks/useApi'
import { formatDate, genderLabel, statusLabel } from '../format/labels'
import { ALL_TRAITS, TRAIT_CATEGORIES } from '../format/traits'
import { fromDisplayString, genotypeToFarbschlag, displayGenotypeSafe } from '../genetics'
import {
fromDisplayString,
genotypeToFarbschlag,
displayGenotypeSafe,
toDisplayString,
hasUnknown,
inferUnknownsFromParents,
} from '../genetics'
import type { Gender, GerbilStatus } from '../api/types'
import FarbschlagImage from '../components/FarbschlagImage'
import GerbilHealthTab from '../components/GerbilHealthTab'
@@ -138,6 +145,37 @@ export default function GerbilDetailPage() {
const showEnclosure = g.status !== 'Deceased' && g.status !== 'GivenAway'
const geno = describeGenotype(g.genotype)
// GEN-5 (Tickets cc9ea3fe / 1a508c04): unbekannte Gencode-Buchstaben aus einem
// reinerbigen Elternteil ergänzen (Mendel: ein reinerbiger Elternteil kann nur
// dieses eine Allel vererben). Greift nur, wenn der Genotyp ein '-' enthält UND
// mindestens ein Elternteil mit Genotyp am eigenen Wurf hinterlegt ist.
const genoInferred = (() => {
if (!g.genotype) return null
let child
try {
child = fromDisplayString(g.genotype)
} catch {
return null
}
if (!hasUnknown(child)) return null
const parse = (s: string | null | undefined) => {
if (!s) return null
try {
return fromDisplayString(s)
} catch {
return null
}
}
const f = parse(father.data?.genotype)
const m = parse(mother.data?.genotype)
if (!f && !m) return null
const res = inferUnknownsFromParents(child, f, m)
if (res.inferred.length === 0) return null
const display = toDisplayString(res.genotype)
if (display === geno?.display) return null
return { display, farbschlag: genotypeToFarbschlag(res.genotype) }
})()
const lookup = (map: Map<string, string>, key: string | null) => (key ? (map.get(key) ?? '—') : '—')
const storedColorName = g.colorVarietyId ? (colorName.get(g.colorVarietyId) ?? null) : null
@@ -342,14 +380,29 @@ export default function GerbilDetailPage() {
<dl className="ak-kvlist">
<Kv label={t.fields.genotype}>
<code className="ak-genotype">{geno.display}</code>
{genoInferred && (
<span className="ak-inferred" title={t.detail.genotypeInferredTitle}>
{' → '}
<code className="ak-genotype">{genoInferred.display}</code>{' '}
<small className="ak-inferred-chip"> {t.detail.genotypeInferred}</small>
</span>
)}
</Kv>
<Kv label={t.detail.resolvedPrefix}>
{geno.farbschlag}
{storedColorName &&
geno.farbschlag !== de.genetics.unknownFarbschlag &&
storedColorName !== geno.farbschlag.replace(' Schecke', '').replace(' Rex', '') && (
<small className="ak-mismatch"> {t.detail.farbschlagMismatch}</small>
)}
{(() => {
const resolvedFarbschlag = (genoInferred ?? geno).farbschlag
return (
<>
{resolvedFarbschlag}
{storedColorName &&
resolvedFarbschlag !== de.genetics.unknownFarbschlag &&
storedColorName !==
resolvedFarbschlag.replace(' Schecke', '').replace(' Rex', '') && (
<small className="ak-mismatch"> {t.detail.farbschlagMismatch}</small>
)}
</>
)
})()}
</Kv>
</dl>
) : (