Files
GerbilManager/tools/import
Gulum 4aca1d528b EXTRACT band-aware Farbschlag: deep bands (gen>=2) have no colour cell
Julian-confirmed against the real xlsx (Kentucky, Akio Kids): pedigree
blocks have two shapes. EARLY bands (gen 0-1, cols B/E/H) are 5-cell
Name/DOB/Farbschlag/Genotype/Zucht — real Farbschlag present. DEEP bands
(gen>=2, cols K/N/Q...) are 3-cell Name/DOB/Genotype with NO Farbschlag —
the colour is derived from the genotype.

The old extractor grabbed the NEXT block's name or a stray health note
(e.g. "DD-Tumor", "Chronische Ohrenentzündung...") as the deep-band
Farbschlag — source of the D1 'name-in-Farbschlag' conflicts and a big
chunk of farbschlagUnmatched. Fix: gen_of(col)>=2 => never assign a
Farbschlag in that block (Name/DOB/Genotype only); the cell is left for the
next block's name search. Deep-band animals load with empty Farbschlag and
the UI computes 'Errechnet' from the stored genotype (lossless).

Dedup already prefers a real (non-empty) early-band Farbschlag, so an animal
appearing in both an early band (Chesnut -> 'Kohlfuchsschimmel') and deep
bands keeps the real value; Chesnut and Tennessee stay separate (distinct
genotype + name+DOB key).

Result: 248/306 loadable animals now have empty Farbschlag (genotype-
derived), 0 suspicious name/note values remain, 0 Farbschlag-based conflicts
(D1 dissolved). Regression test test_extract.py builds a two-band xlsx and
asserts the deep band yields no Farbschlag + the stray note doesn't bleed.

Extractor-only (no C# change). python test_genotype + test_extract green;
dotnet 118/118. Batches with Kevin's GEN-3f seed for one re-import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:56:29 +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                       # uses the default source paths
python extract.py --stammbaeume "<dir>" --wurfchronik "<file.xlsx>"

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

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
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).