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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user