feat(import): neue Stammbäume (12 Charts) + litterChildren.add + EMF-Fotos überspringen
Die Züchterin hat 24 neue/aktualisierte Stammbaum-xlsx geliefert (Ordner
"neuestammbäume"); sie liegen jetzt im kanonischen Quellverzeichnis
Sttammbäume (12 neue Charts, 7 aktualisierte, 3 identisch, das inhaltsgleiche
"Picus Son (2)" ausgelassen). Prod ist per Upload-Ingest aktualisiert:
2372 -> 2451 Tiere, 916 -> 965 Würfe, 432 -> 507 Fotos, 2198 -> 2275 Tiere
mit Geburtsdatum. Overrides/verified-Zeilen, manuelle Tiere und Tickets
haben den Ingest unverändert überlebt.
Zwei Datenfehler, die die neuen Charts aufgedeckt haben — datengetrieben und
re-ingest-stabil gefixt statt an der globalen Heuristik zu drehen:
- litterChildren kennt jetzt `add` [Name | {name, dob}] als Gegenstück zu
`keep`: hängt ein Jungtier an DIESEN Wurf und entfernt den alten Wurf, wenn
er dadurch kinderlos UND virtuell ist. Nötig, weil "Pukas Kids" Akanes
Eltern komplett UNTER ihren Block setzt (N80 Roni = Vater, N81 Fumi =
Mutter) — _reconstruct_parents griff eine Zeile zu hoch, paarte Irish
Coffee (Bonapartes Mutter) mit Roni und riss Akane aus dem Z21-Wurf in
einen Phantom-Wurf, der in der Wurfchronik auftauchte (Ticket 88389f8e).
- Merle: durch das neue Geburtsdatum (18.06.2023) mergt der addAnimals-Stub
in den Chart-Datensatz und verliert dabei sein isResident -> expliziter
resolutions-Override (Ticket 36a3fcde/a8f11ac0, Züchterin: Zuchttier).
Außerdem: Excel legt neben Fotos teils EMF/WMF-Vektorvorschauen ab, die
Browser nicht darstellen können (kaputte Bildkachel in der Tier-Akte) ->
extract._attach_photos überspringt .emf/.wmf (5 Fotos betroffen).
Regressionstests für alle drei Punkte; alle Python-Suites grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4166,17 +4166,45 @@ def main():
|
||||
# Würfe). Alle Kinder, deren Ruf-Name NICHT in `keep` steht, werden aus dem Wurf gelöst
|
||||
# (LitterId=None) — sie verschwinden aus dem Wurf, bleiben aber als Tier erhalten. Optional
|
||||
# `renameTo` setzt den kanonischen Wurf-Namen (z. B. 'Ungeklärt' -> 'M-Wurf').
|
||||
# Optional `add` [Name | {name, dob}] hängt ein Jungtier an DIESEN Wurf (Gegenstück zu `keep`):
|
||||
# nötig, wenn die Chart-Positions-Heuristik ein Jungtier in einen falschen (virtuellen)
|
||||
# Stammbaum-Wurf gezogen hat, obwohl die Wurfchronik es einem echten Wurf zuordnet
|
||||
# (Ticket 36a3fcde/88389f8e: Akane gehört in den Z21-Wurf Roni × Fumi). Der vorherige Wurf
|
||||
# wird entfernt, wenn er dadurch ein kinderloser VIRTUELLER Wurf wird — sonst blieben
|
||||
# Phantom-Würfe in der Wurfchronik stehen.
|
||||
try:
|
||||
with open(CONFLICT_DECISIONS_PATH, encoding="utf-8") as _lcfh:
|
||||
_litter_children = (json.load(_lcfh).get("litterChildren") or [])
|
||||
except Exception:
|
||||
_litter_children = []
|
||||
|
||||
def _drop_if_empty_virtual(litter_id):
|
||||
"""Entfernt einen VIRTUELLEN Wurf, der kein Jungtier mehr hat (Phantom-Wurf aus der
|
||||
Chart-Positions-Heuristik). Echte Wurfchronik-Würfe bleiben immer erhalten — die haben
|
||||
legitim keine namentlich erfassten Jungtiere."""
|
||||
if not litter_id:
|
||||
return 0
|
||||
if any(g.get("LitterId") == litter_id for g in resolved_gerbils):
|
||||
return 0
|
||||
for i, cand in enumerate(resolved_litters):
|
||||
if cand["Id"] != litter_id:
|
||||
continue
|
||||
ext = cand.get("ExternalRef") or ""
|
||||
if cand.get("_virtual") or "-virtual-" in ext or ext.startswith("override-virtual-litter"):
|
||||
del resolved_litters[i]
|
||||
return 1
|
||||
return 0
|
||||
return 0
|
||||
|
||||
_lc_removed = 0
|
||||
_lc_added = 0
|
||||
_lc_dropped = 0
|
||||
for lc in _litter_children:
|
||||
lc_date = parse_date(lc["date"]) if lc.get("date") else None
|
||||
match_child = normalize_name(get_call_name(lc.get("matchChild") or ""))
|
||||
keep = {normalize_name(get_call_name(n)) for n in (lc.get("keep") or [])}
|
||||
if not keep:
|
||||
add = lc.get("add") or []
|
||||
if not keep and not add:
|
||||
continue
|
||||
for l in resolved_litters:
|
||||
if lc_date and l.get("Date") != lc_date:
|
||||
@@ -4185,15 +4213,29 @@ def main():
|
||||
names = {normalize_name(get_call_name(g.get("Name") or "")) for g in kids}
|
||||
if match_child and match_child not in names:
|
||||
continue # falscher, nur zufällig gleich-datierter Wurf
|
||||
for g in kids:
|
||||
if normalize_name(get_call_name(g.get("Name") or "")) not in keep:
|
||||
g["LitterId"] = None
|
||||
_lc_removed += 1
|
||||
if keep:
|
||||
for g in kids:
|
||||
if normalize_name(get_call_name(g.get("Name") or "")) not in keep:
|
||||
g["LitterId"] = None
|
||||
_lc_removed += 1
|
||||
for entry in add:
|
||||
a_name = entry.get("name") if isinstance(entry, dict) else entry
|
||||
a_dob = entry.get("dob") if isinstance(entry, dict) else None
|
||||
child = _find_final_gerbil(a_name, a_dob)
|
||||
if not child or child.get("LitterId") == l["Id"]:
|
||||
continue
|
||||
prev_litter_id = child.get("LitterId")
|
||||
child["LitterId"] = l["Id"]
|
||||
_lc_added += 1
|
||||
_lc_dropped += _drop_if_empty_virtual(prev_litter_id)
|
||||
if lc.get("renameTo"):
|
||||
l["Name"] = lc["renameTo"]
|
||||
break
|
||||
if _lc_removed:
|
||||
print(f"litterChildren: {_lc_removed} fälschlich zugeordnete Jungtiere aus Würfen gelöst")
|
||||
if _lc_added:
|
||||
print(f"litterChildren: {_lc_added} Jungtier(e) dem korrekten Wurf zugeordnet "
|
||||
f"({_lc_dropped} leerer Phantom-Wurf/Würfe entfernt)")
|
||||
|
||||
# ── isResident-Sweep (Ticket 381f7e51, freigegeben) ───────────────────────
|
||||
# Entscheidung der Züchterin: „Nimm wirklich ALLE Tiere aus dem Bestand heraus,
|
||||
|
||||
Reference in New Issue
Block a user