/** * Probeverpaarung (test mating): predict offspring of two parent genotypes. * * Pipeline: Punnett combine -> merge identical genotypes -> apply lethality * (remove SpSp, renormalise, collect warnings) -> annotate each surviving * genotype with its Farbschlag and probability. Also aggregates by Farbschlag. * * Pure & deterministic; no React, no I/O. Display texts (warning messages) are * resolved from CODES in the UI layer (de.ts). */ import { add, toNumber, toPercentString, toString, ZERO, type Fraction } from './fraction' import { type Genotype, toDisplayString, toJSON } from './genotype' import { combineGenotypes, mergeByGenotype } from './punnett' import { applyLethality } from './lethality' import { farbschlagFor } from './catalog' import type { GeneticsWarning } from './warnings' import type { LocusKey } from './loci' export interface Probability { readonly fraction: Fraction /** Decimal in [0,1]. */ readonly value: number /** "3/16" style. */ readonly text: string /** "18.75 %" style. */ readonly percent: string } function prob(f: Fraction): Probability { return { fraction: f, value: toNumber(f), text: toString(f), percent: toPercentString(f) } } export interface OffspringGenotype { readonly farbschlag: string /** Compact display string, e.g. "Aa CC Dd EE GG Pp spsp rere". */ readonly genotype: string /** Structured genotype (JSON storage shape). */ readonly genotypeJson: Record readonly probability: Probability /** True when the Farbschlag could not be named from the catalog. */ readonly unknownFarbschlag: boolean } export interface FarbschlagShare { readonly farbschlag: string readonly probability: Probability } export interface BreedingResult { /** Per-genotype offspring distribution (live births), highest probability first. */ readonly offspring: OffspringGenotype[] /** Aggregated by Farbschlag (colour-morph), highest probability first. */ readonly byFarbschlag: FarbschlagShare[] readonly warnings: GeneticsWarning[] } function byProbDesc(a: T, b: T): number { return b.probability.value - a.probability.value } /** Predict the offspring distribution of father × mother. */ export function breed(father: Genotype, mother: Genotype): BreedingResult { const raw = combineGenotypes(father, mother) const merged = mergeByGenotype(raw) const { distribution, warnings } = applyLethality(merged) const offspring: OffspringGenotype[] = distribution .map((entry) => { const match = farbschlagFor(entry.value) return { farbschlag: match.name, genotype: toDisplayString(entry.value), genotypeJson: toJSON(entry.value), probability: prob(entry.probability), unknownFarbschlag: match.unknown, } }) .sort(byProbDesc) const colorMass = new Map() for (const entry of distribution) { const name = farbschlagFor(entry.value).name colorMass.set(name, add(colorMass.get(name) ?? ZERO, entry.probability)) } const byFarbschlag: FarbschlagShare[] = Array.from(colorMass.entries()) .map(([farbschlag, f]) => ({ farbschlag, probability: prob(f) })) .sort(byProbDesc) return { offspring, byFarbschlag, warnings } }