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:
90
gerbil-manager-web/src/genetics/breed.ts
Normal file
90
gerbil-manager-web/src/genetics/breed.ts
Normal 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 }
|
||||
}
|
||||
39
gerbil-manager-web/src/genetics/index.ts
Normal file
39
gerbil-manager-web/src/genetics/index.ts
Normal 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'
|
||||
Reference in New Issue
Block a user