# CLAUDE.md — GerbilManager Leitfaden für die Arbeit an diesem Repo. Bitte vor Änderungen lesen. ## Was ist das? Verwaltungs-App für eine **Rennmaus-Zucht** („Zucht der kleinen Chaoten"): Tiere, Würfe, Stammbäume/Ahnentafeln, Genetik (Farbschläge/Genotypen), Gehege, Kontakte (Züchter/Abnehmer), Abgabeverträge, Statistik und eine öffentliche CMS-Webseite. Single-User (die Züchterin), Daten größtenteils aus bestehenden Quelldateien importiert. ## Architektur / Projekte | Projekt | Zweck | |---|---| | `GerbilManager.AppHost` | **.NET Aspire** Orchestrator — startet PostgreSQL (Container) + WebAPI | | `GerbilManagerWebAPI` | Backend: ASP.NET **Minimal API**, EF Core + Postgres, Endpoints in `Endpoints/`, Entities in `Models/`, DTOs in `Dtos/` | | `GerbilManager.ServiceDefaults` | Aspire-Defaults (Telemetry/Health) | | `GerbilManager.Tests` | Backend-Tests (xUnit) | | `gerbil-manager-web` | Frontend: **React + Vite + TypeScript** (React Router, react-d3-tree für den Stammbaum) | | `tools/import` | **Python**-Import-Pipeline (zero-dep), wandelt Quelldateien → `resolved_import.json` | ## App starten (IMMER über Aspire, nicht nur die WebAPI) ```bash dotnet run --project GerbilManager.AppHost ``` - WebAPI: http://localhost:5179 · OpenAPI: `/openapi/v1.json` - Aspire-Dashboard: https://localhost:17004 (Login-Token steht im AppHost-Log) - EF-Migrationen werden beim Start automatisch angewandt (`db.Database.Migrate()` in `Program.cs`). - Postgres läuft als Container (Name wechselt pro Run, z. B. `postgres-xxxx`); die **Daten liegen in einem persistenten Volume** → überleben Neustarts. DB-Name: `gerbilmanager`. ## Import → DB (3-stufig, WICHTIG) Die DB wird **nicht** direkt von Python beschrieben. Nach jeder Import-Code-Änderung neu generieren UND neu einspielen, sonst zeigt die App den alten Stand. ```bash # 1. Stammbäume + Wurfchronik → animals.json/litters.json (+ Fotos) python tools/import/extract.py # 2. Abgabeverträge (DOCX) → contracts.json python tools/import/extract_contracts.py # 3. Dedup, Eltern-Resolver, Provenance, Vertrags-Anreicherung → resolved_import.json python tools/import/merge_and_resolve.py # 4. In die laufende DB laden (API muss laufen): WISCHT + lädt neu curl -X POST http://localhost:5179/import/ingest-resolved ``` - **`IngestResolvedService` wischt** Gerbils/Litters/Contacts/Photos/Health/Weights/ **SaleContracts** und lädt aus `resolved_import.json` neu. Importierte SaleContracts sind Teil des Payloads → werden bei jedem Ingest neu erzeugt (idempotent, IDs deterministisch). - **Feedback überlebt den Ingest** (wird nicht gewischt) — daher hat `Feedback` nur **lose, nullable Guid-Spalten ohne FK** (GerbilId/LitterId/ContactId). Dieses Muster für alles nutzen, was Re-Ingests überleben soll. - `resolved_import.json` ist **gitignored** (Runtime-Output); `review-report.md` ist getrackt. - Deterministische GUIDs via `generate_guid("...")` → stabile IDs über Re-Ingests. ### Quelldateien (lokal/Netzwerk) - Stammbäume: `C:\Users\gulum\dev\Sttammbäume\*.xlsx` - Wurfchronik: `C:\Users\gulum\dev\Wurfchronik der Kleine Chaoten Teil1.xlsx` - Abgabeverträge: `\\truenas\Datengrab\Rennmäuse\Verträge\*.docx` (~1,4k; kann zeitweise offline sein) ## Build & Test **Frontend** (`cd gerbil-manager-web`): ```bash npx tsc --noEmit # Typecheck (muss sauber sein) npx vitest run # Unit-Tests npx eslint # Lint npx playwright test [spec]# e2e (Projekte: desktop + phone), läuft gegen Mock-API ``` - e2e nutzt einen **Mock-Modus** (`e2e/mock-api.ts` + `e2e/mock-data.ts`), kein echtes Backend. Neue API-Routen/Felder dort spiegeln. `skipUnlessMock()` am Testanfang. **Backend**: ```bash dotnet build dotnet test # GerbilManager.Tests dotnet ef migrations add --project GerbilManagerWebAPI # neue Migration ``` - Zum Bauen muss eine laufende WebAPI ggf. gestoppt werden (Datei-Lock auf `bin`). **Python** (`cd tools/import`, zero-dep, exit 0 = pass): ```bash python test_extract.py test_extract_docx.py test_genotype.py test_merge_resolve.py test_extract_contracts.py ``` ## Konventionen (bitte einhalten) - **Alle deutschen UI-Texte in `gerbil-manager-web/src/strings/de.ts`** — niemals in Komponenten hartkodieren. - **EF-Migration** für jede Schema-Änderung; wird beim Aspire-Start angewandt. - **Provenance/Datenherkunft** liegt als **JSON-Text-Spalte** (`Provenance`) auf Gerbil/Contact/Litter → JSON-Feld-Erweiterungen brauchen **keine** Migration. - **Lose nullable Guid-Spalten ohne FK** für Tabellen, die den Ingest-Wipe überleben. - Commits: nur wenn der Nutzer es verlangt; auf `main` wird gearbeitet (Remote: `http://truenas:13000/Gulum/GerbilManager.git`). Commit-Messages auf Deutsch, Format `feat(scope): …`. - Subagenten committen NICHT — Haupt-Loop verifiziert und committet. ## Domänenwissen (Rennmaus-Zucht) - **Lebensspanne ~6 Jahre** → ein Elternteil kann max. ~6 Jahre älter als sein Kind sein und muss vorher geboren sein (`parent_age_plausible` im Import; verwirft unplausible Eltern-Links). - **Geschwisterverpaarung** (Vollgeschwister als Eltern): erkannt, wenn Vater & Mutter **dieselbe `litterId`** ODER **dasselbe Geburtsdatum** teilen (gleiches DOB = starkes Indiz). UI: Diagramm führt den Vorfahren-Ast zusammen (Verweis-Knoten); Akte zeigt „⚭ Geschwisterverpaarung"-Chip. - **Box-Farbe im Stammbaum-xlsx = Geschlecht**: weiß = weiblich, blau = männlich. - **Genotyp/Farbschlag**: 8-Locus-Notation (siehe `tools/import/genotype.py`, `gerbil-manager-web/src/genetics`). Unbekanntes Allel = `-` (nicht `?`). E-Locus: `ee`=Fuchs, `eef`=Fuchsschimmel, `efef`=Schimmel. - **Stammbaum-Charts**: Generationen = Spaltenbänder (Proband links in Spalte B/2, je Generation +3 Spalten); Eltern-Position: Vater oben, Mutter unten. Fotos liegen je nach Datei links/auf der Namenszelle — die Seite wird heuristisch über die geringste Spalten-Fehlausrichtung bestimmt (`extract._attach_photos`). ## Stolperfallen - **Aspire-Containername wechselt** pro Run, das Daten-Volume bleibt → DB-Inhalt überlebt Neustarts (auch alte Schema-Stände → Migrationskonflikte möglich, s. u.). - **Migrations-Historie vs. committete Migration-IDs**: Wird eine Migration angewandt und danach mit neuem Zeitstempel neu generiert, sieht EF sie als „pending" und der Start crasht („column … already exists"). Schema ist dann korrekt — nur die `__EFMigrationsHistory`-IDs müssen an die committeten Dateinamen angeglichen werden. - **PowerShell + psql**: PascalCase-Identifier (`"__EFMigrationsHistory"`) über PowerShell→docker→psql zerschießen das Quoting. Stattdessen Bash-Heredoc: `docker exec -i -e PGPASSWORD=$PW psql -U postgres -d gerbilmanager <<'SQL' … SQL`. Passwort: `docker exec printenv POSTGRES_PASSWORD`. - **Direkte DB-Schreibzugriffe** (psql) werden vom Auto-Mode-Classifier blockiert — vorher beim Nutzer rückversichern. - Bash-Tool ist **Git-Bash (POSIX)**, das andere Tool ist **PowerShell** — je eigene Syntax. ## Wo liegt was (Feature-Landkarte) - **Stammbaum-Viewer**: `gerbil-manager-web/src/pages/StammbaumPage.tsx`, `src/pedigree/*` (build.ts = Baum-Aufbau + Geschwister-Erkennung; types.ts). Desktop passt den ganzen Baum ein; Touch: Long-Press öffnet das Kontextmenü (ID kopieren / Fehler melden). - **Datenherkunft/Nachverfolgung**: `src/components/ProvenanceDialog.tsx`; erzeugt im Import via `build_entity_provenance`/`_build_*_history` (chronologische, datei- attribuierte `history[]`, inkl. „⚠ … verworfen — Grund; … verwendet"). - **Fehler melden (Feedback)**: `src/components/ReportErrorDialog.tsx`, `src/api/feedback.ts`, Backend `Endpoints/FeedbackEndpoints.cs` + `Models/Feedback.cs`. - **Verträge**: `Models/SaleContract.cs`, `Endpoints/ContractEndpoints.cs`, `ContractGenerator.cs`, Frontend `src/pages/Vertraege*`; Import: `extract_contracts.py` + `enrich_from_contracts` in `merge_and_resolve.py`. - **Import-Kernlogik**: `tools/import/merge_and_resolve.py` (Dedup, Eltern-Resolver mit Gender-/Alters-Plausibilität, Litter-/Sibling-Dedup, Provenance, Vertrags-Anreicherung). ## Verifikations-Checkliste vor „fertig" 1. `npx tsc --noEmit`, `npx vitest run`, `npx eslint` sauber. 2. Betroffene `npx playwright test`-Specs grün. 3. `dotnet build` + `dotnet test` grün. 4. Bei Import-Änderungen: `python test_*.py` grün + `resolved_import.json` plausibel (Stichprobe), danach App neu starten + `POST /import/ingest-resolved`.