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