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>
This commit is contained in:
83
tools/import/test_extract.py
Normal file
83
tools/import/test_extract.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Zero-dep tests for extract.py band-aware Farbschlag + name-bleed guard.
|
||||
|
||||
Run: python test_extract.py (exit 0 = all pass)
|
||||
Covers (PEDIGREE-LINK / Julian-confirmed): deep pedigree bands (gen >= 2, cols K/N/Q...)
|
||||
are Name/DOB/Genotype ONLY — no Farbschlag cell — so a stray health note or the next
|
||||
block's name must NOT be captured as Farbschlag; early bands (gen 0-1) keep their real
|
||||
Farbschlag. Plus the looks_like_animal_name guard (a parent name must not be a Farbschlag).
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
import tempfile
|
||||
import extract as e
|
||||
|
||||
failed = 0
|
||||
|
||||
|
||||
def check(name, cond):
|
||||
global failed
|
||||
print(("ok: " if cond else "FAIL: ") + name)
|
||||
if not cond:
|
||||
failed += 1
|
||||
|
||||
|
||||
def _cell(ref, text):
|
||||
return f'<c r="{ref}" t="inlineStr"><is><t>{text}</t></is></c>'
|
||||
|
||||
|
||||
def _make_xlsx(path, cells):
|
||||
"""cells: {(colLetter+row): text}. Build a minimal single-sheet xlsx (no styles)."""
|
||||
rows = {}
|
||||
for ref, text in cells.items():
|
||||
r = int("".join(ch for ch in ref if ch.isdigit()))
|
||||
rows.setdefault(r, []).append(_cell(ref, text))
|
||||
body = "".join(f'<row r="{r}">{"".join(cs)}</row>' for r, cs in sorted(rows.items()))
|
||||
sheet = ('<?xml version="1.0"?><worksheet xmlns="http://x"><sheetData>'
|
||||
+ body + "</sheetData></worksheet>")
|
||||
with zipfile.ZipFile(path, "w") as z:
|
||||
z.writestr("xl/worksheets/sheet1.xml", sheet)
|
||||
|
||||
|
||||
# --- band-aware Farbschlag ---
|
||||
# col E = gen 0 (early, HAS Farbschlag); col K = col 11 = gen 2 (deep, NO Farbschlag).
|
||||
tmp = os.path.join(tempfile.gettempdir(), "bandtest.xlsx")
|
||||
_make_xlsx(tmp, {
|
||||
# early band (E): Name / *DOB / Farbschlag / Genotype
|
||||
"E10": "Chesnut",
|
||||
"E11": "*13.11.2019",
|
||||
"E12": "Kohlfuchsschimmel",
|
||||
"E13": "aa CC DD ee GG PP spsp rere",
|
||||
# deep band (K): Name / *DOB / Genotype / stray NOTE (must NOT become Farbschlag)
|
||||
"K10": "DeepAnimal",
|
||||
"K11": "*01.01.2020",
|
||||
"K12": "aa CC DD EE GG PP spsp rere",
|
||||
"K13": "DD-Tumor",
|
||||
})
|
||||
try:
|
||||
animals = e.extract_stammbaum(tmp)
|
||||
by_name = {a["name"]: a for a in animals}
|
||||
check("early band keeps real Farbschlag",
|
||||
by_name.get("Chesnut", {}).get("farbschlag") == "Kohlfuchsschimmel")
|
||||
check("deep band has NO Farbschlag (note not grabbed)",
|
||||
by_name.get("DeepAnimal", {}).get("farbschlag") == "")
|
||||
check("deep-band animal still parsed (Name/DOB/Genotype)",
|
||||
"DeepAnimal" in by_name and by_name["DeepAnimal"]["dob"].startswith("01.01"))
|
||||
finally:
|
||||
try: os.remove(tmp)
|
||||
except OSError: pass
|
||||
|
||||
gen = e.gen_of
|
||||
check("gen_of: early bands < 2 (E,H)", gen(5) < 2 and gen(8) < 2)
|
||||
check("gen_of: deep bands >= 2 (K,N,Q)", gen(11) >= 2 and gen(14) >= 2)
|
||||
|
||||
# --- name-bleed guard (a parent name is not a Farbschlag) ---
|
||||
check("v.d. name rejected", e.looks_like_animal_name("Tennessee von den Kleinen Chaoten"))
|
||||
check("gen.+v.d. name rejected", e.looks_like_animal_name("Victoria Welby gen. Welby v.d. Kleinen Chaoten"))
|
||||
check("real Farbschlag accepted", not e.looks_like_animal_name("Kohlfuchsschimmel"))
|
||||
check("real Farbschlag accepted 2", not e.looks_like_animal_name("Orangeschimmel, hell"))
|
||||
|
||||
if failed:
|
||||
print(f"\n{failed} test(s) FAILED")
|
||||
sys.exit(1)
|
||||
print("\nALL PASS")
|
||||
Reference in New Issue
Block a user