FEAT: Implement deceased/givenaway enclosure visibility rules, preserve external clan name, and hide receiver fields for deceased gerbils

This commit is contained in:
2026-06-13 01:40:19 +02:00
parent 5732398e60
commit 396d8f05d8
53 changed files with 7431 additions and 274 deletions

View File

@@ -35,19 +35,20 @@ DEATH = re.compile(r"\+\s?(\d{1,2}\.\d{1,2}\.(?:\d{4}|\d{2})|\d{4})")
# ---------------------------------------------------------------- helpers ----
def gen_of(colnum):
def gen_of(colnum, offset=0):
"""Map a column number to a generation band (0=proband ... 5=deepest)."""
if colnum <= 6:
return 0 # E band (proband / "Kids")
if colnum <= 9:
return 1 # H band (parents)
if colnum <= 12:
return 2 # K band (grandparents)
if colnum <= 15:
return 3 # N band (great-grandparents)
if colnum <= 17:
return 4 # Q band (gg-grandparents)
return 5 # R/S band (name-pairs)
effective_col = colnum - offset
if effective_col <= 3:
return 0 # Column B (2) -> proband
if effective_col <= 6:
return 1 # Column E (5) -> parents
if effective_col <= 9:
return 2 # Column H (8) -> grandparents
if effective_col <= 12:
return 3 # Column K (11) -> great-grandparents
if effective_col <= 15:
return 4 # Column N (14) -> gg-grandparents
return 5 # Column Q (17) or deeper -> ggg-grandparents
def norm_name(name):
@@ -189,6 +190,9 @@ def extract_stammbaum(path):
cells = xu.read_cells(z, sheets[0], ss)
fillsex = xu.cell_fill_sex(z, sheets[0]) # box colour -> sex (blue=male, white=female)
has_col2 = any(c == 2 for (c, r) in cells)
col_offset = 0 if has_col2 else 3
# group cells by column for block reconstruction
by_col = {}
for (c, r), t in cells.items():
@@ -230,7 +234,7 @@ def extract_stammbaum(path):
# WITH a Farbschlag cell; deep bands (gen >= 2, cols K/N/Q...) are 3-cell blocks
# (Name/DOB/Genotype) with NO Farbschlag — colour is derived from the genotype. So in
# deep bands we must NOT grab the next block's name or a stray health note as Farbschlag.
deep_band = gen_of(c) >= 2
deep_band = gen_of(c, col_offset) >= 2
for rr in range(r + 1, r + 4):
cell = cells.get((c, rr))
if not cell:
@@ -269,7 +273,7 @@ def extract_stammbaum(path):
"parentRefs": [],
"photos": [],
"sourceFiles": [fname],
"_gen": gen_of(c),
"_gen": gen_of(c, col_offset),
"_col": c,
"_row": r,
"_file": fname,
@@ -280,7 +284,7 @@ def extract_stammbaum(path):
for (c, r), t in cells.items():
if (c, r) in used:
continue
if " & " in t and not DOB.search(t) and len(t) < 90 and gen_of(c) >= 4:
if " & " in t and not DOB.search(t) and len(t) < 90 and gen_of(c, col_offset) >= 4:
for part in t.split(" & "):
part = clean_name(part)
if part:
@@ -291,7 +295,7 @@ def extract_stammbaum(path):
"genotype": gt.parse(""), "deaf": None, "tags": [],
"breeder": "", "zucht": zraw,
"parentRefs": [], "photos": [], "sourceFiles": [fname],
"_gen": gen_of(c), "_col": c, "_row": r, "_file": fname,
"_gen": gen_of(c, col_offset), "_col": c, "_row": r, "_file": fname,
"_zucht": norm_zucht(zraw),
})
@@ -337,8 +341,9 @@ def _attach_photos(z, sheets, animals, fname):
for a in animals:
by_gen.setdefault(a["_gen"], []).append(a)
media_dir = os.path.join(OUT, "photos")
col_offset = 0 if any(a["_col"] == 2 for a in animals) else 3
for i, (sp, col, row, media) in enumerate(anchors):
g = gen_of(col)
g = gen_of(col, col_offset)
cands = by_gen.get(g, [])
if not cands:
# fall back to nearest animal by row across all gens
@@ -1026,6 +1031,9 @@ def main():
print(f" {len(got):4d} {os.path.basename(path)}")
raw_animals.extend(got)
# Skip pseudo-animal records (like "DD-Tumor bei Geschwister") that are actually notes
raw_animals = [a for a in raw_animals if "DD-Tumor" not in a["name"]]
litters = []
if os.path.isfile(args.wurfchronik):
litters = extract_wurfchronik(args.wurfchronik)