feat(stammbaum): gleiches Geburtsdatum der Eltern als Geschwister-Indiz werten

Ein identisches Geburtsdatum bei Vater und Mutter ist ein starkes Indiz für eine
Vollgeschwister-Verpaarung — ein gemeinsamer Wurf ist praktisch der einzige
Grund dafür, und die Eltern-Würfe sind in den importierten Daten nicht immer
verknüpft (gleiche litterId fehlt dann).

Die Geschwister-Erkennung (Diagramm-Zusammenführung in build.ts + Akte-Chip in
GerbilDetailPage) greift jetzt bei gleicher litterId ODER gleichem, gesetztem
Geburtsdatum beider Eltern (distinkte Tiere vorausgesetzt).

Tests: gleiches DOB bei fehlender/verschiedener litterId → erkannt; unterschied-
liches DOB → nicht erkannt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 15:02:19 +02:00
parent 03ad9e6e81
commit 2868aad369
3 changed files with 43 additions and 9 deletions

View File

@@ -137,14 +137,16 @@ export default function GerbilDetailPage() {
const lookup = (map: Map<string, string>, key: string | null) => (key ? (map.get(key) ?? '—') : '—')
const storedColorName = g.colorVarietyId ? (colorName.get(g.colorVarietyId) ?? null) : null
// Geschwisterverpaarung: Vater und Mutter dieses Tiers stammen aus demselben
// Wurf (Vollgeschwister) — erkennbar an gemeinsamer litterId der Eltern.
// Geschwisterverpaarung: Vater und Mutter dieses Tiers sind Vollgeschwister —
// erkennbar an gemeinsamer litterId ODER gleichem Geburtsdatum (starkes Indiz:
// ein identisches Datum entsteht praktisch nur durch einen gemeinsamen Wurf,
// und die Eltern-Würfe sind in den Daten nicht immer verknüpft).
const isSiblingPairing =
!!father.data &&
!!mother.data &&
father.data.id !== mother.data.id &&
!!father.data.litterId &&
father.data.litterId === mother.data.litterId
((!!father.data.litterId && father.data.litterId === mother.data.litterId) ||
(!!father.data.dateOfBirth && father.data.dateOfBirth === mother.data.dateOfBirth))
const parentLink = (
pid: string | null,

View File

@@ -208,6 +208,34 @@ describe('buildPedigree', () => {
expect(animal(mutter.mother).gerbil.name).toBe('Oma')
})
it('erkennt Geschwister-Verpaarung am gleichen Geburtsdatum (litterId fehlt/verschieden)', async () => {
// Vater und Mutter haben verschiedene litterId, aber dasselbe Geburtsdatum
// (starkes Indiz: gemeinsamer Wurf, der in den Daten nicht verknüpft ist).
const source = makeSource(
[
makeGerbil('kind', 'Kind', 'w-kind'),
{ ...makeGerbil('vater', 'Vater', 'w-v'), dateOfBirth: '2018-09-22' },
{ ...makeGerbil('mutter', 'Mutter', 'w-m'), dateOfBirth: '2018-09-22' },
],
[makeLitter('w-kind', 'vater', 'mutter')],
)
const root = await buildPedigree(source, 'kind')
expect(animal(root!.mother).siblingPairing).toEqual({ siblingName: 'Vater', siblingPath: 'v' })
})
it('gleiches Geburtsdatum nur bei BEIDEN Eltern (unterschiedliches DOB → keine Verpaarung)', async () => {
const source = makeSource(
[
makeGerbil('kind', 'Kind', 'w-kind'),
{ ...makeGerbil('vater', 'Vater', 'w-v'), dateOfBirth: '2018-09-22' },
{ ...makeGerbil('mutter', 'Mutter', 'w-m'), dateOfBirth: '2019-05-15' },
],
[makeLitter('w-kind', 'vater', 'mutter')],
)
const root = await buildPedigree(source, 'kind')
expect(animal(root!.mother).siblingPairing).toBeUndefined()
})
it('verlangt für Geschwister-Verpaarung dieselbe litterId (Halbgeschwister zählen nicht)', async () => {
// Gemeinsamer Vater (Opa), aber verschiedene Würfe → keine Verpaarung-Marke.
const source = makeSource(

View File

@@ -71,16 +71,20 @@ async function buildAnimal(
parentNode(source, litter?.motherId, path + 'm', generationsLeft - 1, nextAncestors),
])
// Geschwister-Verpaarung: Stammen Vater und Mutter aus DEMSELBEN Wurf
// (gleiche litterId), sind sie Vollgeschwister. Die Vorfahren-Linie ist dann
// auf beiden Seiten identisch — wir markieren die Mutter, damit das Diagramm
// Geschwister-Verpaarung: Vater und Mutter sind Vollgeschwister, wenn sie aus
// DEMSELBEN Wurf stammen (gleiche litterId) ODER am selben Tag geboren sind
// (gleiches Geburtsdatum ist ein starkes Indiz — Würfe sind praktisch der
// einzige Grund für ein identisches Datum, und die Daten verknüpfen die
// Eltern-Würfe nicht immer). Wir markieren dann die Mutter, damit das Diagramm
// ihren Ast zu einem Verweis auf die Vaterlinie zusammenführt. Die echten
// Vorfahren bleiben im Modell erhalten (Druck-Ahnentafel nutzt sie weiter).
if (
father.kind === 'animal' &&
mother.kind === 'animal' &&
father.gerbil.litterId &&
father.gerbil.litterId === mother.gerbil.litterId
father.gerbil.id !== mother.gerbil.id &&
((father.gerbil.litterId && father.gerbil.litterId === mother.gerbil.litterId) ||
(father.gerbil.dateOfBirth != null &&
father.gerbil.dateOfBirth === mother.gerbil.dateOfBirth))
) {
const markedMother: AnimalNode = {
...mother,