From d5c155953b35bdebbc4ff627b1f80219cccc71b3 Mon Sep 17 00:00:00 2001 From: Gulum Date: Sun, 7 Jun 2026 01:28:02 +0200 Subject: [PATCH] GEN-4d: rere (Re-Wildtyp) aus Anzeige ausblenden (Julian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - genotype.ts: toDisplayString filtert Re aus wenn re/re (Wildtyp) — analog zur bestehenden Sls-Regel; Rere/ReRe bleiben sichtbar. Wildtyp-Anzeige jetzt 7 Loci: AA CC DD EE GG PP spsp (ohne rere). - StammbaumPage.tsx: Druck-Ahnentafel nutzt toDisplayString(fromDisplayString(g.genotype)) statt rohem g.genotype-String (Audit: einzige Display-Stelle ausserhalb toDisplayString). (GerbilDetailPage/BreedingResultView gehen bereits via toDisplayString.) - genetics.test.ts: Alle Display-String-Assertions auf 7-Loci-Format aktualisiert; Rere-Nachweis zum Sls/Re-Test ergaenzt; Katalog-Regex auf {6,8}. - colorVarietySeed.generated.json regeneriert (kein rere in canonicalGenotype). - backend.json unveraendert (Pam: DB-Speicherung bleibt volles 8-Loki-Format). --- .../src/genetics/__tests__/genetics.test.ts | 61 ++++---- .../genetics/colorVarietySeed.generated.json | 140 +++++++++--------- gerbil-manager-web/src/genetics/genotype.ts | 21 +-- .../src/pages/StammbaumPage.tsx | 8 +- 4 files changed, 122 insertions(+), 108 deletions(-) diff --git a/gerbil-manager-web/src/genetics/__tests__/genetics.test.ts b/gerbil-manager-web/src/genetics/__tests__/genetics.test.ts index 8fdfad7..1a40766 100644 --- a/gerbil-manager-web/src/genetics/__tests__/genetics.test.ts +++ b/gerbil-manager-web/src/genetics/__tests__/genetics.test.ts @@ -46,8 +46,8 @@ describe('Fraction', () => { describe('Genotype serialization', () => { it('round-trips display string <-> structured form', () => { const g = fromDisplayString('Aa CC Dd EE GG Pp Spsp rere') - expect(toDisplayString(g)).toBe('Aa CC Dd EE GG Pp Spsp rere') - expect(toDisplayString(fromJSON(toJSON(g)))).toBe('Aa CC Dd EE GG Pp Spsp rere') + expect(toDisplayString(g)).toBe('Aa CC Dd EE GG Pp Spsp') + expect(toDisplayString(fromJSON(toJSON(g)))).toBe('Aa CC Dd EE GG Pp Spsp') }) it('parses multi-char C-series alleles via maximal munch', () => { @@ -72,8 +72,8 @@ describe('Genotype serialization', () => { expect(g.P).toEqual(['P', 'p']) }) - it('wild type is AA CC DD EE GG PP spsp rere', () => { - expect(toDisplayString(wildType())).toBe('AA CC DD EE GG PP spsp rere') + it('wild type is AA CC DD EE GG PP spsp (rere omitted)', () => { + expect(toDisplayString(wildType())).toBe('AA CC DD EE GG PP spsp') }) }) @@ -102,7 +102,7 @@ describe('Worked example from research report', () => { expect(result.offspring).toHaveLength(1) const only = result.offspring[0] - expect(only.genotype).toBe('Aa CC DD EE GG Pp spsp rere') + expect(only.genotype).toBe('Aa CC DD EE GG Pp spsp') expect(only.probability.text).toBe('1') expect(result.warnings).toHaveLength(0) }) @@ -223,8 +223,9 @@ describe('Farbschlag catalog', () => { expect(CATALOG[0]).toMatchObject({ name: 'REW', sortOrder: 0 }) // Every row has a non-empty canonical genotype display string and unique name. // GEN-3h: bracket notation (e[f], c[chm], c[h]) allowed in tokens. + // GEN-4d: rere omitted from display -> 7 tokens (no Rex, no Sls), 8 (Rex or Sls), 9 (both). expect(new Set(CATALOG.map((c) => c.name)).size).toBe(CATALOG.length) - expect(CATALOG.every((c) => /^[A-Za-z[\]?-]+( [A-Za-z[\]?-]+){7}$/.test(c.canonicalGenotype))).toBe(true) + expect(CATALOG.every((c) => /^[A-Za-z[\]?-]+( [A-Za-z[\]?-]+){6,8}$/.test(c.canonicalGenotype))).toBe(true) }) it('genotypeToFarbschlag (DATA-1 denormalization contract) returns the plain name', () => { @@ -285,7 +286,7 @@ describe('GEN-3a: Uw=G alias', () => { it('always RENDERS G, never Uw (breeder preference)', () => { // Uw/uw is an input/import alias only; output must echo G/g. expect(toDisplayString(fromDisplayString('AA CC DD EE Uwuw PP spsp rere'))).toBe( - 'AA CC DD EE Gg PP spsp rere', + 'AA CC DD EE Gg PP spsp', ) expect(toDisplayString(fromDisplayString('AA CC DD EE uwuw PP spsp rere'))).not.toContain('uw') }) @@ -297,10 +298,16 @@ describe('GEN-3a: second spotting locus Sls (WP)', () => { expect(fromDisplayString('AA CC DD EE GG PP spsp rere WP').Sls).toEqual(['Sl', 'sl']) }) - it('toDisplayString omits wild-type Sls but shows Slsl', () => { - expect(toDisplayString(wildType())).toBe('AA CC DD EE GG PP spsp rere') + it('toDisplayString omits wild-type Sls and Re, shows Slsl/Rere when non-wildtype', () => { + // GEN-4d: Re (rere) omitted at wildtype, like Sls. + expect(toDisplayString(wildType())).toBe('AA CC DD EE GG PP spsp') + // Rex het → Rere shown + expect(toDisplayString(fromDisplayString('AA CC DD EE GG PP spsp Rere'))).toBe( + 'AA CC DD EE GG PP spsp Rere', + ) + // WP → Slsl shown, rere still omitted expect(toDisplayString(fromDisplayString('AA CC DD EE GG PP spsp rere WP'))).toBe( - 'AA CC DD EE GG PP spsp rere Slsl', + 'AA CC DD EE GG PP spsp Slsl', ) }) @@ -330,7 +337,7 @@ describe('GEN-3a: second spotting locus Sls (WP)', () => { describe('GEN-3a: flag/metadata tokens tolerated', () => { it('dea/taub/Dea/DP/WFNZ/RV/GV do not break parsing (stripped)', () => { const g = fromDisplayString('AA CC DD EE GG PP spsp rere dea WFNZ DP RV GV') - expect(toDisplayString(g)).toBe('AA CC DD EE GG PP spsp rere') + expect(toDisplayString(g)).toBe('AA CC DD EE GG PP spsp') }) it('extractGenotypeFlags reads deafness + tags', () => { @@ -363,11 +370,11 @@ describe("GEN-3c: unknown allele displays as '-' (stored as '?')", () => { it("accepts '-' input, stores '?', displays '-'", () => { const g = fromDisplayString('Aa C- DD EE GG Pp spsp rere') expect(g.C).toEqual(['C', '?']) // stored internal contract stays '?' - expect(toDisplayString(g)).toBe('Aa C- DD EE GG Pp spsp rere') // displayed as '-' + expect(toDisplayString(g)).toBe('Aa C- DD EE GG Pp spsp') // displayed as '-' }) it("'?' and '-' inputs are equivalent", () => { expect(toDisplayString(fromDisplayString('Aa C? DD EE GG Pp spsp rere'))).toBe( - 'Aa C- DD EE GG Pp spsp rere', + 'Aa C- DD EE GG Pp spsp', ) }) }) @@ -590,15 +597,15 @@ describe('GEN-3h: breeder bracket-notation display + E-locus e-before-ef order', it('ef displays as e[f], cchm as c[chm], ch as c[h]', () => { // Fuchsschimmel: E=[ef,ef] hom expect(toDisplayString(fromDisplayString('AA CC DD efef GG PP spsp rere'))).toBe( - 'AA CC DD e[f]e[f] GG PP spsp rere', + 'AA CC DD e[f]e[f] GG PP spsp', ) // C-locus het: cchm + ch expect(toDisplayString(fromDisplayString('aa cchmch DD EE GG PP spsp rere'))).toBe( - 'aa c[chm]c[h] DD EE GG PP spsp rere', + 'aa c[chm]c[h] DD EE GG PP spsp', ) // C-locus hom cchm expect(toDisplayString(fromDisplayString('aa cchmcchm DD EE GG PP spsp rere'))).toBe( - 'aa c[chm]c[chm] DD EE GG PP spsp rere', + 'aa c[chm]c[chm] DD EE GG PP spsp', ) }) @@ -608,24 +615,24 @@ describe('GEN-3h: breeder bracket-notation display + E-locus e-before-ef order', // Display must swap to [e, ef] per breeder convention. const g = fromDisplayString('aa CC DD eef Gg Pp spsp rere') expect(g.E).toEqual(['ef', 'e']) // storage order unchanged - expect(toDisplayString(g)).toBe('aa CC DD ee[f] Gg Pp spsp rere') + expect(toDisplayString(g)).toBe('aa CC DD ee[f] Gg Pp spsp') }) it('E+e stays Ee (E dominant over e, no swap needed)', () => { expect(toDisplayString(fromDisplayString('aa CC DD Ee GG PP spsp rere'))).toBe( - 'aa CC DD Ee GG PP spsp rere', + 'aa CC DD Ee GG PP spsp', ) }) it('E+ef displays Ee[f] (E dominant stays first, ef renders as e[f])', () => { expect(toDisplayString(fromDisplayString('aa CC DD Eef GG PP spsp rere'))).toBe( - 'aa CC DD Ee[f] GG PP spsp rere', + 'aa CC DD Ee[f] GG PP spsp', ) }) // ── Julian oracle fixtures (HUMANQUESTION D3/D4) ───────────────────── - it('Tier C: oracle display string round-trips exactly (aa C- D- ee[f] Gg Pp spsp rere)', () => { - const display = 'aa C- D- ee[f] Gg Pp spsp rere' + it('Tier C: oracle display string round-trips exactly (aa C- D- ee[f] Gg Pp spsp)', () => { + const display = 'aa C- D- ee[f] Gg Pp spsp' const g = fromDisplayString(display) expect(g.E).toEqual(['ef', 'e']) expect(g.C).toEqual(['C', '?']) @@ -633,16 +640,16 @@ describe('GEN-3h: breeder bracket-notation display + E-locus e-before-ef order', // Farbschlag scope is outside GEN-3h; god confirmed colour is correct as-is. }) - it('Zuleika oracle: aa c[chm]c[h] DD Ee Gg PP spsp rere', () => { - const display = 'aa c[chm]c[h] DD Ee Gg PP spsp rere' + it('Zuleika oracle: aa c[chm]c[h] DD Ee Gg PP spsp', () => { + const display = 'aa c[chm]c[h] DD Ee Gg PP spsp' const g = fromDisplayString(display) expect(g.C).toEqual(['cchm', 'ch']) expect(g.E).toEqual(['E', 'e']) expect(toDisplayString(g)).toBe(display) }) - it('Milka oracle: aa Cc[h] dd EE Gg P- Spsp rere', () => { - const display = 'aa Cc[h] dd EE Gg P- Spsp rere' + it('Milka oracle: aa Cc[h] dd EE Gg P- Spsp', () => { + const display = 'aa Cc[h] dd EE Gg P- Spsp' const g = fromDisplayString(display) expect(g.C).toEqual(['C', 'ch']) expect(g.D).toEqual(['d', 'd']) @@ -665,7 +672,7 @@ describe('GEN-3h: breeder bracket-notation display + E-locus e-before-ef order', it('e[-] standalone: parses as [e,?], displays e-', () => { const g = fromDisplayString('aa CC DD e[-] GG PP spsp rere') expect(g.E).toEqual(['e', '?']) - expect(toDisplayString(g)).toBe('aa CC DD e- GG PP spsp rere') + expect(toDisplayString(g)).toBe('aa CC DD e- GG PP spsp') }) it('CR-1a: Silvain oracle ee[-] parses without crash → [e,?], displays e-', () => { @@ -677,6 +684,6 @@ describe('GEN-3h: breeder bracket-notation display + E-locus e-before-ef order', expect(g.C).toEqual(['cchm', 'cchm']) expect(g.D).toEqual(['D', 'd']) expect(g.Sp).toEqual(['Sp', 'sp']) - expect(toDisplayString(g)).toBe('aa c[chm]c[chm] Dd e- Gg Pp Spsp rere') + expect(toDisplayString(g)).toBe('aa c[chm]c[chm] Dd e- Gg Pp Spsp') }) }) diff --git a/gerbil-manager-web/src/genetics/colorVarietySeed.generated.json b/gerbil-manager-web/src/genetics/colorVarietySeed.generated.json index eba4707..cf58486 100644 --- a/gerbil-manager-web/src/genetics/colorVarietySeed.generated.json +++ b/gerbil-manager-web/src/genetics/colorVarietySeed.generated.json @@ -2,425 +2,425 @@ { "name": "REW", "english": "Pink Eyed White", - "canonicalGenotype": "AA c[h]c[h] DD EE GG pp spsp rere", + "canonicalGenotype": "AA c[h]c[h] DD EE GG pp spsp", "sortOrder": 0, "image": "rotaugen-weiss-pew-d-sep-e-sep.jpg" }, { "name": "Hermelin", "english": "Dark Tailed White", - "canonicalGenotype": "aa c[h]c[h] DD EE GG PP spsp rere", + "canonicalGenotype": "aa c[h]c[h] DD EE GG PP spsp", "sortOrder": 1, "image": "hermelin.jpeg" }, { "name": "Himalaya", "english": "Himalayan", - "canonicalGenotype": "AA c[h]c[h] DD EE GG PP spsp rere", + "canonicalGenotype": "AA c[h]c[h] DD EE GG PP spsp", "sortOrder": 2, "image": "himalaya.jpg" }, { "name": "Zobel", "english": "Sable", - "canonicalGenotype": "aa c[chm]c[chm] DD EE gg PP spsp rere", + "canonicalGenotype": "aa c[chm]c[chm] DD EE gg PP spsp", "sortOrder": 3, "image": "zobel.jpeg" }, { "name": "Rotaugenschimmel", "english": "Red-Eyed Roan", - "canonicalGenotype": "AA CC DD e[f]e[f] GG pp spsp rere", + "canonicalGenotype": "AA CC DD e[f]e[f] GG pp spsp", "sortOrder": 4, "image": "rotaugen-schimmel.jpg" }, { "name": "Agouti", "english": "Golden Agouti", - "canonicalGenotype": "AA CC DD EE GG PP spsp rere", + "canonicalGenotype": "AA CC DD EE GG PP spsp", "sortOrder": 5, "image": "agouti-mit-erklaerung-der-genloci.JPG" }, { "name": "Schwarz", "english": "Black", - "canonicalGenotype": "aa CC DD EE GG PP spsp rere", + "canonicalGenotype": "aa CC DD EE GG PP spsp", "sortOrder": 6, "image": "schwarz.jpg" }, { "name": "Silberagouti", "english": "Grey Agouti", - "canonicalGenotype": "AA CC DD EE gg PP spsp rere", + "canonicalGenotype": "AA CC DD EE gg PP spsp", "sortOrder": 7, "image": "silberagouti.jpg" }, { "name": "Anthrazit", "english": "Slate", - "canonicalGenotype": "aa CC DD EE gg PP spsp rere", + "canonicalGenotype": "aa CC DD EE gg PP spsp", "sortOrder": 8, "image": "anthrazit.jpg" }, { "name": "Algierfuchs", "english": "Dark-Eyed Honey", - "canonicalGenotype": "AA CC DD ee GG PP spsp rere", + "canonicalGenotype": "AA CC DD ee GG PP spsp", "sortOrder": 9, "image": "algierfuchs.jpg" }, { "name": "Blau", "english": "Blue", - "canonicalGenotype": "aa CC dd EE GG PP spsp rere", + "canonicalGenotype": "aa CC dd EE GG PP spsp", "sortOrder": 10, "image": "blau-schwarz-dd.JPG" }, { "name": "Gold", "english": "Argente Golden", - "canonicalGenotype": "AA CC DD EE GG pp spsp rere", + "canonicalGenotype": "AA CC DD EE GG pp spsp", "sortOrder": 11, "image": "gold.jpg" }, { "name": "Platin", "english": "Lilac", - "canonicalGenotype": "aa CC DD EE GG pp spsp rere", + "canonicalGenotype": "aa CC DD EE GG pp spsp", "sortOrder": 12, "image": "platin.JPG" }, { "name": "Goldfuchs", "english": "Yellow Fox", - "canonicalGenotype": "AA CC DD ee GG pp spsp rere", + "canonicalGenotype": "AA CC DD ee GG pp spsp", "sortOrder": 13, "image": "goldfuchs.jpg" }, { "name": "Rotfuchs", "english": "Argente Nutmeg", - "canonicalGenotype": "aa CC DD ee GG pp spsp rere", + "canonicalGenotype": "aa CC DD ee GG pp spsp", "sortOrder": 14, "image": "rotfuchs.JPG" }, { "name": "Dilute Gold", "english": "dd Argente Golden", - "canonicalGenotype": "AA CC dd EE GG pp spsp rere", + "canonicalGenotype": "AA CC dd EE GG pp spsp", "sortOrder": 15, "image": "gold-dd.jpg" }, { "name": "Dilute Platin", "english": "dd Lilac", - "canonicalGenotype": "aa CC dd EE GG pp spsp rere", + "canonicalGenotype": "aa CC dd EE GG pp spsp", "sortOrder": 16, "image": "platin-dd.jpg" }, { "name": "Altweiss (REW)", - "canonicalGenotype": "aa CC DD EE gg pp spsp rere", + "canonicalGenotype": "aa CC DD EE gg pp spsp", "sortOrder": 17, "image": "altweiss-rew.jpeg" }, { "name": "Apricot (Blassfuchs)", - "canonicalGenotype": "AA CC DD ee gg pp spsp rere", + "canonicalGenotype": "AA CC DD ee gg pp spsp", "sortOrder": 18, "image": "apricot-blassfuchs.jpg" }, { "name": "Blaufuchs", - "canonicalGenotype": "aa CC DD ee gg PP spsp rere", + "canonicalGenotype": "aa CC DD ee gg PP spsp", "sortOrder": 19, "image": "blaufuchs.jpg" }, { "name": "C-Separator", - "canonicalGenotype": "aa CC DD ee gg pp spsp rere", + "canonicalGenotype": "aa CC DD ee gg pp spsp", "sortOrder": 20, "image": "c-separator.jpg" }, { "name": "Elfenbein", - "canonicalGenotype": "AA CC DD EE gg pp spsp rere", + "canonicalGenotype": "AA CC DD EE gg pp spsp", "sortOrder": 21, "image": "elfenbein.jpg" }, { "name": "Kohlfuchs", - "canonicalGenotype": "aa CC DD ee GG PP spsp rere", + "canonicalGenotype": "aa CC DD ee GG PP spsp", "sortOrder": 22, "image": "kohlfuchs.jpg" }, { "name": "Polarfuchs", - "canonicalGenotype": "AA CC DD ee gg PP spsp rere", + "canonicalGenotype": "AA CC DD ee gg PP spsp", "sortOrder": 23, "image": "polarfuchs.jpg" }, { "name": "Saphir", - "canonicalGenotype": "aa CC DD EE GG pp spsp rere", + "canonicalGenotype": "aa CC DD EE GG pp spsp", "sortOrder": 24, "image": "saphir.jpg" }, { "name": "Orangeschimmel", - "canonicalGenotype": "AA CC DD e[f]e[f] GG PP spsp rere", + "canonicalGenotype": "AA CC DD e[f]e[f] GG PP spsp", "sortOrder": 25, "image": "schimmel-orangeschimmel.jpg" }, { "name": "Topas", - "canonicalGenotype": "AA CC DD EE GG pp spsp rere", + "canonicalGenotype": "AA CC DD EE GG pp spsp", "sortOrder": 26, "image": "topas.jpg" }, { "name": "Platin-Hell", - "canonicalGenotype": "aa CC DD EE GG pp spsp rere", + "canonicalGenotype": "aa CC DD EE GG pp spsp", "sortOrder": 27, "image": "platin-hell.jpg" }, { "name": "Dilute Agouti", - "canonicalGenotype": "AA CC dd EE GG PP spsp rere", + "canonicalGenotype": "AA CC dd EE GG PP spsp", "sortOrder": 28, "image": "agouti-dd.jpg" }, { "name": "Dilute Silberagouti", - "canonicalGenotype": "AA CC dd EE gg PP spsp rere", + "canonicalGenotype": "AA CC dd EE gg PP spsp", "sortOrder": 29, "image": "silberagouti-dd.jpg" }, { "name": "Dilute Kohlfuchs", - "canonicalGenotype": "aa CC dd ee GG PP spsp rere", + "canonicalGenotype": "aa CC dd ee GG PP spsp", "sortOrder": 30, "image": "kohlfuchs-dd.jpg" }, { "name": "Dilute Anthrazit", - "canonicalGenotype": "aa CC dd EE gg PP spsp rere", + "canonicalGenotype": "aa CC dd EE gg PP spsp", "sortOrder": 31, "image": "anthrazit-dd.jpg" }, { "name": "Dilute Algierfuchs", - "canonicalGenotype": "AA CC dd ee GG PP spsp rere", + "canonicalGenotype": "AA CC dd ee GG PP spsp", "sortOrder": 32 }, { "name": "Dilute Goldfuchs", - "canonicalGenotype": "AA CC dd ee GG pp spsp rere", + "canonicalGenotype": "AA CC dd ee GG pp spsp", "sortOrder": 33 }, { "name": "Dilute Rotfuchs", - "canonicalGenotype": "aa CC dd ee GG pp spsp rere", + "canonicalGenotype": "aa CC dd ee GG pp spsp", "sortOrder": 34 }, { "name": "Dilute Polarfuchs", - "canonicalGenotype": "AA CC dd ee gg PP spsp rere", + "canonicalGenotype": "AA CC dd ee gg PP spsp", "sortOrder": 35 }, { "name": "Silberschimmel", - "canonicalGenotype": "AA CC DD e[f]e[f] gg PP spsp rere", + "canonicalGenotype": "AA CC DD e[f]e[f] gg PP spsp", "sortOrder": 36, "image": "silberschimmel.jpg" }, { "name": "Polarfuchsschimmel", - "canonicalGenotype": "AA CC DD e[f]e[f] gg PP spsp rere", + "canonicalGenotype": "AA CC DD e[f]e[f] gg PP spsp", "sortOrder": 37, "image": "polarfuchsschimmel.jpg" }, { "name": "Algierfuchsschimmel", - "canonicalGenotype": "AA CC DD e[f]e[f] GG PP spsp rere", + "canonicalGenotype": "AA CC DD e[f]e[f] GG PP spsp", "sortOrder": 38, "image": "algierfuchsschimmel.jpg" }, { "name": "Kohlfuchsschimmel", - "canonicalGenotype": "aa CC DD e[f]e[f] GG PP spsp rere", + "canonicalGenotype": "aa CC DD e[f]e[f] GG PP spsp", "sortOrder": 39, "image": "kohlfuchsschimmel.jpg" }, { "name": "Blaufuchsschimmel", - "canonicalGenotype": "aa CC DD e[f]e[f] gg PP spsp rere", + "canonicalGenotype": "aa CC DD e[f]e[f] gg PP spsp", "sortOrder": 40, "image": "blaufuchsschimmel.jpg" }, { "name": "Kohlfuchs, hell", - "canonicalGenotype": "aa CC DD ee GG PP spsp rere", + "canonicalGenotype": "aa CC DD ee GG PP spsp", "sortOrder": 41, "image": "kohlfuchs-hell.jpg" }, { "name": "Goldfuchs, hell", - "canonicalGenotype": "AA CC DD ee GG pp spsp rere", + "canonicalGenotype": "AA CC DD ee GG pp spsp", "sortOrder": 42, "image": "goldfuchs-hell.jpg" }, { "name": "Goldfuchsschimmel", - "canonicalGenotype": "AA CC DD e[f]e[f] GG pp spsp rere", + "canonicalGenotype": "AA CC DD e[f]e[f] GG pp spsp", "sortOrder": 43, "image": "goldfuchsschimmel.jpg" }, { "name": "Gold-Hell", - "canonicalGenotype": "AA CC DD EE GG pp spsp rere", + "canonicalGenotype": "AA CC DD EE GG pp spsp", "sortOrder": 44, "image": "gold-hell.jpg" }, { "name": "Blaufuchs, hell", - "canonicalGenotype": "aa CC DD ee gg PP spsp rere", + "canonicalGenotype": "aa CC DD ee gg PP spsp", "sortOrder": 45, "image": "blaufuchs-hell.jpeg" }, { "name": "Rotfuchsschimmel", - "canonicalGenotype": "aa CC DD e[f]e[f] GG pp spsp rere", + "canonicalGenotype": "aa CC DD e[f]e[f] GG pp spsp", "sortOrder": 46, "image": "rotfuchsschimmel.jpg" }, { "name": "Polarfuchs, hell", - "canonicalGenotype": "AA CC DD ee gg PP spsp rere", + "canonicalGenotype": "AA CC DD ee gg PP spsp", "sortOrder": 47, "image": "polarfuchs-hell.jpeg" }, { "name": "Kohlfuchsschimmel, hell", - "canonicalGenotype": "aa CC DD e[f]e[f] GG PP spsp rere", + "canonicalGenotype": "aa CC DD e[f]e[f] GG PP spsp", "sortOrder": 48, "image": "kohlfuchsschimmel-hell.jpg" }, { "name": "Rotfuchs, hell", - "canonicalGenotype": "aa CC DD ee GG pp spsp rere", + "canonicalGenotype": "aa CC DD ee GG pp spsp", "sortOrder": 49, "image": "rotfuchs-hell.jpg" }, { "name": "Kohlfuchs-Hell", - "canonicalGenotype": "aa CC DD ee GG PP spsp rere", + "canonicalGenotype": "aa CC DD ee GG PP spsp", "sortOrder": 50, "image": "kohlfuchs-hell-2.jpg" }, { "name": "Algierfuchs, hell", - "canonicalGenotype": "AA CC DD ee GG PP spsp rere", + "canonicalGenotype": "AA CC DD ee GG PP spsp", "sortOrder": 51, "image": "algierfuchs-hell.JPG" }, { "name": "Dilute Topas", - "canonicalGenotype": "AA CC dd EE GG pp spsp rere", + "canonicalGenotype": "AA CC dd EE GG pp spsp", "sortOrder": 52, "image": "topas-dd.jpg" }, { "name": "Dilute Blaufuchs", - "canonicalGenotype": "aa CC dd ee gg pp spsp rere", + "canonicalGenotype": "aa CC dd ee gg pp spsp", "sortOrder": 53, "image": "blaufuchs-dd.jpg" }, { "name": "Marder", - "canonicalGenotype": "aa c[chm]c[chm] DD EE GG PP spsp rere", + "canonicalGenotype": "aa c[chm]c[chm] DD EE GG PP spsp", "sortOrder": 54, "image": "marder.JPG" }, { "name": "Siam", - "canonicalGenotype": "aa c[chm]c[h] DD EE GG PP spsp rere", + "canonicalGenotype": "aa c[chm]c[h] DD EE GG PP spsp", "sortOrder": 55, "image": "siam-marder-hell.JPG" }, { "name": "Zobel-Hell", - "canonicalGenotype": "aa c[chm]c[h] DD EE gg PP spsp rere", + "canonicalGenotype": "aa c[chm]c[h] DD EE gg PP spsp", "sortOrder": 56, "image": "zobel-hell.jpg" }, { "name": "CP-Agouti", - "canonicalGenotype": "AA c[chm]c[chm] DD EE GG PP spsp rere", + "canonicalGenotype": "AA c[chm]c[chm] DD EE GG PP spsp", "sortOrder": 57, "image": "agouti-cp.jpg" }, { "name": "CP-Agouti-Hell", - "canonicalGenotype": "AA c[chm]c[h] DD EE GG PP spsp rere", + "canonicalGenotype": "AA c[chm]c[h] DD EE GG PP spsp", "sortOrder": 58 }, { "name": "CP-Silberagouti", - "canonicalGenotype": "AA c[chm]c[chm] DD EE gg PP spsp rere", + "canonicalGenotype": "AA c[chm]c[chm] DD EE gg PP spsp", "sortOrder": 59, "image": "silberagouti-cp.JPG" }, { "name": "CP-Silberagouti-Hell", - "canonicalGenotype": "AA c[chm]c[h] DD EE gg PP spsp rere", + "canonicalGenotype": "AA c[chm]c[h] DD EE gg PP spsp", "sortOrder": 60 }, { "name": "CP-Algierfuchs", - "canonicalGenotype": "AA c[chm]c[chm] DD ee GG PP spsp rere", + "canonicalGenotype": "AA c[chm]c[chm] DD ee GG PP spsp", "sortOrder": 61, "image": "algierfuchs-cp.jpg" }, { "name": "CP-Algierfuchs-Hell", - "canonicalGenotype": "AA c[chm]c[h] DD ee GG PP spsp rere", + "canonicalGenotype": "AA c[chm]c[h] DD ee GG PP spsp", "sortOrder": 62 }, { "name": "CP-Polarfuchs", - "canonicalGenotype": "AA c[chm]c[chm] DD ee gg PP spsp rere", + "canonicalGenotype": "AA c[chm]c[chm] DD ee gg PP spsp", "sortOrder": 63, "image": "polarfuchs-cp.jpg" }, { "name": "CP-Polarfuchs-Hell", - "canonicalGenotype": "AA c[chm]c[h] DD ee gg PP spsp rere", + "canonicalGenotype": "AA c[chm]c[h] DD ee gg PP spsp", "sortOrder": 64 }, { "name": "CP-Fuchs", - "canonicalGenotype": "AA c[chm]c[chm] dd ee GG PP spsp rere", + "canonicalGenotype": "AA c[chm]c[chm] dd ee GG PP spsp", "sortOrder": 65 }, { "name": "CP-Fuchs-Hell", - "canonicalGenotype": "AA c[chm]c[h] dd ee GG PP spsp rere", + "canonicalGenotype": "AA c[chm]c[h] dd ee GG PP spsp", "sortOrder": 66 }, { "name": "CP-Blaufuchs", - "canonicalGenotype": "AA c[chm]c[chm] dd ee gg PP spsp rere", + "canonicalGenotype": "AA c[chm]c[chm] dd ee gg PP spsp", "sortOrder": 67 }, { "name": "CP-Orangeschimmel", - "canonicalGenotype": "AA c[chm]c[chm] DD e[f]e[f] GG PP spsp rere", + "canonicalGenotype": "AA c[chm]c[chm] DD e[f]e[f] GG PP spsp", "sortOrder": 68 }, { "name": "CP-Orangeschimmel-Hell", - "canonicalGenotype": "AA c[chm]c[h] DD e[f]e[f] GG PP spsp rere", + "canonicalGenotype": "AA c[chm]c[h] DD e[f]e[f] GG PP spsp", "sortOrder": 69 } ] diff --git a/gerbil-manager-web/src/genetics/genotype.ts b/gerbil-manager-web/src/genetics/genotype.ts index 5a54606..0d5df6d 100644 --- a/gerbil-manager-web/src/genetics/genotype.ts +++ b/gerbil-manager-web/src/genetics/genotype.ts @@ -101,18 +101,21 @@ function displayPair(locus: LocusKey, pair: AllelePair): AllelePair { } /** - * Compact display string, e.g. "Aa CC Dd EE GG Pp spsp rere". - * The Sls locus is OMITTED when wild-type (sl/sl) so legacy 8-locus strings and - * the colour catalog stay byte-identical; it only appears for WP/Sls carriers - * (e.g. "… spsp rere Slsl"). Round-trips: a missing Sls re-parses to sl/sl. - * GEN-3c: unknown alleles are STORED as '?' but DISPLAYED as '-' (breeder - * convention) — e.g. ['C','?'] renders "C-". - * GEN-3h: sub-alleles use breeder bracket notation (e[f], c[chm], c[h]); - * E-locus display order is E > e > e[f] (e before e[f] in het pairs). + * Compact display string, e.g. "Aa CC Dd EE GG Pp spsp". + * The Re locus is OMITTED when wild-type (re/re) — Julian: only show Rex when a + * Rex allele is present (Rere/ReRe). Matches the 7-locus notation used by the + * breeder. The Sls locus is likewise omitted when wild-type (sl/sl) so legacy + * 8-locus strings and the colour catalog stay byte-identical; it only appears for + * WP/Sls carriers (e.g. "… spsp Slsl"). Round-trips: missing Re → re/re; missing + * Sls → sl/sl. GEN-3c: unknown alleles stored as '?' displayed as '-' (breeder + * convention). GEN-3h: bracket notation (e[f], c[chm], c[h]); E-locus display + * order E > e > e[f]. */ export function toDisplayString(g: Genotype): string { return LOCUS_ORDER.filter( - (locus) => locus !== 'Sls' || !(g.Sls[0] === 'sl' && g.Sls[1] === 'sl'), + (locus) => + !(locus === 'Re' && g.Re[0] === 're' && g.Re[1] === 're') && + !(locus === 'Sls' && g.Sls[0] === 'sl' && g.Sls[1] === 'sl'), ) .map((locus) => { const [a, b] = displayPair(locus, g[locus]) diff --git a/gerbil-manager-web/src/pages/StammbaumPage.tsx b/gerbil-manager-web/src/pages/StammbaumPage.tsx index 8f93b01..5e6a703 100644 --- a/gerbil-manager-web/src/pages/StammbaumPage.tsx +++ b/gerbil-manager-web/src/pages/StammbaumPage.tsx @@ -27,7 +27,7 @@ import { getInbreedingCoefficient } from '../api/pedigree' import type { Gender, Gerbil } from '../api/types' import { useApi } from '../hooks/useApi' import { formatDate, genderLabel } from '../format/labels' -import { UNKNOWN_FARBSCHLAG, fromDisplayString, genotypeToFarbschlag } from '../genetics' +import { UNKNOWN_FARBSCHLAG, fromDisplayString, genotypeToFarbschlag, toDisplayString } from '../genetics' import { DEFAULT_GENERATIONS, ancestorsAt, @@ -512,7 +512,11 @@ function PrintCell({ )} {farbschlag &&
{farbschlag}
} - {g.genotype && gen <= 2 &&
{g.genotype}
} + {g.genotype && gen <= 2 && ( +
+ {toDisplayString(fromDisplayString(g.genotype))} +
+ )} ) }