fix(akte): falscher Hinweis „Weicht vom eingetragenen Farbschlag ab"
All checks were successful
CI / Backend Tests (.NET) (push) Successful in 1m16s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m38s
CI / Docker Build & Push (push) Successful in 1m32s
CI / Deploy auf TrueNAS (Custom App) (push) Successful in 9s

Beim Prod-Nachtest aufgefallen: Bei Schecken meldete die Akte eine Abweichung,
obwohl eingetragener und errechneter Farbschlag identisch angezeigt wurden
(JackJack: „Saphir Schecke" vs. „Saphir Schecke"). Zwei Ursachen:

- Der Vergleich schnitt „Schecke"/„Rex" nur auf der ERRECHNETEN Seite ab, nicht
  auf der eingetragenen (die den Zusatz über formatVarietyName trägt). Jetzt
  werden beide Seiten gleich normalisiert.
- formatVarietyName hängte den Modifier auch an, wenn der Katalogname ihn schon
  trägt („Schwarz Schecke" → „Schwarz Schecke Schecke").

Tests: 3 vitest-Fälle zu formatVarietyName, e2e SCHECKE-MISMATCH mit Gegenprobe
(Mock-Tier mit echter Abweichung zeigt den Hinweis weiter).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-20 00:22:59 +02:00
parent 748ce4733f
commit 45c3f87f01
5 changed files with 58 additions and 4 deletions

View File

