fix(import): Eltern-Ref-Auswahl geschlechts-bewusst machen (Regression behoben)

Die in 03ad9e6 eingeführte DOB-Bevorzugung war zu grob: ein datierter, aber
geschlechts-falscher Ref (z. B. weibliche „Danielle") gewann den Vater-Slot
gegen einen undatieren männlichen/unbekannten (z. B. „Hagrid Rubeus") — wodurch
Mollys korrekte Mutter Arya Stark in der Rollen-Normalisierung verloren ging.

pick_parent_ref wertet jetzt zuerst das (aus den Tierdaten ermittelte) Geschlecht
für die Rolle: ein klar falsches Geschlecht wird stark abgewertet, erst danach
zählt plausibles DOB > kein DOB > unplausibles DOB. Fixt Molly (Hagrid × Arya)
UND behält Solice (Lui × Molly = Geschwister von Silvain). Keine Abdeckungs-
Regression (0 Selbst-Verpaarungen / 0 Gender-Fehler / 0 Alter-Verletzungen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 15:11:53 +02:00
parent 2868aad369
commit 1845d37476
2 changed files with 63 additions and 10 deletions

View File

@@ -596,27 +596,38 @@ def parent_age_plausible(parent_dob, litter_date):
return 0 < (ld - pd) <= MAX_PARENT_AGE_DAYS
def pick_parent_ref(parent_refs, role, child_dob, avoid_name=None):
def pick_parent_ref(parent_refs, role, child_dob, avoid_name=None, gender_of=None):
"""Choose the best parent ref for a role from possibly-conflicting chart refs.
A Stammbaum lists an animal at several positions, so its parentRefs can carry
contradictory guesses (the first one is not necessarily right). Prefer a ref
whose own DOB is age-plausible for the child, then a ref with no DOB, and
avoid re-using the other role's animal (full-sibling charts repeat the same
name in both parent slots). Returns the chosen ref dict or None.
contradictory guesses (the first one is not necessarily right). Rank candidates
(lower = better):
0 right/unknown gender for the role, age-plausible dated ref
1 right/unknown gender, no DOB (usable, but a plausible dated ref wins)
2 right/unknown gender, dated but age-impossible
3 resolved gender is clearly WRONG for the role (e.g. a female father)
4 would duplicate the animal chosen for the other role
Gender is decisive over DOB: a dated female ref must not win the father slot
over an undated male/unknown one. `gender_of(name)` returns 'male'/'female'
or None (unknown/ambiguous → not penalised). Returns the chosen ref or None.
"""
role_refs = [p for p in parent_refs if p.get("roleGuess") == role]
if not role_refs:
return None
avoid = normalize_name(avoid_name) if avoid_name else None
expected = "male" if role == "father" else "female"
def rank(p):
if avoid is not None and normalize_name(p.get("name")) == avoid:
return 3 # would duplicate the other parent role
return 4 # would duplicate the other parent role
g = gender_of(p.get("name")) if gender_of else None
if g in ("male", "female") and g != expected:
return 3 # wrong sex for this role
dob = p.get("dob")
if not dob:
return 1 # unknown age — usable, but a plausible-dated ref wins
return 0 if parent_age_plausible(dob, child_dob) else 2 # dated & impossible → last
return 1
return 0 if parent_age_plausible(dob, child_dob) else 2
order = sorted(range(len(role_refs)), key=lambda i: (rank(role_refs[i]), i))
return role_refs[order[0]]
@@ -959,14 +970,35 @@ def main():
key = (normalize_name(f_name), normalize_name(m_name), ldate)
md_litters_idx[key] = rl
# Gender index for parent-ref selection: normalized name → 'male' | 'female'
# | 'ambiguous'. Drives the gender-aware ranking in pick_parent_ref so a dated
# but wrong-sex ref (e.g. female „Danielle“) cannot win the father slot over
# an undated male/unknown one (e.g. „Hagrid Rubeus“).
gender_idx = {}
for a in stammbaum_only_animals:
g = (a.get("gender") or "").lower().strip()
g = g if g in ("male", "female") else None
for key in {normalize_name(a.get("name")), normalize_name(get_call_name(a.get("name") or ""))}:
if not key:
continue
if key not in gender_idx:
gender_idx[key] = g
elif gender_idx[key] != g:
gender_idx[key] = "ambiguous"
def gender_of_name(name):
v = gender_idx.get(normalize_name(name))
return v if v in ("male", "female") else None
# Create virtual litters for stammbaum animals
created_virtual_litters = {}
for a in stammbaum_only_animals:
parent_refs = a.get("parentRefs", [])
child_dob_raw = a.get("dob")
father_ref = pick_parent_ref(parent_refs, "father", child_dob_raw)
father_ref = pick_parent_ref(parent_refs, "father", child_dob_raw, gender_of=gender_of_name)
mother_ref = pick_parent_ref(parent_refs, "mother", child_dob_raw,
avoid_name=father_ref.get("name") if father_ref else None)
avoid_name=father_ref.get("name") if father_ref else None,
gender_of=gender_of_name)
a["_mapped_litter_scoped_id"] = None
if father_ref and mother_ref: