feat(import): merge animals with different DOBs if their parent names match
This commit is contained in:
@@ -612,6 +612,49 @@ def dedup(animals):
|
||||
"files": sorted(set(f for a in grp for f in a["sourceFiles"])),
|
||||
})
|
||||
|
||||
# 2. Merge groups that have different DOBs but same call-name (and Zucht) and matching parents
|
||||
def get_parent_keys(a):
|
||||
parent_refs = a.get("parentRefs", [])
|
||||
f_name = next((p["name"] for p in parent_refs if p.get("roleGuess") == "father"), "")
|
||||
m_name = next((p["name"] for p in parent_refs if p.get("roleGuess") == "mother"), "")
|
||||
return norm_name(f_name), norm_name(m_name)
|
||||
|
||||
def groups_parents_match(g1, g2):
|
||||
for a1 in g1:
|
||||
for a2 in g2:
|
||||
f1, m1 = get_parent_keys(a1)
|
||||
f2, m2 = get_parent_keys(a2)
|
||||
if f1 and f2 and f1 == f2 and m1 and m2 and m1 == m2:
|
||||
return True
|
||||
return False
|
||||
|
||||
i = 0
|
||||
while i < len(final_groups):
|
||||
g1 = final_groups[i]
|
||||
call1, _ = split_name_zucht(g1[0]["name"])
|
||||
n_call1 = norm_name(call1)
|
||||
zucht1 = g1[0].get("_zucht", "")
|
||||
|
||||
j = i + 1
|
||||
merged_any = False
|
||||
while j < len(final_groups):
|
||||
g2 = final_groups[j]
|
||||
call2, _ = split_name_zucht(g2[0]["name"])
|
||||
n_call2 = norm_name(call2)
|
||||
zucht2 = g2[0].get("_zucht", "")
|
||||
|
||||
if n_call1 == n_call2:
|
||||
if zucht1 == zucht2 or not zucht1 or not zucht2:
|
||||
if groups_parents_match(g1, g2):
|
||||
g1.extend(g2)
|
||||
final_groups.pop(j)
|
||||
merged_any = True
|
||||
continue
|
||||
j += 1
|
||||
if merged_any:
|
||||
continue
|
||||
i += 1
|
||||
|
||||
merged = []
|
||||
conflicts = []
|
||||
for grp in final_groups:
|
||||
@@ -652,11 +695,25 @@ def dedup(animals):
|
||||
return sum(1 for pair in gd["mapped8locus"].values() for a in pair if a != "?")
|
||||
best = max((a["genotype"] for a in grp),
|
||||
key=lambda gd: (len(gd["mapped8locus"]), _specificity(gd), len(gd["rawGenotype"])))
|
||||
|
||||
# Find best DOB (proband first)
|
||||
best_dob = ""
|
||||
for a in grp:
|
||||
if a.get("_gen") == 0 and a.get("dob"):
|
||||
best_dob = a["dob"]
|
||||
break
|
||||
if not best_dob:
|
||||
for a in grp:
|
||||
if a.get("dob"):
|
||||
best_dob = a["dob"]
|
||||
break
|
||||
chosen_dob = norm_dob(best_dob or base["dob"])
|
||||
|
||||
out = {
|
||||
"id": slug(base["name"], base["dob"]),
|
||||
"id": slug(base["name"], chosen_dob),
|
||||
"name": base["name"],
|
||||
"nameVariants": sorted(v for v in variants if v),
|
||||
"dob": norm_dob(base["dob"]),
|
||||
"dob": chosen_dob,
|
||||
"death": sorted(deaths)[0] if deaths else "",
|
||||
# box-colour sex (blue=male, white=female): majority across mentions, else None.
|
||||
"gender": Counter(genders).most_common(1)[0][0] if genders else None,
|
||||
|
||||
@@ -1173,6 +1173,17 @@ def main():
|
||||
norm_b, keep_b = get_normalized_contact_name(raw_breeder)
|
||||
raw_breeder = norm_b if keep_b else None
|
||||
|
||||
parent_refs = []
|
||||
if old_litter_id:
|
||||
rl = next((l for l in raw_litters if l.get("_scoped_id") == old_litter_id), None)
|
||||
if rl:
|
||||
f_name = rl.get("FatherName") or rl.get("fatherName") or rl.get("ParentMaleName") or rl.get("parentMaleName") or rl.get("_father_name")
|
||||
m_name = rl.get("MotherName") or rl.get("motherName") or rl.get("ParentFemaleName") or rl.get("parentFemaleName") or rl.get("_mother_name")
|
||||
if f_name:
|
||||
parent_refs.append({"name": f_name, "roleGuess": "father"})
|
||||
if m_name:
|
||||
parent_refs.append({"name": m_name, "roleGuess": "mother"})
|
||||
|
||||
all_processed_gerbils.append({
|
||||
"Id": new_guid,
|
||||
"Name": name_val,
|
||||
@@ -1198,6 +1209,7 @@ def main():
|
||||
"CharacterNote": char_note,
|
||||
"IsDeaf": is_deaf,
|
||||
"IsResident": is_resident,
|
||||
"parentRefs": parent_refs,
|
||||
"_photos": rg.get("photos", []),
|
||||
"_old_scoped_litter_id": old_litter_id,
|
||||
"_eff_dob": eff_dob_val,
|
||||
@@ -1306,6 +1318,7 @@ def main():
|
||||
"CharacterNote": None,
|
||||
"IsDeaf": is_deaf,
|
||||
"IsResident": a_id in stammbaum_resident_ids,
|
||||
"parentRefs": a.get("parentRefs", []),
|
||||
"_photos": a.get("photos", []),
|
||||
"_old_scoped_litter_id": scoped_litter_id,
|
||||
"_eff_dob": dob_val or "2010-01-01",
|
||||
@@ -1364,6 +1377,15 @@ def main():
|
||||
# Residents: if sold/given away, it's not a resident
|
||||
is_resident = not bool(o_name)
|
||||
|
||||
parent_refs = []
|
||||
if scoped_litter_id:
|
||||
dl = next((l for l in docx_litters if docx_litter_id_map.get((l["wsCode"], parse_date(l["dob"]))) == scoped_litter_id), None)
|
||||
if dl:
|
||||
if dl.get("fatherName"):
|
||||
parent_refs.append({"name": dl["fatherName"], "roleGuess": "father"})
|
||||
if dl.get("motherName"):
|
||||
parent_refs.append({"name": dl["motherName"], "roleGuess": "mother"})
|
||||
|
||||
all_processed_gerbils.append({
|
||||
"Id": scoped_id,
|
||||
"Name": name_val,
|
||||
@@ -1389,6 +1411,7 @@ def main():
|
||||
"CharacterNote": None,
|
||||
"IsDeaf": None,
|
||||
"IsResident": is_resident,
|
||||
"parentRefs": parent_refs,
|
||||
"_photos": da.get("photos", []),
|
||||
"_old_scoped_litter_id": scoped_litter_id,
|
||||
"_eff_dob": dob_val or "2020-01-01",
|
||||
@@ -1418,6 +1441,20 @@ def main():
|
||||
bd1 = g1.get("_birth_date")
|
||||
bd2 = g2.get("_birth_date")
|
||||
|
||||
# New rule: if name and parents match, they are compatible regardless of DOB!
|
||||
p1 = g1.get("parentRefs", [])
|
||||
p2 = g2.get("parentRefs", [])
|
||||
f1 = next((p["name"] for p in p1 if p.get("roleGuess") == "father"), "")
|
||||
m1 = next((p["name"] for p in p1 if p.get("roleGuess") == "mother"), "")
|
||||
f2 = next((p["name"] for p in p2 if p.get("roleGuess") == "father"), "")
|
||||
m2 = next((p["name"] for p in p2 if p.get("roleGuess") == "mother"), "")
|
||||
|
||||
parents_match = False
|
||||
if f1 and f2 and m1 and m2:
|
||||
if normalize_name(f1) == normalize_name(f2) and normalize_name(m1) == normalize_name(m2):
|
||||
parents_match = True
|
||||
|
||||
if not parents_match:
|
||||
# If both have explicit birth dates, they must match within 30 days
|
||||
if bd1 and bd2:
|
||||
days1 = date_to_days(bd1)
|
||||
|
||||
@@ -5,14 +5,14 @@ _Automatisch erzeugt von `tools/import/extract.py` — **noch nichts in die Date
|
||||
## Überblick
|
||||
|
||||
- Rohe Tier-Einträge aus den Stammbäumen: **2449**
|
||||
- Nach Zusammenführung (eindeutige Tiere): **1005**
|
||||
- davon mit Geburtsdatum: 680
|
||||
- in mehreren Dateien gefunden (Dubletten zusammengeführt): 460
|
||||
- Konflikte zur Klärung: **2**
|
||||
- Nach Zusammenführung (eindeutige Tiere): **1000**
|
||||
- davon mit Geburtsdatum: 677
|
||||
- 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**
|
||||
- Würfe aus der Wurfchronik: **752**
|
||||
- Tiere mit Wurf verknüpft: **270** (davon über Geburtsdatum **und** Eltern: 165, nur über Geburtsdatum: 105; mehrdeutig: 16)
|
||||
- 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)
|
||||
|
||||
## Zusammenführungs-Schlüssel
|
||||
@@ -35,7 +35,9 @@ Gleiches Tier (Name+Datum), aber widersprüchliche Angaben in verschiedenen Date
|
||||
| Tier | Geburtsdatum | abweichende Genotypen | abweichende Farbschläge | Sterbedaten | Dateien |
|
||||
|---|---|---|---|---|---|
|
||||
| Osamu | 10.12.2015 | AA CC DD ee gg P- spsp // AA CC DD ee gg PP spsp // AA CC DD ee uw[d]uw[d] PP spsp | — | 01.10.2020 // 18.12.2020 | Stammbaum von Danako, Stammbaum von Ella, Stammbaum von Jin, Stammbaum von Kazuya, Stammbaum von Kentucky, Stammbaum von Martin, Stammbaum von Rainny, Stammbaum von Ren, Stammbaum von South Dakota, Stammbaum von Stella Kids, Stammbaum von Tennessee, Stammbaum von Zenon von Elea |
|
||||
| | 20.04.2024 | Aa C- dd Ee Gg P- Spsp | Dilute Agouti Kragenschecke // Dilute Kohlfuchs Kragenschecke DP | — | Stammbaum von Fire Kids, Stammbaum von Stella Kids |
|
||||
| Hanami | 10.09.2015 | aa Cc[chm] D- Ee gg P- spsp // aa Cc[chm] D- Ee uw[d]uw[d] P- spsp | — | 02.01.2020 // 12.12.2019 // 14.01.2020 | Stammbaum von Hana, Stammbaum von Kentucky, Stammbaum von Rainny, Stammbaum von Ren, Stammbaum von Stella Kids, Stammbaum von Vance, Stammbaum von Zac (Vance.Dorie) |
|
||||
| | 13.08.2025 | Aa C- D- ee[f] G(G) pp Spsp // aa Cc[chm] D- ee[f] Gg Pp Spsp | Goldfuchsschimmel Kragenschecke // Kohlfuchsschimmel, hell | — | Stammbaum von Kohlief, Goldfuchsef Sp von Chrissi, Stammbaum von Watarus Kids |
|
||||
|
||||
## Mehrdeutige / unvollständige Einträge
|
||||
|
||||
@@ -173,11 +175,11 @@ Diese Tokens stehen weiter in `rawGenotype`/`unmappedTokens` — Entscheidung (M
|
||||
| `/+Dezember'2014` | 1 | ? |
|
||||
| `(Ansatz)` | 1 | ? |
|
||||
| `-psp` | 1 | ? |
|
||||
| `G(G)` | 1 | ? |
|
||||
| `!Niereninsuffizienz!` | 1 | ? |
|
||||
| `+09.07.2017` | 1 | ? |
|
||||
| `+2018` | 1 | ? |
|
||||
| `AAA` | 1 | ? |
|
||||
| `chmchm` | 1 | Schreibweise (c[chm]c[chm]) |
|
||||
|
||||
## Wurfchronik — Datenqualitäts-Hinweise
|
||||
|
||||
|
||||
Reference in New Issue
Block a user