GEN-1: add Probeverpaarung API (breed) + public index

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-05 23:42:05 +02:00
parent d99294e30e
commit 2bee5caaa2
2 changed files with 129 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
/**
* 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<LocusKey, [string, string]>
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<T extends { probability: Probability }>(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<string, Fraction>()
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 }
}

View File

@@ -0,0 +1,39 @@
/**
* Gerbil colour-genetics engine — public API.
*
* Pure TypeScript domain module (no React, no API calls). The UI layer (FEAT-5)
* consumes `breed()` and resolves warning CODES to German text via de.ts.
*/
export { breed } from './breed'
export type {
BreedingResult,
OffspringGenotype,
FarbschlagShare,
Probability,
} from './breed'
export {
makeGenotype,
wildType,
toDisplayString,
fromDisplayString,
toJSON,
fromJSON,
hasUnknown,
WILDCARD,
} from './genotype'
export type { Genotype, AllelePair } from './genotype'
export { LOCI, LOCUS_ORDER } from './loci'
export type { LocusKey, LocusDef } from './loci'
export { phenotypeTokens } from './phenotype'
export type { PhenotypeTokens } from './phenotype'
export { farbschlagFor, BASE_COLORS, CATALOG_SIZE, UNKNOWN_FARBSCHLAG } from './catalog'
export type { FarbschlagEntry, FarbschlagMatch } from './catalog'
export { GeneticsWarningCode } from './warnings'
export type { GeneticsWarning } from './warnings'
export type { Fraction } from './fraction'