diff --git a/tools/import/conflict-decisions.json b/tools/import/conflict-decisions.json index cc3fe0a..7441b59 100644 --- a/tools/import/conflict-decisions.json +++ b/tools/import/conflict-decisions.json @@ -414,6 +414,27 @@ "mother": "Zaibunissa von den Kleinen Chaoten", "motherDob": "21.02.2020", "source": "Züchterin 2026-06-23 — Ticket ba63325a" + }, + { + "name": "Bentley", + "externalRef": "Wurfchronik Teil 1_page_0054.md-50505050-0003-4000-8000-000000000003", + "isResident": false, + "decision": "Nur Vorfahre (Clan of Black Forest), kein eigenes Zuchttier. Bestätigt von der Züchterin.", + "source": "Züchterin 2026-06-23 — Ticket 09bcac78" + }, + { + "name": "Alexandria", + "externalRef": "Wurfchronik Teil 1_page_0054.md-50505050-0004-4000-8000-000000000004", + "isResident": false, + "decision": "Nur Vorfahre, kein eigenes Zuchttier (wie Bentley).", + "source": "Züchterin 2026-06-23 — Ticket 45cc501b" + }, + { + "name": "Bugatti", + "externalRef": "Wurfchronik Teil 1_page_0054.md-40404040-0003-4000-8000-000000000003", + "isResident": false, + "decision": "Bugatti stammt aus Clan of Black Forest, war nie ihr Zuchttier — nur Vorfahre.", + "source": "Züchterin 2026-06-23 — Ticket 09bcac78" } ], "addAnimals": [ diff --git a/tools/import/merge_and_resolve.py b/tools/import/merge_and_resolve.py index 03511ee..db6fc88 100644 --- a/tools/import/merge_and_resolve.py +++ b/tools/import/merge_and_resolve.py @@ -3016,6 +3016,51 @@ def main(): print(f"Deduplicated to {len(resolved_gerbils)} unique gerbil records.") + # ── isResident-Overrides (Mensch-Entscheidung) ──────────────────────────── + # NACH dem Dedup anwenden, sonst würde die Dedup-Zusammenführung (IsResident=True + # falls eine Variante resident ist) sie wieder überschreiben. Matcht primär über + # `externalRef` (präzise, auch für namenlose Tiere) oder sonst über + # normalize(call-name)+ISO-dob (leere dob = name-only, für Vorfahren ohne Datum). + _isres_by_extref = {} + _isres_by_namedob = {} + for d in _decisions: + if "isResident" not in d: + continue + val = bool(d["isResident"]) + er = (d.get("externalRef") or "").strip() + if er: + _isres_by_extref[er] = val + nm = d.get("name") + if nm is not None: + ck = normalize_name(get_call_name(nm or "")) + iso = parse_date(d.get("dob")) if d.get("dob") else "" + _isres_by_namedob[(ck, iso or "")] = val + _isres_applied = 0 + if _isres_by_extref or _isres_by_namedob: + for g in resolved_gerbils: + er = g.get("ExternalRef") or "" + ck = normalize_name(get_call_name(g.get("Name") or "")) + iso = g.get("DateOfBirth") or "" + if er and er in _isres_by_extref: + val = _isres_by_extref[er] + elif er and any(er.endswith(k) for k in _isres_by_extref): + val = next(v for k, v in _isres_by_extref.items() if er.endswith(k)) + elif (ck, iso) in _isres_by_namedob: + val = _isres_by_namedob[(ck, iso)] + elif (ck, "") in _isres_by_namedob: + val = _isres_by_namedob[(ck, "")] + else: + continue + if g.get("IsResident") != val: + g["IsResident"] = val + # Ein Nicht-Bestandstier ist kein eigenes Zuchttier → den eigenen + # Zucht-Breeder entfernen (nur, wenn er auf die eigene Zucht zeigt). + if not val and g.get("OriginBreeder") == "Zucht der kleinen Chaoten": + g["OriginBreeder"] = None + _isres_applied += 1 + if _isres_applied: + print(f"isResident-Overrides angewandt: {_isres_applied}") + # Apply age-based death threshold (6.0 years) to all resolved gerbils dt_now = datetime.now() for g in resolved_gerbils: diff --git a/tools/import/test_merge_resolve.py b/tools/import/test_merge_resolve.py index fc2bd1f..baffe27 100644 --- a/tools/import/test_merge_resolve.py +++ b/tools/import/test_merge_resolve.py @@ -559,17 +559,30 @@ if _os.path.exists(_resolved): # Sunny von PZ Karl: father corrected Hiro → Bill von Privat. _check_parents("Sunny (parents)", "Sunny von PZ Karl", "Bill von Privat", "Melly von Privat") - # Danielle: mother = Ella *10.06.2019 (the elder one), father unknown. + # Danielle: mother = Ella *10.06.2019, father = Makoto (sibling pairing; Ticket 4692fd5c). _dan = _find("Danielle von den Kleinen") _df, _dm = _parents(_dan) check("Danielle: mother is Ella", (_dm or "") == "Ella") - check("Danielle: father unknown (sibling pairing)", _df is None) + check("Danielle: father is Makoto", (_df or "").startswith("Makoto")) if _dan and _dan.get("LitterId"): _dl = _L.get(_dan["LitterId"]) _dmom = _G.get(_dl.get("MotherId")) if _dl else None check("Danielle: mother Ella is the *2019-06-10 one (not the *2023 Ella)", _dmom is not None and _dmom.get("DateOfBirth") == "2019-06-10") + # Catelyn (Ticket 7bbc045c): father Eddard Stark of Sunset Glow, mother Milena. + _check_parents("Catelyn", "Catelyn Stark von den Kleinen", "Eddard Stark", "Milena") + + # Gaida (Ticket ba63325a): Geschwisterverpaarung Zhuāngzǐ × Zaibunissa (beide *2020-02-21). + _check_parents("Gaida", "Gaida von den Kleinen Chaoten", "Zhuāngzǐ", "Zaibunissa") + + # Bentley / Alexandria / Bugatti (Ticket 09bcac78 / 45cc501b): nur Vorfahren → isResident False. + for _tag, _ref in [("Bentley", "Wurfchronik Teil 1_page_0054.md-50505050-0003-4000-8000-000000000003"), + ("Alexandria", "Wurfchronik Teil 1_page_0054.md-50505050-0004-4000-8000-000000000004"), + ("Bugatti", "Wurfchronik Teil 1_page_0054.md-40404040-0003-4000-8000-000000000003")]: + _g = next((g for g in _d["gerbils"] if g.get("ExternalRef") == _ref), None) + check(f"{_tag}: isResident override == False", _g is not None and _g.get("IsResident") is False) + # Cherry Berry's Quqquluuruu: gender override female (box colour misread). _cherry = _find("Cherry Berry") check("Cherry Berry: gender override female",