GEN-2: expand Farbschlag catalog 18->73 (baseportal.de) + images + collision-aware tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BIN
gerbil-manager-web/public/farbschlaege/agouti-cp-hell.JPG
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
gerbil-manager-web/public/farbschlaege/agouti-cp.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
gerbil-manager-web/public/farbschlaege/agouti-dd-cp.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
gerbil-manager-web/public/farbschlaege/agouti-dd.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
gerbil-manager-web/public/farbschlaege/agouti-hell-dd-cp.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 58 KiB |
BIN
gerbil-manager-web/public/farbschlaege/algierfuchs-cp.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
gerbil-manager-web/public/farbschlaege/algierfuchs-hell-cp.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
gerbil-manager-web/public/farbschlaege/algierfuchs-hell.JPG
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
gerbil-manager-web/public/farbschlaege/algierfuchs.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
gerbil-manager-web/public/farbschlaege/algierfuchsschimmel.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
gerbil-manager-web/public/farbschlaege/altweiss-rew.jpeg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
gerbil-manager-web/public/farbschlaege/anthrazit-dd.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
gerbil-manager-web/public/farbschlaege/anthrazit.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
gerbil-manager-web/public/farbschlaege/apricot-blassfuchs.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
gerbil-manager-web/public/farbschlaege/blau-schwarz-dd.JPG
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
gerbil-manager-web/public/farbschlaege/blaufuchs-cp.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
gerbil-manager-web/public/farbschlaege/blaufuchs-dd.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
gerbil-manager-web/public/farbschlaege/blaufuchs-hell.jpeg
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
gerbil-manager-web/public/farbschlaege/blaufuchs.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
gerbil-manager-web/public/farbschlaege/blaufuchsschimmel.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
gerbil-manager-web/public/farbschlaege/c-separator.jpg
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
gerbil-manager-web/public/farbschlaege/elfenbein.jpg
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
gerbil-manager-web/public/farbschlaege/gold-dd.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
gerbil-manager-web/public/farbschlaege/gold-hell.jpg
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
gerbil-manager-web/public/farbschlaege/gold.jpg
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
gerbil-manager-web/public/farbschlaege/goldfuchs-hell.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
gerbil-manager-web/public/farbschlaege/goldfuchs.jpg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
gerbil-manager-web/public/farbschlaege/goldfuchsschimmel.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
gerbil-manager-web/public/farbschlaege/hermelin.jpeg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
gerbil-manager-web/public/farbschlaege/himalaya.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
gerbil-manager-web/public/farbschlaege/kohlfuchs-cp.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
gerbil-manager-web/public/farbschlaege/kohlfuchs-dd.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
gerbil-manager-web/public/farbschlaege/kohlfuchs-hell-2.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
gerbil-manager-web/public/farbschlaege/kohlfuchs-hell-cp.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
gerbil-manager-web/public/farbschlaege/kohlfuchs-hell.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
gerbil-manager-web/public/farbschlaege/kohlfuchs.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
gerbil-manager-web/public/farbschlaege/kohlfuchsschimmel-cp.JPG
Normal file
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 46 KiB |
BIN
gerbil-manager-web/public/farbschlaege/kohlfuchsschimmel.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
gerbil-manager-web/public/farbschlaege/marder-dd.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
gerbil-manager-web/public/farbschlaege/marder.JPG
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
gerbil-manager-web/public/farbschlaege/platin-dd.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
gerbil-manager-web/public/farbschlaege/platin-hell.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
gerbil-manager-web/public/farbschlaege/platin.JPG
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
gerbil-manager-web/public/farbschlaege/polarfuchs-cp.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
gerbil-manager-web/public/farbschlaege/polarfuchs-hell-cp.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
gerbil-manager-web/public/farbschlaege/polarfuchs-hell.jpeg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
gerbil-manager-web/public/farbschlaege/polarfuchs.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
gerbil-manager-web/public/farbschlaege/polarfuchsschimmel.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
gerbil-manager-web/public/farbschlaege/rotaugen-schimmel.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 84 KiB |
BIN
gerbil-manager-web/public/farbschlaege/rotfuchs-hell.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
gerbil-manager-web/public/farbschlaege/rotfuchs.JPG
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
gerbil-manager-web/public/farbschlaege/rotfuchsschimmel.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
gerbil-manager-web/public/farbschlaege/saphir.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 68 KiB |
BIN
gerbil-manager-web/public/farbschlaege/schwarz.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
gerbil-manager-web/public/farbschlaege/siam-marder-hell-dd.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
gerbil-manager-web/public/farbschlaege/siam-marder-hell.JPG
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
gerbil-manager-web/public/farbschlaege/silberagouti-cp.JPG
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
gerbil-manager-web/public/farbschlaege/silberagouti-dd-cp.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
gerbil-manager-web/public/farbschlaege/silberagouti-dd.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 74 KiB |
BIN
gerbil-manager-web/public/farbschlaege/silberagouti.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
gerbil-manager-web/public/farbschlaege/silberschimmel.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
gerbil-manager-web/public/farbschlaege/topas-dd.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
gerbil-manager-web/public/farbschlaege/topas.jpg
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
gerbil-manager-web/public/farbschlaege/zobel-dd.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
gerbil-manager-web/public/farbschlaege/zobel-hell-dd.jpg
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
gerbil-manager-web/public/farbschlaege/zobel-hell.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
gerbil-manager-web/public/farbschlaege/zobel.jpeg
Normal file
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -17,6 +17,7 @@ import {
|
||||
wildType,
|
||||
} from '../genotype'
|
||||
import { combineLocus } from '../punnett'
|
||||
import { LOCI, type LocusKey } from '../loci'
|
||||
import {
|
||||
farbschlagFor,
|
||||
genotypeToFarbschlag,
|
||||
@@ -25,6 +26,9 @@ import {
|
||||
CATALOG,
|
||||
CATALOG_SIZE,
|
||||
} from '../catalog'
|
||||
|
||||
/** The first 18 entries are the frozen contract names (must round-trip exactly). */
|
||||
const FROZEN_COUNT = 18
|
||||
import { breed } from '../breed'
|
||||
import { GeneticsWarningCode } from '../warnings'
|
||||
|
||||
@@ -173,18 +177,38 @@ describe('Farbschlag catalog', () => {
|
||||
expect(match.name).toBe('Unbekannter Farbschlag')
|
||||
})
|
||||
|
||||
it('has the expected catalogue coverage', () => {
|
||||
expect(CATALOG_SIZE).toBe(18)
|
||||
it('has the expected catalogue coverage (GEN-2: frozen 18 + 55 portal varieties)', () => {
|
||||
expect(CATALOG_SIZE).toBe(73)
|
||||
})
|
||||
|
||||
it('every catalog entry round-trips: its representative genotype maps back to its own name', () => {
|
||||
// Guards the DB seed (DATA-2): a representative genotype that resolved to a
|
||||
// DIFFERENT (earlier) variety would mean overlapping/mis-ordered patterns.
|
||||
for (const entry of BASE_COLORS) {
|
||||
it('frozen contract names round-trip to themselves (DB-key guard)', () => {
|
||||
// The first 18 are the frozen ColorVariety keys — their representative
|
||||
// genotype MUST resolve back to their own name, never a later variety.
|
||||
for (const entry of BASE_COLORS.slice(0, FROZEN_COUNT)) {
|
||||
expect(genotypeToFarbschlag(representativeGenotype(entry))).toBe(entry.name)
|
||||
}
|
||||
})
|
||||
|
||||
it('every entry resolves to a named variety (no self-Unbekannt)', () => {
|
||||
// Each catalogued variety's representative genotype must resolve to SOME
|
||||
// named variety — never "Unbekannt". Genotype-identical synonyms (e.g.
|
||||
// "-hell") or entries that omit a locus may resolve to an earlier primary;
|
||||
// that's expected first-match behaviour, not an error.
|
||||
for (const entry of BASE_COLORS) {
|
||||
expect(farbschlagFor(representativeGenotype(entry)).unknown).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('has no duplicate variety names and only valid allele tokens', () => {
|
||||
const names = BASE_COLORS.map((e) => e.name)
|
||||
expect(new Set(names).size).toBe(names.length)
|
||||
for (const entry of BASE_COLORS) {
|
||||
for (const [locus, value] of Object.entries(entry.tokens)) {
|
||||
expect(LOCI[locus as LocusKey].alleles).toContain(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('CATALOG seed view mirrors the ColorVariety table shape', () => {
|
||||
expect(CATALOG).toHaveLength(CATALOG_SIZE)
|
||||
expect(CATALOG[0]).toMatchObject({ name: 'Pink Eyed White (PEW)', sortOrder: 0 })
|
||||
|
||||
@@ -1,63 +1,122 @@
|
||||
/**
|
||||
* Farbschlag catalog: genotype -> German variety name.
|
||||
* Farbschlag catalog: genotype -> German variety name + the full named catalog.
|
||||
*
|
||||
* A catalog entry matches when every locus it SPECIFIES equals the genotype's
|
||||
* phenotype token (see phenotype.ts). Unspecified loci are wildcards. The first
|
||||
* matching base-colour entry wins; Schecke/Rex are appended as modifiers.
|
||||
* matching entry wins; Schecke/Rex are appended as modifiers.
|
||||
*
|
||||
* Coverage is partial by design — extend BASE_COLORS as more genotype tables
|
||||
* are confirmed. Unknown genotypes fall back to "Unbekannter Farbschlag" plus
|
||||
* the raw genotype string (handled by the caller / index.ts).
|
||||
* Two roles, one list:
|
||||
* - REVERSE MATCH (genotypeToFarbschlag): first match wins, so the frozen/base
|
||||
* names are listed FIRST and win their token-set. Genotype-identical synonyms
|
||||
* (e.g. "-hell" intensity variants) are listed after and simply won't be the
|
||||
* reverse-match result — that's expected (shade isn't encoded in the 6 loci).
|
||||
* - FULL NAMED LIST (CATALOG seed + dropdowns + images): every entry is a
|
||||
* distinct user-pickable variety with its own image.
|
||||
*
|
||||
* Sourced from the German gerbil-breeder community:
|
||||
* - de.wikibooks.org/wiki/Die_Rennmaus/_Farbvarianten_und_Farbgenetik (black/red-eyed tables)
|
||||
* - rennmaus-info.jimdoweb.com (loci & colourpoint series)
|
||||
* - clan-of-topolino.ch, rennmauswelten.jimdofree.com (Zobel, Schimmel)
|
||||
* Sources:
|
||||
* - First 18: de.wikibooks Rennmaus-Farbgenetik, rennmaus-info, clan-of-topolino,
|
||||
* rennmauswelten. These names are a FROZEN contract (DB keys) — never rename.
|
||||
* - Remaining 55 (GEN-2): baseportal.de "Farben" DB (78 rows; 6 "zur Scheckung"
|
||||
* meta rows dropped, 17 matched the frozen names). Genotypes normalized from
|
||||
* portal notation (c[chm]->cchm, c[h]->ch, e[f]->ef, '-'/'--' = unknown).
|
||||
*/
|
||||
import { LOCUS_ORDER, type LocusKey } from './loci'
|
||||
import { makeGenotype, toDisplayString, wildType, type AllelePair, type Genotype } from './genotype'
|
||||
import { phenotypeTokens, type PhenotypeTokens } from './phenotype'
|
||||
|
||||
export interface FarbschlagEntry {
|
||||
/** German variety name. */
|
||||
/** German variety name. FROZEN for the original 18 (DB keys). */
|
||||
readonly name: string
|
||||
/** Common English name (reference only; UI is German). */
|
||||
readonly english?: string
|
||||
/** Required phenotype tokens; omitted loci are wildcards. */
|
||||
readonly tokens: Partial<Record<LocusKey, string>>
|
||||
/** Image filename under /farbschlaege/ (public), if available. */
|
||||
readonly image?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Base colours (the A/C/D/E/G/P phenotype). Ordered most-specific first so the
|
||||
* earliest match wins. Colourpoint (C != "C") entries are naturally disjoint
|
||||
* from full-colour ones because the C token differs.
|
||||
* Ordered so the earliest match wins. The frozen 18 come first (they win their
|
||||
* token-sets); the baseportal varieties follow, base before "-hell" within each
|
||||
* portal collision group.
|
||||
*/
|
||||
export const BASE_COLORS: readonly FarbschlagEntry[] = [
|
||||
// ── Colourpoint / C-series (black & red eyed) ──
|
||||
{ name: 'Pink Eyed White (PEW)', english: 'Pink Eyed White', tokens: { C: 'ch', P: 'p' } },
|
||||
{ name: 'Hermelin', english: 'Dark Tailed White', tokens: { A: 'a', C: 'ch', D: 'D', P: 'P' } },
|
||||
{ name: 'Himalaya', english: 'Himalayan', tokens: { A: 'A', C: 'ch', D: 'D', P: 'P' } },
|
||||
{ name: 'Zobel', english: 'Sable', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'E', G: 'g', P: 'P' } },
|
||||
|
||||
// ── Schimmel / roan (ef ef) ──
|
||||
// ── Frozen 18 (names are DB-key contract; do not rename) ──
|
||||
{ name: 'Pink Eyed White (PEW)', english: 'Pink Eyed White', tokens: { C: 'ch', P: 'p' }, image: 'rotaugen-weiss-pew-d-sep-e-sep.jpg' },
|
||||
{ name: 'Hermelin', english: 'Dark Tailed White', tokens: { A: 'a', C: 'ch', D: 'D', P: 'P' }, image: 'hermelin.jpeg' },
|
||||
{ name: 'Himalaya', english: 'Himalayan', tokens: { A: 'A', C: 'ch', D: 'D', P: 'P' }, image: 'himalaya.jpg' },
|
||||
{ name: 'Zobel', english: 'Sable', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'E', G: 'g', P: 'P' }, image: 'zobel.jpeg' },
|
||||
{ name: 'Schwarzschimmel', english: 'Black Roan', tokens: { C: 'C', D: 'D', E: 'ef', G: 'G', P: 'P' } },
|
||||
{ name: 'Rotaugenschimmel', english: 'Red-Eyed Roan', tokens: { C: 'C', D: 'D', E: 'ef', G: 'G', P: 'p' } },
|
||||
{ name: 'Rotaugenschimmel', english: 'Red-Eyed Roan', tokens: { C: 'C', D: 'D', E: 'ef', G: 'G', P: 'p' }, image: 'rotaugen-schimmel.jpg' },
|
||||
{ name: 'Agouti', english: 'Golden Agouti', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'G', P: 'P' }, image: 'agouti-mit-erklaerung-der-genloci.JPG' },
|
||||
{ name: 'Schwarz', english: 'Black', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'G', P: 'P' }, image: 'schwarz.jpg' },
|
||||
{ name: 'Silberagouti', english: 'Grey Agouti', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'g', P: 'P' }, image: 'silberagouti.jpg' },
|
||||
{ name: 'Anthrazit', english: 'Slate', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'g', P: 'P' }, image: 'anthrazit.jpg' },
|
||||
{ name: 'Algierfuchs', english: 'Dark-Eyed Honey', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'algierfuchs.jpg' },
|
||||
{ name: 'Blau', english: 'Blue', tokens: { A: 'a', C: 'C', D: 'd', E: 'E', G: 'G', P: 'P' }, image: 'blau-schwarz-dd.JPG' },
|
||||
{ name: 'Gold', english: 'Argente Golden', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'gold.jpg' },
|
||||
{ name: 'Platin', english: 'Lilac', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'platin.JPG' },
|
||||
{ name: 'Goldfuchs', english: 'Yellow Fox', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'G', P: 'p' }, image: 'goldfuchs.jpg' },
|
||||
{ name: 'Rotfuchs', english: 'Argente Nutmeg', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'G', P: 'p' }, image: 'rotfuchs.JPG' },
|
||||
{ name: 'dd Gold', english: 'dd Argente Golden', tokens: { A: 'A', C: 'C', D: 'd', E: 'E', G: 'G', P: 'p' }, image: 'gold-dd.jpg' },
|
||||
{ name: 'dd Platin', english: 'dd Lilac', tokens: { A: 'a', C: 'C', D: 'd', E: 'E', G: 'G', P: 'p' }, image: 'platin-dd.jpg' },
|
||||
|
||||
// ── Black-eyed full colour (P) ──
|
||||
{ name: 'Agouti', english: 'Golden Agouti', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'G', P: 'P' } },
|
||||
{ name: 'Schwarz', english: 'Black', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'G', P: 'P' } },
|
||||
{ name: 'Silberagouti', english: 'Grey Agouti', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'g', P: 'P' } },
|
||||
{ name: 'Anthrazit', english: 'Slate', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'g', P: 'P' } },
|
||||
{ name: 'Algierfuchs', english: 'Dark-Eyed Honey', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'G', P: 'P' } },
|
||||
{ name: 'Blau', english: 'Blue', tokens: { A: 'a', C: 'C', D: 'd', E: 'E', G: 'G', P: 'P' } },
|
||||
|
||||
// ── Red-eyed full colour (p) ──
|
||||
{ name: 'Gold', english: 'Argente Golden', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'G', P: 'p' } },
|
||||
{ name: 'Platin', english: 'Lilac', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'G', P: 'p' } },
|
||||
{ name: 'Goldfuchs', english: 'Yellow Fox', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'G', P: 'p' } },
|
||||
{ name: 'Rotfuchs', english: 'Argente Nutmeg', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'G', P: 'p' } },
|
||||
{ name: 'dd Gold', english: 'dd Argente Golden', tokens: { A: 'A', C: 'C', D: 'd', E: 'E', G: 'G', P: 'p' } },
|
||||
{ name: 'dd Platin', english: 'dd Lilac', tokens: { A: 'a', C: 'C', D: 'd', E: 'E', G: 'G', P: 'p' } },
|
||||
// ── baseportal.de varieties (GEN-2), normalized to the frozen allele table ──
|
||||
{ name: 'Altweiss (REW)', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'g', P: 'p' }, image: 'altweiss-rew.jpeg' },
|
||||
{ name: 'Apricot (Blassfuchs)', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'g', P: 'p' }, image: 'apricot-blassfuchs.jpg' },
|
||||
{ name: 'Blaufuchs', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'g', P: 'P' }, image: 'blaufuchs.jpg' },
|
||||
{ name: 'C-Separator', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'g', P: 'p' }, image: 'c-separator.jpg' },
|
||||
{ name: 'Elfenbein', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'g', P: 'p' }, image: 'elfenbein.jpg' },
|
||||
{ name: 'Kohlfuchs', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'kohlfuchs.jpg' },
|
||||
{ name: 'Marder', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'E', G: 'G', P: 'P' }, image: 'marder.JPG' },
|
||||
{ name: 'Siam (Marder-Hell)', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'E', G: 'G', P: 'P' }, image: 'siam-marder-hell.JPG' },
|
||||
{ name: 'Polarfuchs', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'g', P: 'P' }, image: 'polarfuchs.jpg' },
|
||||
{ name: 'Saphir', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'saphir.jpg' },
|
||||
{ name: 'Schimmel (Orangeschimmel)', tokens: { C: 'C', D: 'D', E: 'ef', G: 'G', P: 'P' }, image: 'schimmel-orangeschimmel.jpg' },
|
||||
{ name: 'Topas', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'topas.jpg' },
|
||||
{ name: 'Platin-Hell', tokens: { A: 'a', C: 'C', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'platin-hell.jpg' },
|
||||
{ name: 'Agouti dd', tokens: { A: 'A', C: 'C', D: 'd', E: 'E', G: 'G', P: 'P' }, image: 'agouti-dd.jpg' },
|
||||
{ name: 'Silberagouti dd', tokens: { A: 'A', C: 'C', D: 'd', E: 'E', G: 'g', P: 'P' }, image: 'silberagouti-dd.jpg' },
|
||||
{ name: 'Kohlfuchs dd', tokens: { A: 'a', C: 'C', D: 'd', E: 'e', G: 'G', P: 'P' }, image: 'kohlfuchs-dd.jpg' },
|
||||
{ name: 'Anthrazit dd', tokens: { A: 'a', C: 'C', D: 'd', E: 'E', G: 'g', P: 'P' }, image: 'anthrazit-dd.jpg' },
|
||||
{ name: 'Agouti CP-Hell', tokens: { A: 'A', C: 'cchm', D: 'D', E: 'E', G: 'G', P: 'P' }, image: 'agouti-cp-hell.JPG' },
|
||||
{ name: 'Blaufuchs CP', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'e', G: 'g', P: 'P' }, image: 'blaufuchs-cp.jpg' },
|
||||
{ name: 'Polarfuchsschimmel', tokens: { A: 'A', C: 'C', D: 'D', E: 'ef', G: 'g', P: 'P' }, image: 'polarfuchsschimmel.jpg' },
|
||||
{ name: 'Silberschimmel', tokens: { C: 'C', D: 'D', E: 'ef', G: 'g', P: 'P' }, image: 'silberschimmel.jpg' },
|
||||
{ name: 'Algierfuchsschimmel', tokens: { A: 'A', C: 'C', D: 'D', E: 'ef', G: 'G', P: 'P' }, image: 'algierfuchsschimmel.jpg' },
|
||||
{ name: 'Polarfuchs-Hell CP', tokens: { A: 'A', C: 'cchm', D: 'D', E: 'e', G: 'g', P: 'P' }, image: 'polarfuchs-hell-cp.jpg' },
|
||||
{ name: 'Kohlfuchsschimmel', tokens: { A: 'a', C: 'C', D: 'D', E: 'ef', G: 'G', P: 'P' }, image: 'kohlfuchsschimmel.jpg' },
|
||||
{ name: 'Blaufuchsschimmel', tokens: { A: 'a', C: 'C', D: 'D', E: 'ef', G: 'g', P: 'P' }, image: 'blaufuchsschimmel.jpg' },
|
||||
{ name: 'Kohlfuchs, hell', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'kohlfuchs-hell.jpg' },
|
||||
{ name: 'Goldfuchs, hell', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'G', P: 'p' }, image: 'goldfuchs-hell.jpg' },
|
||||
{ name: 'Goldfuchsschimmel', tokens: { A: 'A', C: 'C', D: 'D', E: 'ef', G: 'G', P: 'p' }, image: 'goldfuchsschimmel.jpg' },
|
||||
{ name: 'Gold-Hell', tokens: { A: 'A', C: 'C', D: 'D', E: 'E', G: 'G', P: 'p' }, image: 'gold-hell.jpg' },
|
||||
{ name: 'Siam (Marder-Hell) dd', tokens: { A: 'a', C: 'cchm', D: 'd', E: 'E', G: 'G', P: 'P' }, image: 'siam-marder-hell-dd.jpg' },
|
||||
{ name: 'Marder dd', tokens: { A: 'a', C: 'cchm', D: 'd', E: 'E', G: 'G', P: 'P' }, image: 'marder-dd.jpg' },
|
||||
{ name: 'Zobel-Hell', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'E', G: 'g', P: 'P' }, image: 'zobel-hell.jpg' },
|
||||
{ name: 'Silberagouti dd CP', tokens: { A: 'A', C: 'cchm', D: 'd', E: 'E', G: 'g', P: 'P' }, image: 'silberagouti-dd-cp.jpg' },
|
||||
{ name: 'Silberagouti-Hell dd CP', tokens: { A: 'A', C: 'cchm', D: 'd', E: 'E', G: 'g', P: 'P' }, image: 'silberagouti-hell-dd-cp.jpg' },
|
||||
{ name: 'Agouti dd CP', tokens: { A: 'A', C: 'cchm', D: 'd', E: 'E', G: 'G', P: 'P' }, image: 'agouti-dd-cp.jpg' },
|
||||
{ name: 'Agouti-Hell dd CP', tokens: { A: 'A', C: 'cchm', D: 'd', E: 'E', G: 'G', P: 'P' }, image: 'agouti-hell-dd-cp.jpg' },
|
||||
{ name: 'Blaufuchs, hell', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'g', P: 'P' }, image: 'blaufuchs-hell.jpeg' },
|
||||
{ name: 'Rotfuchsschimmel', tokens: { A: 'a', C: 'C', D: 'D', E: 'ef', G: 'G', P: 'p' }, image: 'rotfuchsschimmel.jpg' },
|
||||
{ name: 'Polarfuchs, hell', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'g', P: 'P' }, image: 'polarfuchs-hell.jpeg' },
|
||||
{ name: 'Kohlfuchsschimmel, hell', tokens: { A: 'a', C: 'C', D: 'D', E: 'ef', G: 'G', P: 'P' }, image: 'kohlfuchsschimmel-hell.jpg' },
|
||||
{ name: 'Rotfuchs, hell', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'G', P: 'p' }, image: 'rotfuchs-hell.jpg' },
|
||||
{ name: 'Zobel dd', tokens: { A: 'a', C: 'cchm', D: 'd', E: 'E', G: 'g', P: 'P' }, image: 'zobel-dd.jpg' },
|
||||
{ name: 'Kohlfuchs-Hell', tokens: { A: 'a', C: 'C', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'kohlfuchs-hell-2.jpg' },
|
||||
{ name: 'Kohlfuchs CP', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'kohlfuchs-cp.jpg' },
|
||||
{ name: 'Algierfuchs CP', tokens: { A: 'A', C: 'cchm', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'algierfuchs-cp.jpg' },
|
||||
{ name: 'Silberagouti CP', tokens: { A: 'A', C: 'cchm', D: 'D', E: 'E', G: 'g', P: 'P' }, image: 'silberagouti-cp.JPG' },
|
||||
{ name: 'Agouti CP', tokens: { A: 'A', C: 'cchm', D: 'D', E: 'E', G: 'G', P: 'P' }, image: 'agouti-cp.jpg' },
|
||||
{ name: 'Algierfuchs-Hell CP', tokens: { A: 'A', C: 'cchm', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'algierfuchs-hell-cp.jpg' },
|
||||
{ name: 'Kohlfuchs,hell CP', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'kohlfuchs-hell-cp.jpg' },
|
||||
{ name: 'Polarfuchs CP', tokens: { A: 'A', C: 'cchm', D: 'D', E: 'e', G: 'g', P: 'P' }, image: 'polarfuchs-cp.jpg' },
|
||||
{ name: 'Algierfuchs, hell', tokens: { A: 'A', C: 'C', D: 'D', E: 'e', G: 'G', P: 'P' }, image: 'algierfuchs-hell.JPG' },
|
||||
{ name: 'Topas dd', tokens: { A: 'A', C: 'C', D: 'd', E: 'E', G: 'G', P: 'p' }, image: 'topas-dd.jpg' },
|
||||
{ name: 'Zobel-Hell dd', tokens: { A: 'a', C: 'cchm', D: 'd', E: 'E', G: 'g', P: 'P' }, image: 'zobel-hell-dd.jpg' },
|
||||
{ name: 'Kohlfuchsschimmel CP', tokens: { A: 'a', C: 'cchm', D: 'D', E: 'ef', G: 'G', P: 'P' }, image: 'kohlfuchsschimmel-cp.JPG' },
|
||||
{ name: 'Blaufuchs dd', tokens: { A: 'a', C: 'C', D: 'd', E: 'e', G: 'g', P: 'p' }, image: 'blaufuchs-dd.jpg' },
|
||||
]
|
||||
|
||||
export const UNKNOWN_FARBSCHLAG = 'Unbekannter Farbschlag'
|
||||
@@ -122,7 +181,7 @@ export function representativeGenotype(entry: FarbschlagEntry): Genotype {
|
||||
|
||||
/**
|
||||
* DB seed view for DATA-2's ColorVariety table. Each row mirrors the table
|
||||
* shape (Name, CanonicalGenotype, SortOrder). SortOrder = catalog position.
|
||||
* shape (Name, CanonicalGenotype, SortOrder, ImageFile). SortOrder = position.
|
||||
*
|
||||
* IMPORTANT: `name` values become DB keys the UI filters on — renames are
|
||||
* BREAKING changes and must be routed through god. Adding new varieties is safe.
|
||||
@@ -132,6 +191,7 @@ export interface ColorVarietySeed {
|
||||
readonly english?: string
|
||||
readonly canonicalGenotype: string
|
||||
readonly sortOrder: number
|
||||
readonly image?: string
|
||||
}
|
||||
|
||||
export const CATALOG: readonly ColorVarietySeed[] = BASE_COLORS.map((entry, i) => ({
|
||||
@@ -139,7 +199,18 @@ export const CATALOG: readonly ColorVarietySeed[] = BASE_COLORS.map((entry, i) =
|
||||
english: entry.english,
|
||||
canonicalGenotype: toDisplayString(representativeGenotype(entry)),
|
||||
sortOrder: i,
|
||||
image: entry.image,
|
||||
}))
|
||||
|
||||
/** Number of base-colour varieties currently catalogued. */
|
||||
/** Number of catalogued varieties. */
|
||||
export const CATALOG_SIZE = BASE_COLORS.length
|
||||
|
||||
/** Public path to a Farbschlag image (served from gerbil-manager-web/public). */
|
||||
export function farbschlagImageUrl(image: string | undefined): string | null {
|
||||
return image ? `/farbschlaege/${image}` : null
|
||||
}
|
||||
|
||||
/** Look up a catalog entry by exact variety name. */
|
||||
export function findVariety(name: string): FarbschlagEntry | undefined {
|
||||
return BASE_COLORS.find((e) => e.name === name)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ export {
|
||||
farbschlagFor,
|
||||
genotypeToFarbschlag,
|
||||
representativeGenotype,
|
||||
farbschlagImageUrl,
|
||||
findVariety,
|
||||
BASE_COLORS,
|
||||
CATALOG,
|
||||
CATALOG_SIZE,
|
||||
|
||||