fix: Naho Eltern + Foto-Layout-Erkennung + Eltern-Fallback-Lookup
This commit is contained in:
@@ -257,6 +257,14 @@
|
||||
"father": "Wilbur von den Kleinen Chaoten",
|
||||
"mother": "Naho von den Kleinen Chaoten",
|
||||
"source": "Stammbaum von Kazuya.xlsx"
|
||||
},
|
||||
{
|
||||
"name": "Naho von den Kleinen Chaoten",
|
||||
"dob": "20.07.2017",
|
||||
"decision": "father = Osamu von den Kleinen Chaoten, mother = Montana v.d. Kleinen Chaoten (from Stammbaum von Kazuya.xlsx)",
|
||||
"father": "Osamu von den Kleinen Chaoten",
|
||||
"mother": "Montana v.d. Kleinen Chaoten",
|
||||
"source": "Stammbaum von Kazuya.xlsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -340,13 +340,37 @@ def _attach_photos(z, sheets, animals, fname):
|
||||
anchors = [a for a in xu.image_anchors(z)]
|
||||
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)
|
||||
|
||||
def get_anchor_gen(colnum, offset=0):
|
||||
effective_col = colnum - offset
|
||||
if left_style:
|
||||
if effective_col <= 3: return 0
|
||||
if effective_col <= 6: return 1
|
||||
if effective_col <= 9: return 2
|
||||
if effective_col <= 12: return 3
|
||||
if effective_col <= 15: return 4
|
||||
return 5
|
||||
else:
|
||||
if effective_col <= 4: return 0
|
||||
if effective_col <= 7: return 1
|
||||
if effective_col <= 10: return 2
|
||||
if effective_col <= 13: return 3
|
||||
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 = gen_of(col, col_offset)
|
||||
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
|
||||
@@ -362,7 +386,8 @@ def _attach_photos(z, sheets, animals, fname):
|
||||
try:
|
||||
with z.open(media) as src, open(os.path.join(OUT, rel), "wb") as dst:
|
||||
shutil.copyfileobj(src, dst)
|
||||
target["photos"].append(rel)
|
||||
if rel not in target["photos"]:
|
||||
target["photos"].append(rel)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ _Automatisch erzeugt von `tools/import/extract.py` — **noch nichts in die Date
|
||||
- in mehreren Dateien gefunden (Dubletten zusammengeführt): 462
|
||||
- Konflikte zur Klärung: **4**
|
||||
- Mehrdeutige / unvollständige Einträge (ohne Name+Datum): **342**
|
||||
- Fotos zugeordnet: **416**
|
||||
- Fotos zugeordnet: **422**
|
||||
- Würfe aus der Wurfchronik: **752**
|
||||
- Tiere mit Wurf verknüpft: **269** (davon über Geburtsdatum **und** Eltern: 165, nur über Geburtsdatum: 104; mehrdeutig: 16)
|
||||
- Würfe mit Datenqualitäts-Hinweisen: 113 (+ 138 Zeilen mit abweichendem Spaltenschema)
|
||||
@@ -19,6 +19,17 @@ _Automatisch erzeugt von `tools/import/extract.py` — **noch nichts in die Date
|
||||
|
||||
Tiere wurden zusammengeführt über **normalisierter Rufname + Geburtsdatum**, mit der **Zucht als Unterscheidungsmerkmal** (Julians Regel: die `[Klammern]` in der Wurfchronik und das `of/von <Linie>`-Suffix der Stammbäume bezeichnen beide die Zucht und werden zusammengeführt — z. B. `[ZdkC]` ≙ `von den Kleinen Chaoten`). Namensvarianten (z. B. `v.d.` ↔ `von den`, `gen.`-Spitznamen) werden als `nameVariants` erhalten.
|
||||
|
||||
### Erweiterte Zusammenführungsregel: Gleicher Name + gleiche Eltern
|
||||
|
||||
Wenn zwei Einträge denselben Rufnamen **und** dieselben Eltern (Vater + Mutter) tragen, werden sie als dasselbe Tier betrachtet — auch wenn das Geburtsdatum abweicht. Das DOB des Eintrags, in dem das Tier Proband ist (`_gen == 0`), hat Priorität. Diese Regel greift als Sicherheitsnetz für Datenfehler beim Geburtsdatum.
|
||||
|
||||
**Im aktuellen Datensatz ausgelöst für:**
|
||||
|
||||
| Tier | DOB (falsch) | DOB (korrekt) | Vater | Mutter | Lösung |
|
||||
|---|---|---|---|---|---|
|
||||
| Kazuya von den Kleinen Chaoten | 22.08.2018 | 14.07.2019 | Wilbur von den Kleinen Chaoten | Naho von den Kleinen Chaoten | `correctDob`-Eintrag in conflict-decisions.json → DOB vor Dedup remapped |
|
||||
|
||||
|
||||
### Gleicher Name + Geburtsdatum, aber unterschiedliche Zucht (NICHT zusammengeführt — bitte prüfen)
|
||||
|
||||
| Tier | Geburtsdatum | Zuchten | Dateien |
|
||||
|
||||
@@ -111,10 +111,15 @@ def main():
|
||||
# Index animals for fast lookup by slug ID and by normalized name+dob
|
||||
animal_by_id = {a["id"]: a for a in animals}
|
||||
animal_by_name_dob = {}
|
||||
animal_by_name = {}
|
||||
for a in animals:
|
||||
key = (ex.norm_name(a["name"]), ex.norm_dob(a["dob"]))
|
||||
if key[0] and key[1]:
|
||||
animal_by_name_dob.setdefault(key, []).append(a)
|
||||
nk = ex.norm_name(a["name"])
|
||||
if nk:
|
||||
animal_by_name.setdefault(nk, []).append(a)
|
||||
|
||||
|
||||
# 2. Extract and resolve unique Contacts (Breeders & Zuchten)
|
||||
print("Extracting unique contacts...")
|
||||
@@ -163,8 +168,36 @@ def main():
|
||||
l_parents = litter_parent_map.setdefault(l_id, [None, None]) # [father, mother]
|
||||
|
||||
for p_ref in a.get("parentRefs", []):
|
||||
p_key = (ex.norm_name(p_ref["name"]), ex.norm_dob(p_ref["dob"]))
|
||||
p_dob = ex.norm_dob(p_ref.get("dob", ""))
|
||||
p_key = (ex.norm_name(p_ref["name"]), p_dob)
|
||||
p_candidates = animal_by_name_dob.get(p_key, [])
|
||||
if not p_candidates:
|
||||
# Fallback to name-only lookup when parent DOB is empty/not found
|
||||
p_norm = ex.norm_name(p_ref["name"])
|
||||
candidates = animal_by_name.get(p_norm, [])
|
||||
if candidates:
|
||||
offspring_dob_str = parse_date_only(a["dob"])
|
||||
if offspring_dob_str:
|
||||
try:
|
||||
o_dob = datetime.strptime(offspring_dob_str, "%Y-%m-%d")
|
||||
valid_candidates = []
|
||||
for cand in candidates:
|
||||
cand_dob_str = parse_date_only(cand["dob"])
|
||||
if cand_dob_str:
|
||||
try:
|
||||
c_dob = datetime.strptime(cand_dob_str, "%Y-%m-%d")
|
||||
if c_dob < o_dob:
|
||||
valid_candidates.append(cand)
|
||||
except ValueError:
|
||||
valid_candidates.append(cand)
|
||||
else:
|
||||
valid_candidates.append(cand)
|
||||
if valid_candidates:
|
||||
p_candidates = [valid_candidates[0]]
|
||||
except ValueError:
|
||||
p_candidates = [candidates[0]]
|
||||
else:
|
||||
p_candidates = [candidates[0]]
|
||||
if p_candidates:
|
||||
p_guid = animal_guid_map[p_candidates[0]["id"]]
|
||||
if p_ref["roleGuess"] == "father":
|
||||
|
||||
Reference in New Issue
Block a user