From 2bee5caaa25aa6f159ab7221f8759691feb9a53e Mon Sep 17 00:00:00 2001 From: Gulum Date: Fri, 5 Jun 2026 23:42:05 +0200 Subject: [PATCH] GEN-1: add Probeverpaarung API (breed) + public index Co-Authored-By: Claude Opus 4.8 (1M context) --- gerbil-manager-web/src/genetics/breed.ts | 90 ++++++++++++++++++++++++ gerbil-manager-web/src/genetics/index.ts | 39 ++++++++++ 2 files changed, 129 insertions(+) create mode 100644 gerbil-manager-web/src/genetics/breed.ts create mode 100644 gerbil-manager-web/src/genetics/index.ts diff --git a/gerbil-manager-web/src/genetics/breed.ts b/gerbil-manager-web/src/genetics/breed.ts new file mode 100644 index 0000000..c8502d6 --- /dev/null +++ b/gerbil-manager-web/src/genetics/breed.ts @@ -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 + 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 } +} diff --git a/gerbil-manager-web/src/genetics/index.ts b/gerbil-manager-web/src/genetics/index.ts new file mode 100644 index 0000000..2bb9c21 --- /dev/null +++ b/gerbil-manager-web/src/genetics/index.ts @@ -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'