Files
GerbilManager/tools/import
Gulum 5a6d1c2541
All checks were successful
CI / Frontend Tests (Node/Vite) (push) Successful in 9m38s
CI / Backend Tests (.NET) (push) Successful in 1m14s
CI / Docker Build & Push (push) Successful in 1m33s
CI / Deploy auf TrueNAS (Custom App) (push) Successful in 8s
fix(tickets): 16 Tickets der Züchterin — Genetik, Importer, Daten, Akte
Genetik-Engine (bde4ec70, f89e95ad, 2322c2a8):
- Saphir/Platin/Platin-Hell unterscheiden sich NUR in der C-Zygotie und waren im
  Katalog identisch (locusToken reduziert auf das dominante Allel) → Saphir war
  unerreichbar. Token darf jetzt ein exaktes, ungeordnetes Allelpaar "x/y"
  verlangen: Platin C/C, Saphir C/cchm, Platin-Hell C/ch. TS- und Python-Mirror
  identisch, Seeds regeneriert, Migration ReseedColorVarietiesGen6Saphir.
- c[hm] wird als Alias auf c[chm] normalisiert (Tippfehler in zwei Charts) —
  vorher war Jays Gencode unparsebar und ergab "Zobel-Hell".
- 6 Tiere wechseln den errechneten Farbschlag (5x Platin→Saphir, Jay→Zobel).

Importer-Logik:
- renameTo-Notiz-Sweep: der alte Name blieb in Wurf-Notizen stehen ("Blacky +
  Kruke") — jetzt wortgenau ersetzt, mit vier Guards gegen Kollateralschaden.
- Neuer Resolution-Schlüssel goHomeDate (autoritativ, zieht GivenAway nach) und
  spottingType; Gencode-Overrides gewinnen jetzt bei präzisem Match (vorher
  wurden Entscheidungen stumm verschluckt, z. B. Eliza und Chris).
- parse_date verwirft implausible Jahre (JackJack hatte 1310-05-13).
- extract.py erkennt Stammbaum-Blöcke auch ohne Stern vor dem Geburtsdatum
  (Bijou bekam dadurch den Nachbar-Ast als Mutter).
- Backend-Ingest überträgt SpottingType (nur bei Payload-Wert).

Daten (conflict-decisions.json, re-ingest-stabil): Kuke-Merge + Zuchtname,
Jamie- und Sakura-Dubletten, Phantom-Tier "Unbekannt", Eltern von Kathlin,
Fast Boy/Ziwa und dem Q4-/TS-Wurf, Eliza-Gencode, Merle-Abgabe, JackJack.

Frontend (24522f5f): Abgabedatum steht in der Tier-Akte jetzt direkt unter dem
Abnehmer und hängt nicht mehr am Status.

Tests: +25 Checks test_merge_resolve, +7 test_genotype, +8 genetics.test.ts,
neuer Extract- und Ingest-Test, e2e GOHOME-ROW. Alles grün.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 23:32:28 +02:00
..

GerbilManager import tooling (FEAT-8b)

One-off migration tooling (Python, no third-party deps) that turns Julian's wife's hand-built spreadsheets into normalised JSON for review and, later, import. This is not product code — it lives outside the app and is run manually.

See the format analysis in FEAT-8a-format-spec.md (Pam's hive workspace).

What it does

extract.py runs stages 12 of the pipeline:

  1. Extract (stage 1)
    • 10 Stammbaum pedigree charts → animals (name, DOB, death, Farbschlag, genotype, breeder, positionally-reconstructed parent links, photos).
    • Wurfchronik litter chronicle → litters (date, dam, sire, Wurfstärke, sex breakdown, Zuchtnummer, notes). Columns are read by header row because the two sheets use different schemas.
    • Embedded photos (xl/media) → output/photos/<animal-slug>/, mapped to the animal by drawing anchor position.
  2. Dedup + review (stage 2)
    • Merge animals on normalise(call-name) + DOB, with the Zucht as discriminator (Julian's ruling: Wurfchronik [brackets] ≡ Stammbaum of/von <line> suffix — both are the breeding line; same name+DOB but different Zucht stays two animals).
    • Match animals onto Wurfchronik litters (litterRef) via DOB + (Vater, Mutter) — the Pam-validated build order (chronicle litters are canonical).
    • Emit a German-language output/review-report.md for the breeder to verify (merges, conflicts, ambiguous/incomplete entries, unmapped genotype tokens, litter data-quality warnings).
    • Nothing is loaded into the database — stage 3 (API load) is separate and waits on DATA-2 + FEAT-1b phase 2.

Wurfchronik column semantics (Julian, authoritative)

A Wurfbezeichnung · B Geburtsdatum · C Mutter · D Vater ([…] = Zucht, & = multiple sires) · E survivedToGoHome (Tabelle1 only, unlabeled — detected positionally) · F Wurfstärke → totalBorn · G breakdown Männchen,Weibchen,TG,smales/females/stillborn/diedLater (s = died after birth, before Abgabe) · last column → note. Validation: E should equal F TG s; mismatches become German warnings in the review report (data-quality signal, not an import blocker). A few Tabelle2 rows shift these columns — they are read value-adaptively and flagged with a warning.

Genotypes are mapped to the frozen 8-locus contract (A C D E G P Sp Re) while preserving everything: genotype.mapped8locus, genotype.rawGenotype (verbatim), genotype.unmappedTokens (e.g. the Uw locus, markers WFNZ/WP/DP). A - (unknown second allele) maps to ?.

Run

cd tools/import
python extract.py                       # xlsx → animals.json / litters.json
python extract.py --stammbaeume "<dir>" --wurfchronik "<file.xlsx>"
python extract_docx.py                  # Wurfchronik-Detail.docx → docx_*.json
python extract_contracts.py             # Abgabeverträge (.docx) → contracts.json
python merge_and_resolve.py             # → resolved_import.json (DB-ready)

Requires Python 3 (zero third-party deps). Re-runnable / idempotent — re-run when more files arrive (Wurfchronik Teil2+, new charts, or new contracts).

extract_contracts.py scans the breeder's sale-contract share (\\truenas\…\Verträge, ~1.4k .docx) and emits one record per contract (buyer, animal call-names, Farbschlag, dates, price, source filename). It skips the blank template, Abstammungsnachweis/Geburtsurkunde documents, and any file that is not a readable .docx. merge_and_resolve.py then conservatively folds contracts into the resolved data: buyers become receiver Contacts, and unambiguously matched gerbils get ReceiverContactId / GoHomeDate / Status=GivenAway (only where not already set), with a provenance history line. Ambiguous / unmatched animals are counted and skipped, never guessed.

Output (tools/import/output/, git-ignored except the report)

File Contents
animals.json deduped animals with genotype, parentRefs, photos, sourceFiles
litters.json litters from the Wurfchronik
docx_animals.json / docx_litters.json Wurfchronik-Detail.docx rows
contracts.json one record per Abgabevertrag (buyer, animals, dates, price)
resolved_import.json merged DB-ready payload consumed by IngestResolvedService
photos/<slug>/… extracted, anchor-mapped images
review-report.md human review deliverable (committed)

Files

  • xlsx_util.py — dependency-free .xlsx reader (zip + XML): shared strings, cells by reference, image/drawing anchors.
  • genotype.py — genotype notation parser → 8-locus mapping + raw + unmapped.
  • extract.py — the pipeline (stages 12).