"""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'{text}' 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'{"".join(cs)}' for r, cs in sorted(rows.items())) sheet = ('' + body + "") 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) # --- conflict-decisions consumption (HUMANQUESTION D / C6) --- dec_path = os.path.join(tempfile.gettempdir(), "conflict-decisions-test.json") import json as _json _json.dump({"resolutions": [ {"name": "Firefly von den Kleinen Chaoten", "dob": "18.12.2019", "decision": "D-locus = D-", "genotype": "Aa c[chm]c[chm] D- Ee Gg PP Spsp", "source": "test"}, ]}, open(dec_path, "w", encoding="utf-8")) merged = [{"id": "x1", "name": "Firefly von den Kleinen Chaoten", "dob": "18.12.2019", "conflict": True, "farbschlag": "", "genotype": {"mapped8locus": {"D": ["D", "D"]}, "rawGenotype": "DD", "unmappedTokens": []}}] conflicts = [{"id": "x1", "name": "Firefly von den Kleinen Chaoten", "dob": "18.12.2019"}] n = e.apply_conflict_decisions(merged, conflicts, dec_path) check("decision un-quarantines (conflict cleared)", merged[0]["conflict"] is False) check("decision marks resolvedByDecision", merged[0].get("resolvedByDecision") is True) check("decision genotype is authoritative (D- not DD)", merged[0]["genotype"]["mapped8locus"]["D"] == ["D", "?"]) check("decision removes entry from conflicts list", conflicts == []) check("apply_conflict_decisions returns resolved count", n == 1) check("missing decisions file tolerated (returns 0)", e.apply_conflict_decisions([], [], os.path.join(tempfile.gettempdir(), "does-not-exist.json")) == 0) try: os.remove(dec_path) except OSError: pass # --- 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")