GEN-3a: Uw=G alias, second spotting locus Sls (SlSl lethal), Sp×Sls Superschecke, flag/metadata token tolerance (dea/WFNZ/DP/RV/GV)

This commit is contained in:
2026-06-06 10:25:48 +02:00
parent c93c24f37c
commit 1e7dc8f91d
4 changed files with 128 additions and 21 deletions

View File

@@ -25,6 +25,7 @@ interface LethalRule {
const LETHAL_RULES: readonly LethalRule[] = [
{ locus: 'Sp', allele: 'Sp', kind: 'lethal', warning: GeneticsWarningCode.ScheckeLethal },
{ locus: 'Sls', allele: 'Sl', kind: 'lethal', warning: GeneticsWarningCode.SlsLethal },
{ locus: 'Re', allele: 'Re', kind: 'semi', warning: GeneticsWarningCode.RexSemiLethal },
]
@@ -67,14 +68,23 @@ export function applyLethality(dist: DistEntry<Genotype>[]): LethalityResult {
? survivors
: survivors.map((e) => ({ value: e.value, probability: divide(e.probability, survivingMass) }))
if (lethalMass.num > 0) {
warnings.push({
code: GeneticsWarningCode.ScheckeLethal,
detail: {
youngLostFraction: toString(lethalMass),
youngLostPercent: Number(((lethalMass.num / lethalMass.den) * 100).toFixed(2)),
},
})
// One lethal warning PER lethal rule that actually removed young (so SpSp ->
// ScheckeLethal and S(l)S(l) -> SlsLethal are reported distinctly).
for (const rule of LETHAL_RULES) {
if (rule.kind !== 'lethal') continue
const mass = dist.reduce<Fraction>(
(acc, e) => (isHomozygous(e.value, rule.locus, rule.allele) ? add(acc, e.probability) : acc),
ZERO,
)
if (mass.num > 0) {
warnings.push({
code: rule.warning,
detail: {
youngLostFraction: toString(mass),
youngLostPercent: Number(((mass.num / mass.den) * 100).toFixed(2)),
},
})
}
}
// Semi-lethal: warn if any surviving genotype is homozygous for a semi-lethal allele.
@@ -95,5 +105,22 @@ export function applyLethality(dist: DistEntry<Genotype>[]): LethalityResult {
}
}
// Superschecke: surviving young carrying BOTH spotting markers (Sp present and
// S(l) present) are very-high-white and deafness-prone — info warning.
const superMass = distribution.reduce<Fraction>((acc, e) => {
const hasSp = e.value.Sp.includes('Sp')
const hasSl = e.value.Sls.includes('Sl')
return hasSp && hasSl ? add(acc, e.probability) : acc
}, ZERO)
if (superMass.num > 0) {
warnings.push({
code: GeneticsWarningCode.SuperscheckeDeaf,
detail: {
affectedFraction: toString(superMass),
affectedPercent: Number(((superMass.num / superMass.den) * 100).toFixed(2)),
},
})
}
return { distribution, warnings }
}