fix(import): Foto-Generations-Shift beim Stammbaum-Import beheben
Fotos liegen in den Stammbaum-xlsx je nach Datei links, rechts oder auf der Namenszelle. Die alte Seiten-Erkennung hing an festen Spalten (col 1 / col 4): Blätter ohne solche Anker wurden als Rechts-Layout fehlgedeutet, wodurch jedes Foto eine Generation zum Probanden hin verrutschte. Folge: Kazuya trug das Foto seines Vaters (Wilbur), Wilbur das seines Großvaters (Elay), Elay hatte keins. Fix (extract._attach_photos): beide Interpretationen (links/rechts) durchrechnen und die wählen, die jedes Foto am nächsten an die Namensspalte seines Tiers legt (minimale horizontale Fehlausrichtung). Diagnose über alle 42 Dateien: keine ist echt rechts-seitig; ~21 waren fehlklassifiziert. Selbstjustierend statt fester Spalten-Annahme. Regressionstest in test_extract.py: Kazuya ohne Foto, Wilbur/Elay mit ihren eigenen (gated auf vorhandene Quelldatei). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -341,14 +341,22 @@ def _attach_photos(z, sheets, animals, fname):
|
||||
if not anchors:
|
||||
return
|
||||
|
||||
# Detect left_style: whether photo is to the left or to the right of the name cell
|
||||
a_count = sum(1 for a in anchors if a[1] == 1)
|
||||
d_count = sum(1 for a in anchors if a[1] == 4)
|
||||
has_proband_in_d = any(a[1] == 4 and 50 <= a[2] <= 70 for a in anchors)
|
||||
left_style = a_count > 0 or (d_count > 0 and not has_proband_in_d)
|
||||
by_gen = {}
|
||||
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
|
||||
|
||||
def get_anchor_gen(colnum, offset=0):
|
||||
effective_col = colnum - offset
|
||||
# A photo sits one column either side of (or on) its animal's name cell;
|
||||
# the generation is read from the photo's column. Whether photos are LEFT or
|
||||
# RIGHT of the name varies, and the old fixed-column heuristic (col 1 / col 4)
|
||||
# misclassified sheets that have neither, shifting every photo one generation
|
||||
# toward the proband (e.g. „Stammbaum von Kazuya“ / „Picus Son“: Kazuya wore
|
||||
# his father's photo, the father his grandfather's). Instead, try BOTH
|
||||
# interpretations and keep the one that places photos closest to their
|
||||
# assigned animal's name column (minimal horizontal misalignment).
|
||||
def get_anchor_gen(colnum, left_style):
|
||||
effective_col = colnum - col_offset
|
||||
if left_style:
|
||||
if effective_col <= 3: return 0
|
||||
if effective_col <= 6: return 1
|
||||
@@ -364,18 +372,26 @@ def _attach_photos(z, sheets, animals, fname):
|
||||
if effective_col <= 16: return 4
|
||||
return 5
|
||||
|
||||
by_gen = {}
|
||||
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 = get_anchor_gen(col, col_offset)
|
||||
cands = by_gen.get(g, [])
|
||||
if not cands:
|
||||
# fall back to nearest animal by row across all gens
|
||||
cands = animals
|
||||
target = min(cands, key=lambda a: abs((a["_row"] - row) - 10)) if cands else None
|
||||
def targets_for(left_style):
|
||||
out = []
|
||||
for (sp, col, row, media) in anchors:
|
||||
cands = by_gen.get(get_anchor_gen(col, left_style), []) or animals
|
||||
out.append(min(cands, key=lambda a: abs((a["_row"] - row) - 10)) if cands else None)
|
||||
return out
|
||||
|
||||
def mean_col_offset(targets):
|
||||
vals = [abs(col - t["_col"]) for (sp, col, row, m), t in zip(anchors, targets) if t]
|
||||
return sum(vals) / len(vals) if vals else 0.0
|
||||
|
||||
left_targets = targets_for(True)
|
||||
right_targets = targets_for(False)
|
||||
targets = (
|
||||
left_targets
|
||||
if mean_col_offset(left_targets) <= mean_col_offset(right_targets)
|
||||
else right_targets
|
||||
)
|
||||
|
||||
for (sp, col, row, media), target in zip(anchors, targets):
|
||||
if not target:
|
||||
continue
|
||||
ext = os.path.splitext(media)[1] or ".img"
|
||||
|
||||
@@ -10,7 +10,7 @@ _Automatisch erzeugt von `tools/import/extract.py` — **noch nichts in die Date
|
||||
- in mehreren Dateien gefunden (Dubletten zusammengeführt): 476
|
||||
- Konflikte zur Klärung: **7**
|
||||
- Mehrdeutige / unvollständige Einträge (ohne Name+Datum): **380**
|
||||
- Fotos zugeordnet: **441**
|
||||
- Fotos zugeordnet: **435**
|
||||
- Würfe aus der Wurfchronik: **752**
|
||||
- Tiere mit Wurf verknüpft: **270** (davon über Geburtsdatum **und** Eltern: 168, nur über Geburtsdatum: 102; mehrdeutig: 17)
|
||||
- Würfe mit Datenqualitäts-Hinweisen: 113 (+ 138 Zeilen mit abweichendem Spaltenschema)
|
||||
|
||||
@@ -396,6 +396,27 @@ if os.path.exists(danako_path):
|
||||
else:
|
||||
print("\nWarning: Danako stammbaum file not found, skipping integration checks.")
|
||||
|
||||
# --- Stammbaum von Kazuya: photo generation-shift regression (PHOTO-LEFT-STYLE) ---
|
||||
# This sheet has neither col-1 nor col-4 image anchors, so the old fixed-column
|
||||
# heuristic misread it as right-style and shifted every photo one generation
|
||||
# toward the proband: Kazuya wore his father's (Wilbur's) photo, Wilbur wore the
|
||||
# grandfather's (Elay's). The fix picks the layout that places photos closest to
|
||||
# their animal's name column → photos land on the correct generation.
|
||||
kazuya_path = r"C:\Users\gulum\dev\Sttammbäume\Stammbaum von Kazuya.xlsx"
|
||||
if os.path.exists(kazuya_path):
|
||||
print(f"\nFound Kazuya stammbaum, running photo generation-shift validation...")
|
||||
kz = {a["name"]: a for a in e.extract_stammbaum(kazuya_path)}
|
||||
if "Kazuya" in kz:
|
||||
check("Kazuya (proband) has NO photo of his own", kz["Kazuya"]["photos"] == [])
|
||||
if "Wilbur" in kz:
|
||||
check("Wilbur (father) gets his own photo (image7), not the grandfather's",
|
||||
kz["Wilbur"]["photos"] == ["photos/wilbur-19032017/image7.jpeg"])
|
||||
if "Elay" in kz:
|
||||
check("Elay (grandfather) gets his own photo (image2)",
|
||||
kz["Elay"]["photos"] == ["photos/elay-16032016/image2.jpeg"])
|
||||
else:
|
||||
print("\nWarning: Kazuya stammbaum file not found, skipping photo-shift checks.")
|
||||
|
||||
if failed:
|
||||
print(f"\n{failed} test(s) FAILED")
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user