GEN-1: add locus/allele model (A C D E G P Sp Re, multi-allele C/E)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
76
gerbil-manager-web/src/genetics/loci.ts
Normal file
76
gerbil-manager-web/src/genetics/loci.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Locus / allele model for the German gerbil colour-genetics system.
|
||||
*
|
||||
* Identifiers are English/canonical; all user-facing texts live in de.ts.
|
||||
*
|
||||
* Each locus owns an ordered list of alleles, most-dominant first. The
|
||||
* dominance order drives phenotype resolution and lets us pick the
|
||||
* "expressed" allele of a genotype pair.
|
||||
*
|
||||
* Sources (German gerbil-breeder community):
|
||||
* - rennmaus-info.jimdoweb.com/zucht/farb-genloci-bei-rennmäusen
|
||||
* - de.wikibooks.org/wiki/Die_Rennmaus/_Farbvarianten_und_Farbgenetik
|
||||
*/
|
||||
|
||||
/** Canonical locus keys, in conventional display order. */
|
||||
export const LOCUS_ORDER = ['A', 'C', 'D', 'E', 'G', 'P', 'Sp', 'Re'] as const
|
||||
export type LocusKey = (typeof LOCUS_ORDER)[number]
|
||||
|
||||
export interface LocusDef {
|
||||
readonly key: LocusKey
|
||||
/** German locus name (for tooltips/UI; kept here as reference, mirror in de.ts). */
|
||||
readonly nameDe: string
|
||||
/** Allele symbols, most-dominant first. Symbols are the canonical serialised form. */
|
||||
readonly alleles: readonly string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The eight loci. Multi-allele C series modelled explicitly:
|
||||
* C = full colour (dominant)
|
||||
* cchm = marked colourpoint ("chinchilla-medium") — more residual pigment
|
||||
* ch = himalayan colourpoint — least pigment
|
||||
* E series:
|
||||
* E = full extension
|
||||
* ef = Schimmel/roan (progressive whitening)
|
||||
* e = Fox (suppresses eumelanin)
|
||||
* Sp/Re are dominant markers, lethal/semi-lethal when homozygous (see lethality.ts):
|
||||
* Sp = Schecke (checkered); checkered animals are always Spsp, SpSp dies in utero.
|
||||
* Re = Rex (curly coat); rex animals are Re-, ReRe is semi-lethal.
|
||||
*/
|
||||
export const LOCI: Readonly<Record<LocusKey, LocusDef>> = {
|
||||
A: { key: 'A', nameDe: 'Agouti', alleles: ['A', 'a'] },
|
||||
C: { key: 'C', nameDe: 'Farbe (Albino-Serie)', alleles: ['C', 'cchm', 'ch'] },
|
||||
D: { key: 'D', nameDe: 'Verdünnung (Dilute)', alleles: ['D', 'd'] },
|
||||
E: { key: 'E', nameDe: 'Extension (Fox/Schimmel)', alleles: ['E', 'ef', 'e'] },
|
||||
G: { key: 'G', nameDe: 'Grau (Underwhite)', alleles: ['G', 'g'] },
|
||||
P: { key: 'P', nameDe: 'Rotaugenaufhellung (Pink-Eye)', alleles: ['P', 'p'] },
|
||||
Sp: { key: 'Sp', nameDe: 'Schecke', alleles: ['Sp', 'sp'] },
|
||||
Re: { key: 'Re', nameDe: 'Rex', alleles: ['Re', 're'] },
|
||||
}
|
||||
|
||||
/** Set of all valid allele symbols, longest-first (for maximal-munch parsing). */
|
||||
export const ALLELE_SYMBOLS: readonly string[] = Array.from(
|
||||
new Set(LOCUS_ORDER.flatMap((k) => LOCI[k].alleles)),
|
||||
).sort((a, b) => b.length - a.length)
|
||||
|
||||
/** Map every allele symbol to the locus it belongs to. */
|
||||
export const ALLELE_TO_LOCUS: Readonly<Record<string, LocusKey>> = Object.fromEntries(
|
||||
LOCUS_ORDER.flatMap((k) => LOCI[k].alleles.map((al) => [al, k] as const)),
|
||||
)
|
||||
|
||||
/** Dominance rank within a locus (0 = most dominant). */
|
||||
export function dominanceRank(locus: LocusKey, allele: string): number {
|
||||
const idx = LOCI[locus].alleles.indexOf(allele)
|
||||
if (idx < 0) throw new Error(`Unknown allele "${allele}" for locus ${locus}`)
|
||||
return idx
|
||||
}
|
||||
|
||||
/** The dominant (expressed) allele of a pair at a locus. */
|
||||
export function dominantAllele(locus: LocusKey, a: string, b: string): string {
|
||||
return dominanceRank(locus, a) <= dominanceRank(locus, b) ? a : b
|
||||
}
|
||||
|
||||
/** Wild-type homozygous allele (the most dominant) for a locus. */
|
||||
export function wildTypeAllele(locus: LocusKey): string {
|
||||
return LOCI[locus].alleles[0]
|
||||
}
|
||||
Reference in New Issue
Block a user