@@ -168,6 +168,10 @@ export function seedDb(): MockDb {
gerbil('balu', 'Balu', 'male', '2021-04-20', 'w-balu', 'cv-agouti'), gerbil('balu', 'Balu', 'male', '2021-04-20', 'w-balu', 'cv-agouti'),
gerbil('maja', 'Maja', 'female', '2021-06-11', null, 'cv-schwarz-schecke', 'aa CC DD EE GG PP Spsp rere'), gerbil('maja', 'Maja', 'female', '2021-06-11', null, 'cv-schwarz-schecke', 'aa CC DD EE GG PP Spsp rere'),
gerbil('karlsson', 'Karlsson', 'male', '2022-02-02', 'w-karlsson', 'cv-blau'), gerbil('karlsson', 'Karlsson', 'male', '2022-02-02', 'w-karlsson', 'cv-blau'),
// FARBSCHLAG-MISMATCH: eingetragen „Schwarz Schecke", Gencode ergibt Agouti Schecke
// → der Hinweis „Weicht vom eingetragenen Farbschlag ab" MUSS hier erscheinen.
gerbil('mismatch-maus', 'Mismatch Maus', 'female', '2023-02-02', null, 'cv-schwarz-schecke',
'AA CC DD EE GG PP Spsp rere'),
gerbil('smilla', 'Smilla', 'female', '2022-09-30', null, 'cv-himalaya'), gerbil('smilla', 'Smilla', 'female', '2022-09-30', null, 'cv-himalaya'),
gerbil('anton', 'Anton', 'male', '2019-07-07', 'w-anton', 'cv-agouti'), gerbil('anton', 'Anton', 'male', '2019-07-07', 'w-anton', 'cv-agouti'),
gerbil('greta', 'Greta', 'female', '2019-05-23', null, 'cv-schwarz'), gerbil('greta', 'Greta', 'female', '2019-05-23', null, 'cv-schwarz'),

View File

@@ -241,6 +241,23 @@ test('Detailseite: Abgabedatum steht direkt unter dem Abnehmer, auch bei Verstor
await expect(goHomeRow.locator('.ak-kv-value')).toHaveText('15.01.2022') await expect(goHomeRow.locator('.ak-kv-value')).toHaveText('15.01.2022')
}) })
test('Detailseite: Farbschlag-Hinweis nur bei echter Abweichung (SCHECKE-MISMATCH)', async ({
page,
}) => {
skipUnlessMock()
// Maja: eingetragen „Schwarz Schecke", Gencode ergibt genau das → KEIN Hinweis.
// (Vorher wurde der Zusatz „Schecke" nur auf der errechneten Seite abgeschnitten,
// dadurch bekam jede Schecke faelschlich „Weicht vom eingetragenen Farbschlag ab".)
await page.goto('/rennmaeuse/maja')
const genetik = page.locator('.ak-card', { hasText: t.detail.genetics }).first()
await expect(genetik.getByText(t.detail.resolvedPrefix, { exact: false })).toBeVisible()
await expect(page.getByText(t.detail.farbschlagMismatch)).toBeHidden()
// Gegenprobe: bei echter Abweichung erscheint der Hinweis weiter.
await page.goto('/rennmaeuse/mismatch-maus')
await expect(page.getByText(t.detail.farbschlagMismatch)).toBeVisible()
})
/** QOL-ELTERN: Vater/Mutter im Abstammungs-Block des Tier-Formulars setzen. */ /** QOL-ELTERN: Vater/Mutter im Abstammungs-Block des Tier-Formulars setzen. */
async function pickParent(page: Page, legend: string, name: string) { async function pickParent(page: Page, legend: string, name: string) {
const group = page.getByRole('group', { name: legend }) const group = page.getByRole('group', { name: legend })

View File

@@ -24,6 +24,7 @@ import { combineLocus } from '../punnett'
import { LOCI, type LocusKey } from '../loci' import { LOCI, type LocusKey } from '../loci'
import { import {
farbschlagFor, farbschlagFor,
formatVarietyName,
genotypeToFarbschlag, genotypeToFarbschlag,
representativeGenotype, representativeGenotype,
BASE_COLORS, BASE_COLORS,
@@ -963,6 +964,22 @@ describe('GEN-6: C-Locus-Zygotie Platin / Saphir / Platin-Hell (bde4ec70 / f89e9
}) })
}) })
describe('formatVarietyName: Modifier nicht doppeln', () => {
it('haengt „Schecke" an einen Basisnamen an', () => {
expect(formatVarietyName('Saphir', 'aa Cc[chm] DD E- G- pp Spsp')).toBe('Saphir Schecke')
})
it('doppelt „Schecke" NICHT, wenn der Katalogname es schon traegt', () => {
// Sonst „Schwarz Schecke Schecke" — und der Akten-Vergleich „eingetragen vs. errechnet"
// meldete eine Abweichung, obwohl beides dasselbe ist.
expect(formatVarietyName('Schwarz Schecke', 'aa CC DD EE GG PP Spsp rere')).toBe('Schwarz Schecke')
})
it('ohne Gencode bleibt der Name unveraendert', () => {
expect(formatVarietyName('Saphir', null)).toBe('Saphir')
})
})
describe('Schreibfehler-Alias c[hm] == c[chm] (Ticket 2322c2a8, Jay *24.10.2021)', () => { describe('Schreibfehler-Alias c[hm] == c[chm] (Ticket 2322c2a8, Jay *24.10.2021)', () => {
it('c[hm] parst als c[chm] und ergibt Zobel, nicht Zobel-Hell', () => { it('c[hm] parst als c[chm] und ergibt Zobel, nicht Zobel-Hell', () => {
// Die Quell-Charts (Alberto Kids / Pukas Kids) schreiben „aa c[chm]c[hm] …" — // Die Quell-Charts (Alberto Kids / Pukas Kids) schreiben „aa c[chm]c[hm] …" —

View File

@@ -365,8 +365,13 @@ export function formatVarietyName(baseName: string, genotypeStr: string | null |
try { try {
const g = fromDisplayString(genotypeStr) const g = fromDisplayString(genotypeStr)
const modifiers: string[] = [] const modifiers: string[] = []
if (locusToken(g, 'Sp') === 'Sp') modifiers.push('Schecke') // Einen Modifier NICHT doppeln, wenn der Katalogname ihn schon traegt: Bestaende mit
if (locusToken(g, 'Re') === 'Re') modifiers.push('Rex') // Namen wie „Schwarz Schecke" ergaeben sonst „Schwarz Schecke Schecke" — und der
// Vergleich mit dem errechneten Farbschlag meldet dann eine Abweichung, die es nicht gibt.
const words = baseName.toLowerCase().split(/[\s-]+/)
const alreadyNamed = (modifier: string) => words.includes(modifier.toLowerCase())
if (locusToken(g, 'Sp') === 'Sp' && !alreadyNamed('Schecke')) modifiers.push('Schecke')
if (locusToken(g, 'Re') === 'Re' && !alreadyNamed('Rex')) modifiers.push('Rex')
return [baseName, ...modifiers].join(' ') return [baseName, ...modifiers].join(' ')
} catch { } catch {
return baseName return baseName

View File

@@ -55,6 +55,11 @@ function describeGenotype(genotype: string | null): { display: string; farbschla
} }
/** A labelled key/value row inside a card (semantic dt/dd, styled as a grid). */ /** A labelled key/value row inside a card (semantic dt/dd, styled as a grid). */
/** „Saphir Schecke" → „Saphir": Modifier-Zusaetze fuer den Namensvergleich abschneiden. */
function stripVarietyModifiers(name: string): string {
return name.replace(' Schecke', '').replace(' Rex', '').trim()
}
function Kv({ label, children }: { label: string; children: ReactNode }) { function Kv({ label, children }: { label: string; children: ReactNode }) {
return ( return (
<div className="ak-kv"> <div className="ak-kv">
@@ -594,10 +599,16 @@ export default function GerbilDetailPage() {
return ( return (
<> <>
{resolvedFarbschlag} {resolvedFarbschlag}
{/* Vergleich OHNE die Modifier-Zusaetze auf BEIDEN Seiten: der
eingetragene Name traegt „Schecke"/„Rex" bereits (formatVarietyName
haengt sie aus dem Gencode an), der errechnete ebenfalls. Vorher wurde
der Zusatz nur rechts entfernt → jede Schecke bekam faelschlich den
Hinweis „Weicht vom eingetragenen Farbschlag ab" (z. B. JackJack:
„Saphir Schecke" vs. „Saphir Schecke"). */}
{storedColorName && {storedColorName &&
resolvedFarbschlag !== de.genetics.unknownFarbschlag && resolvedFarbschlag !== de.genetics.unknownFarbschlag &&
storedColorName !== stripVarietyModifiers(storedColorName) !==
resolvedFarbschlag.replace(' Schecke', '').replace(' Rex', '') && ( stripVarietyModifiers(resolvedFarbschlag) && (
<small className="ak-mismatch"> {t.detail.farbschlagMismatch}</small> <small className="ak-mismatch"> {t.detail.farbschlagMismatch}</small>
)} )}
</> </>