Files
GerbilManager/gerbil-manager-web/e2e/mock-data.ts
Gulum 9c98c37d2a feat(rennmausakte): Datenherkunft/Nachverfolgung pro Rennmaus
Neuer „Datenherkunft"-Button in der Rennmausakte öffnet einen Dialog, der zeigt,
aus welchen Quellen der Eintrag erzeugt wurde: Quelldateien (Stammbäume +
Wurfchronik), Anzahl zusammengeführter Datensätze, Eltern-Herleitung
(Methode/Konfidenz) und Hinweise (z. B. „per manueller Entscheidung zugeordnet",
„Konflikt gelöst", „aus N Datensätzen zusammengeführt").

Import: build_provenance() in merge_and_resolve.py sammelt die Herkunft über alle
deduplizierten Datensätze und schreibt sie als Provenance-JSON je Tier in
resolved_import.json. Backend: Gerbil.Provenance (nullable text) + Migration
AddGerbilProvenance, gemappt im Ingest und im GerbilDto zurückgegeben. Frontend:
ProvenanceDialog + Typen + Strings.

Tests: Ingest-Round-trip (vorhanden/abwesend), e2e provenance.spec.ts.
dotnet(212)/vitest(129)/playwright/tsc/eslint grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:45:15 +02:00

341 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* QA-1: Seed-Daten für den Mock-Modus — geformt exakt nach dem DATA-2-Vertrag
* (camelCase-DTOs, Gridify-paged Listen). Datensatz angelehnt an Kellys
* FEAT-4-Verifikationsdaten (mehrgenerationiger Stammbaum inkl. gemeinsamem
* Vorfahren Anton), erweitert um Becken/Kontakte/Gesundheit/Gewicht für die
* FEAT-2/FEAT-6-Flüsse.
*/
import type { Contact, Enclosure, Gerbil, Litter } from '../src/api/types'
import type { InboxRequest } from '../src/api/requests'
export interface HealthRecordRow {
id: string
gerbilId: string
date: string
type: string
description: string
veterinarian: string | null
createdAt: string
}
export interface WeightRecordRow {
id: string
gerbilId: string
date: string
weightGrams: number
notes: string | null
}
/** WEB-0b: CMS-Block (data ist das typ-spezifische JSON-Objekt). */
export interface MockBlock {
id: string
order: number
type: string
data: Record<string, unknown>
}
/** WEB-0b: CMS-Seite der öffentlichen Webseite. */
export interface MockPage {
id: string
slug: string
title: string
seoDescription: string | null
status: 'Draft' | 'Published'
blocks: MockBlock[]
}
/** ABGABE: Abgabevertrag (mirrors SaleContract in src/api/contracts.ts). */
export interface MockContract {
id: string
contactId: string
price: number
handoverDate: string
contractDate: string
fileName: string
createdAt: string
gerbilIds: string[]
url: string
}
export interface MockDb {
gerbils: Gerbil[]
litters: Litter[]
enclosures: Enclosure[]
contacts: Contact[]
colorVarieties: { id: string; name: string; canonicalGenotype: string | null; sortOrder: number }[]
healthRecords: HealthRecordRow[]
weightRecords: WeightRecordRow[]
pages: MockPage[]
// INBOX-1: Anfragen-Posteingang. Die Flags steuern die Fehlerpfade des Mocks
// (Specs können sie pro Test umlegen): KI-Entwurf 503 / Sync+Send Mail-Fehler.
requests: InboxRequest[]
aiConfigured: boolean
mailConfigured: boolean
// ABGABE: Verträge + KI-Inserat-Flag
contracts: MockContract[]
saleAdConfigured: boolean
// FEAT-NAMEGEN: Namensvorschläge — false = 503 NamesKeyMissing simulieren
namesConfigured: boolean
// FEEDBACK: "Fehler melden" — gesammelte Berichte (POST /feedback)
feedback: Record<string, unknown>[]
}
function gerbil(
id: string,
name: string,
gender: 'male' | 'female',
dateOfBirth: string,
litterId: string | null,
colorVarietyId: string | null,
genotype: string | null = null,
): Gerbil {
return {
id,
name,
gender,
status: 'Breeding',
dateOfBirth,
dateOfDeath: null,
causeOfDeath: null,
goHomeDate: null,
litterId,
enclosureId: null,
colorVarietyId,
originContactId: null,
receiverContactId: null,
originBreeder: null,
// SEARCH-2b: backend stores a normalized name (lowercase, [\s._-] stripped).
nameSearch: name.toLowerCase().replace(/[\s._-]/g, ''),
genotype,
notes: null,
// BESTAND-FILTER: default = eigener Bestand; einzelne externe Ahnen unten gesetzt.
isResident: true,
profilePhotoUrl: null,
}
}
/** Frischer, unabhängiger Datenbestand pro Test. */
export function seedDb(): MockDb {
const gerbils: Gerbil[] = [
{
...gerbil('kruemel', 'Krümel', 'female', '2025-03-12', 'w-kruemel', 'cv-agouti', 'Aa CC DD EE GG Pp spsp rere'),
enclosureId: 'enc-gross',
originContactId: 'con-meier',
originBreeder: 'Clan-Kleine-Chaoten',
// NACHVERFOLGUNG: Datenherkunft (vom Import erzeugter JSON-String).
provenance: JSON.stringify({
sourceFiles: ['Stammbaum von Krümel.xlsx', 'Wurfchronik-Detail.docx'],
mergedRecordCount: 2,
fromWurfchronik: true,
parentMethod: 'chart-position',
parentConfidence: 'medium',
notes: ['aus 2 Datensätzen zusammengeführt'],
}),
},
{ ...gerbil('fridolin', 'Fridolin', 'male', '2023-05-01', 'w-fridolin', 'cv-schwarz', 'aa CC DD EE GG PP spsp rere'), enclosureId: 'enc-gross', originBreeder: 'Zoohandlung Meier', profilePhotoUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' },
gerbil('luna', 'Luna', 'female', '2023-08-15', 'w-luna', 'cv-gold', 'AA CC DD EE GG pp spsp rere'),
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('karlsson', 'Karlsson', 'male', '2022-02-02', 'w-karlsson', 'cv-blau'),
gerbil('smilla', 'Smilla', 'female', '2022-09-30', null, 'cv-himalaya'),
gerbil('anton', 'Anton', 'male', '2019-07-07', 'w-anton', 'cv-agouti'),
gerbil('greta', 'Greta', 'female', '2019-05-23', null, 'cv-schwarz'),
gerbil('frieda', 'Frieda', 'female', '2020-01-18', null, 'cv-gold'),
gerbil('emil', 'Emil', 'male', '2017-03-03', 'w-emil', 'cv-agouti'),
// BESTAND-FILTER: externe Ahnen (Gründertiere fremder Zuchten, nur für den Stammbaum).
{ ...gerbil('hilde', 'Hilde', 'female', '2017-11-11', null, 'cv-schwarz'), isResident: false, originBreeder: 'Zoohandlung Meier' },
{ ...gerbil('max', 'Max', 'male', '2015-08-08', null, 'cv-agouti'), isResident: false, originBreeder: 'Zoohandlung Meier' },
// Statistik: Verstorbene + Abgegebene für Verluste/Bestandskurve
{ ...gerbil('willi', 'Willi', 'male', '2019-09-09', null, 'cv-schwarz'), status: 'Deceased', dateOfDeath: '2022-04-04' },
{ ...gerbil('pauli', 'Pauli', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-huber' },
{ ...gerbil('rosa', 'Rosa', 'female', '2020-02-02', null, 'cv-gold'), status: 'Deceased', dateOfDeath: '2023-08-15' },
{ ...gerbil('pauli-breeder', 'Pauli Breeder', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-meier' },
{ ...gerbil('pauli-neither', 'Pauli Neither', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-neither' },
// ABGABE: zwei Tiere zur Abgabe im Quarantänebecken (enc-leer ist leer, kein Konflikt
// mit dem Becken-Belegungstest der Großbecken-2-Tiere-Assertion).
{
...gerbil('sale-balu', 'Balu Abgabe', 'male', '2024-03-12', null, 'cv-agouti'),
status: 'ForSale' as const,
enclosureId: 'enc-leer',
notes: 'ruhig, nimmt Leckerlis aus der Hand',
},
{
...gerbil('sale-benny', 'Benny Abgabe', 'male', '2024-03-12', null, 'cv-schwarz-schecke'),
status: 'ForSale' as const,
enclosureId: 'enc-leer',
notes: 'neugieriger Entdecker',
},
// LITTER-JUVENILE-DETAILS: Jungtiere für Wurf K mit Zusatzinfos.
// JUVENILE-AVATARS: Pippa hat profilePhotoUrl (1×1-PNG data-URI) → Avatar-Img sichtbar.
{
...gerbil('pup-abgabe', 'Pippa', 'female', '2025-03-12', 'w-kruemel', 'cv-gold'),
status: 'GivenAway' as const,
goHomeDate: '2025-05-01',
receiverContactId: 'con-huber',
profilePhotoUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
},
{
...gerbil('pup-verstorben', 'Benny', 'male', '2025-03-12', 'w-kruemel', null),
status: 'Deceased' as const,
dateOfDeath: '2025-04-15',
causeOfDeath: 'Altersschwäche',
},
// UI-POLISH-1: Import-Stub ohne Namen — testet den '(ohne Namen)'-Platzhalter in Liste + Detail.
gerbil('nameless-stub', '', 'male', '2023-01-01', null, null),
// SIBLING-PAIRING: Vollgeschwister-Verpaarung (in der Zucht häufig). Beide
// Eltern von 'inzucht-kind' stammen aus DEMSELBEN Wurf 'w-zwillinge' → das
// Diagramm führt ihren Vorfahren-Ast zu einem Verweis-Knoten zusammen.
gerbil('zwilling-bock', 'Zwilling Bock', 'male', '2020-05-01', 'w-zwillinge', 'cv-agouti'),
gerbil('zwilling-maus', 'Zwilling Maus', 'female', '2020-05-01', 'w-zwillinge', 'cv-schwarz'),
{ ...gerbil('opa-w', 'Opa W', 'male', '2018-01-01', null, 'cv-agouti'), isResident: false },
{ ...gerbil('oma-u', 'Oma U', 'female', '2018-02-01', null, 'cv-gold'), isResident: false },
gerbil('inzucht-kind', 'Inzucht Kind', 'female', '2021-06-01', 'w-inzucht', 'cv-agouti'),
]
const litters: Litter[] = [
{ id: 'w-zwillinge', name: 'Wurf Z', date: '2020-05-01', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'opa-w', motherId: 'oma-u' },
{ id: 'w-inzucht', name: 'Wurf I', date: '2021-06-01', totalBorn: 3, expectedGoHomeDate: null, notes: null, fatherId: 'zwilling-bock', motherId: 'zwilling-maus' },
{ id: 'w-kruemel', name: 'Wurf K', date: '2025-03-12', totalBorn: 5, expectedGoHomeDate: '2025-04-16', notes: null, fatherId: 'fridolin', motherId: 'luna', deathsWithin8Weeks: 1 },
{ id: 'w-fridolin', name: 'Wurf F', date: '2023-05-01', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'balu', motherId: 'maja' },
{ id: 'w-luna', name: 'Wurf L', date: '2023-08-15', totalBorn: 6, expectedGoHomeDate: null, notes: null, fatherId: 'karlsson', motherId: 'smilla' },
{ id: 'w-balu', name: 'Wurf B', date: '2021-04-20', totalBorn: 3, expectedGoHomeDate: null, notes: null, fatherId: 'anton', motherId: 'greta' },
// Anton ist auch Karlssons Vater -> gemeinsamer Vorfahre (Inzucht-Demo)
{ id: 'w-karlsson', name: 'Wurf C', date: '2022-02-02', totalBorn: 5, expectedGoHomeDate: null, notes: null, fatherId: 'anton', motherId: 'frieda' },
{ id: 'w-anton', name: 'Wurf A', date: '2019-07-07', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'emil', motherId: 'hilde' },
{ id: 'w-emil', name: 'Wurf E', date: '2017-03-03', totalBorn: 2, expectedGoHomeDate: null, notes: null, fatherId: 'max', motherId: null },
]
const enclosures: Enclosure[] = [
{ id: 'enc-gross', name: 'Großbecken', notes: '120×50 cm' },
{ id: 'enc-leer', name: 'Quarantänebecken', notes: null },
]
// FEAT-13: contactInfo (Freitext) wurde durch strukturierte Felder ersetzt.
const contacts: Contact[] = [
{ id: 'con-meier', name: 'Zoohandlung Meier', email: 'meier@example.de', phone: null, address: 'Hauptstraße 1, 12345 Musterstadt', notes: null, isBreeder: true, isReceiver: true },
{ id: 'con-huber', name: 'Familie Huber', email: null, phone: '0151 2345678', address: null, notes: null, isBreeder: false, isReceiver: true },
{ id: 'con-frei', name: 'Züchterin Frei', email: null, phone: null, address: null, notes: 'unverknüpft', isBreeder: true, isReceiver: false },
{ id: 'con-neither', name: 'Weder Noch', email: null, phone: null, address: null, notes: 'weder züchter noch abnehmer', isBreeder: false, isReceiver: false },
]
const colorVarieties = [
{ id: 'cv-agouti', name: 'Agouti', canonicalGenotype: 'AA CC DD EE GG PP spsp rere', sortOrder: 1 },
{ id: 'cv-schwarz', name: 'Schwarz', canonicalGenotype: 'aa CC DD EE GG PP spsp rere', sortOrder: 2 },
{ id: 'cv-gold', name: 'Gold', canonicalGenotype: 'AA CC DD EE GG pp spsp rere', sortOrder: 3 },
{ id: 'cv-blau', name: 'Blau', canonicalGenotype: 'aa CC dd EE GG PP spsp rere', sortOrder: 4 },
{ id: 'cv-himalaya', name: 'Himalaya', canonicalGenotype: 'AA chch DD EE GG PP spsp rere', sortOrder: 5 },
{ id: 'cv-schwarz-schecke', name: 'Schwarz Schecke', canonicalGenotype: 'aa CC DD EE GG PP Spsp rere', sortOrder: 6 },
]
const healthRecords: HealthRecordRow[] = [
{
id: 'hr-1',
gerbilId: 'kruemel',
date: '2026-01-15',
type: 'Vaccination',
description: 'Jahresimpfung',
veterinarian: 'Dr. Vogel',
createdAt: '2026-01-15T10:00:00Z',
},
]
const weightRecords: WeightRecordRow[] = [
{ id: 'wr-1', gerbilId: 'kruemel', date: '2026-05-01', weightGrams: 78, notes: null },
{ id: 'wr-2', gerbilId: 'kruemel', date: '2026-05-20', weightGrams: 82, notes: null },
]
// WEB-0b: die 6 Webseiten-Seiten (wie vom Backend geseedet), mit ein paar Blöcken.
const pages: MockPage[] = [
{
id: 'page-start',
slug: 'start',
title: 'Startseite',
seoDescription: 'Willkommen bei unserer Rennmauszucht.',
status: 'Published',
blocks: [
{ id: 'blk-start-1', order: 0, type: 'Heading', data: { text: 'Willkommen', level: 2 } },
{ id: 'blk-start-2', order: 1, type: 'RichText', data: { markdown: 'Schön, dass du da bist.' } },
],
},
{ id: 'page-zucht', slug: 'ueber-die-zucht', title: 'Über die Zucht', seoDescription: null, status: 'Draft', blocks: [] },
{
id: 'page-abgabe',
slug: 'abgabetiere',
title: 'Abgabetiere',
seoDescription: null,
status: 'Published',
blocks: [
{ id: 'blk-abgabe-1', order: 0, type: 'AbgabetiereList', data: { mode: 'auto', intro: 'Diese Tiere suchen ein Zuhause.' } },
],
},
{ id: 'page-bedingungen', slug: 'abgabebedingungen', title: 'Abgabebedingungen', seoDescription: null, status: 'Draft', blocks: [] },
{ id: 'page-farben', slug: 'farben-genetik', title: 'Farben & Genetik', seoDescription: null, status: 'Draft', blocks: [] },
{ id: 'page-kontakt', slug: 'kontakt', title: 'Kontakt', seoDescription: null, status: 'Draft', blocks: [] },
]
// INBOX-1: Anfragen (neueste zuerst nach receivedAt sortierbar)
const requests: InboxRequest[] = [
{
id: 'req-anna',
gmailMessageId: '<anna-1@mail.example>',
threadId: 'thr-1',
fromAddress: 'anna@example.de',
fromName: 'Anna Albrecht',
subject: 'Anfrage: Pärchen zur Abgabe?',
bodyText:
'Hallo,\n\nich habe euer Inserat gesehen — sind die beiden Agouti-Jungs noch zu haben?\n\nViele Grüße\nAnna',
receivedAt: '2026-06-05T18:30:00Z',
status: 'New',
assignedContactId: null,
draftReply: null,
answeredAt: null,
},
{
id: 'req-ben',
gmailMessageId: '<ben-1@mail.example>',
threadId: 'thr-2',
fromAddress: 'ben@example.org',
fromName: 'Ben Berger',
subject: 'Frage zur Haltung',
bodyText: 'Guten Tag, was für ein Becken empfehlt ihr für zwei Rennmäuse?',
receivedAt: '2026-06-04T09:00:00Z',
status: 'InProgress',
assignedContactId: null,
draftReply: 'Hallo Ben, wir empfehlen mindestens 100×50 cm …',
answeredAt: null,
},
{
id: 'req-clara',
gmailMessageId: '<clara-1@mail.example>',
threadId: null,
fromAddress: 'clara@example.com',
fromName: null,
subject: 'Danke!',
bodyText: 'Die beiden sind super angekommen — danke nochmal!',
receivedAt: '2026-06-01T12:00:00Z',
status: 'Answered',
assignedContactId: 'con-meier',
draftReply: null,
answeredAt: '2026-06-02T08:00:00Z',
},
]
return {
gerbils,
litters,
enclosures,
contacts,
colorVarieties,
healthRecords,
weightRecords,
pages,
requests,
aiConfigured: true,
mailConfigured: true,
contracts: [],
saleAdConfigured: true,
namesConfigured: true,
feedback: [],
}
}