PEDIGREE-LINK: chart parentRefs→litters, box-colour=sex, name-bleed fix
Structural fix (god, Julian-reported via 'C'): the loader ignored
SourceAnimal.ParentRefs, so animals whose ancestry exists only as
Stammbaum chart-position refs loaded with LitterId=null ("unbekannt").
ImportService now synthesizes/reuses a derived litter from parentRefs:
resolves father+mother via name+DOB, groups siblings (same parents+dob)
into one litter, sets Father/Mother + offspring LitterId, dates it to the
offspring DOB, and tags Notes "aus Stammbaum-Diagramm abgeleitet
(Konfidenz: …)" so it's transparent/reversible. Existing animals that
become linkable are re-linked on re-run (sweep-idempotent). Dry-run counts
included. Projected: ~124 loadable animals gain a parent link.
Box-colour = sex (Julian): blue box = male, white box = female. All 11
pedigrees encode this as a solid theme-8 (accent5/blue) fill vs no fill.
xlsx_util.cell_fill_sex reads it; extract.py sets animal.gender from the
box; ImportService.InferGender prefers it over sire/dam name inference.
Result: 306/306 loadable animals now sexed (154♂/152♀).
Extractor noise fix (god): reject Farbschlag values that are actually a
parent NAME bled across cells (contain v.d./von/of/gen.) — cleared phantom
conflicts (e.g. Chayton). Combined with GEN-3 Uw→G: Konflikte 32→21.
Also skip Excel "~$" lock files in the glob.
GEN-3a contract (Kevin): ComposeGenotype appends "Slsl" for WP/Sls
carriers (wild-type sl/sl omitted) so 8-locus strings stay unchanged.
Importer-only. The live re-import into Julian's DB stays a separate
supervised gated step. 95 C# tests + python genotype tests green;
has-pending-model-changes clean (no schema change on this branch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import glob
|
||||
import shutil
|
||||
import argparse
|
||||
import unicodedata
|
||||
from collections import Counter
|
||||
|
||||
import xlsx_util as xu
|
||||
import genotype as gt
|
||||
@@ -96,6 +97,18 @@ ZUCHT_ALIASES = {
|
||||
"zdkc": "kleinechaote", # "Zucht der kleinen Chaoten" (home cattery shorthand)
|
||||
}
|
||||
|
||||
# A Farbschlag value must NOT contain cattery/line connectors (v.d./von/of/gen.) — when it
|
||||
# does, a parent's NAME has bled into the Farbschlag cell (cross-cell chart read, PEDIGREE-LINK
|
||||
# bug: e.g. "Victoria Welby gen. Welby v.d. Kleinen Chaoten" became a Farbschlag variant and
|
||||
# spawned a phantom conflict). Reject such values so they don't pollute farbschlag/conflicts.
|
||||
_NAME_MARKER = re.compile(r"\bv\.\s?d\.|\bvon\b|\bof\b|\bgen\.", re.IGNORECASE)
|
||||
|
||||
|
||||
def looks_like_animal_name(text):
|
||||
"""True if a candidate Farbschlag cell actually looks like an animal name (has a
|
||||
cattery/line connector). Real Farbschläge are short colour words without these."""
|
||||
return bool(_NAME_MARKER.search(text or ""))
|
||||
|
||||
|
||||
def split_name_zucht(raw):
|
||||
"""'Luna [ZdkC]' -> ('Luna','ZdkC'); 'Pikachu of Black Forest' ->
|
||||
@@ -159,6 +172,7 @@ def extract_stammbaum(path):
|
||||
ss = xu.shared_strings(z)
|
||||
sheets = xu.sheet_paths(z)
|
||||
cells = xu.read_cells(z, sheets[0], ss)
|
||||
fillsex = xu.cell_fill_sex(z, sheets[0]) # box colour -> sex (blue=male, white=female)
|
||||
|
||||
# group cells by column for block reconstruction
|
||||
by_col = {}
|
||||
@@ -207,7 +221,8 @@ def extract_stammbaum(path):
|
||||
elif re.search(r"\b(Zucht|Privatzucht)\b", cell) or cell.startswith("("):
|
||||
breeder = cell
|
||||
used.add((c, rr))
|
||||
elif not farbschlag and not re.match(r"^\*?\s?\d", cell):
|
||||
elif not farbschlag and not re.match(r"^\*?\s?\d", cell) \
|
||||
and not looks_like_animal_name(cell):
|
||||
farbschlag = cell
|
||||
used.add((c, rr))
|
||||
used.add((c, r))
|
||||
@@ -224,7 +239,7 @@ def extract_stammbaum(path):
|
||||
"nameVariants": [],
|
||||
"dob": dob,
|
||||
"death": death,
|
||||
"gender": None,
|
||||
"gender": fillsex.get((c, r)), # box colour: blue=male, white=female
|
||||
"farbschlag": farbschlag,
|
||||
"genotype": genodict,
|
||||
"deaf": genodict.get("deaf"),
|
||||
@@ -536,11 +551,14 @@ def dedup(animals):
|
||||
deaths = set()
|
||||
deaf_seen = set()
|
||||
tags_set = set()
|
||||
genders = []
|
||||
for a in grp:
|
||||
variants.add(a["name"])
|
||||
files.update(a["sourceFiles"])
|
||||
photos.extend(a["photos"])
|
||||
parent_refs.extend(a["parentRefs"])
|
||||
if a.get("gender"):
|
||||
genders.append(a["gender"])
|
||||
if a["genotype"]["mapped8locus"]:
|
||||
genos.add(a["genotype"]["rawGenotype"])
|
||||
geno_keys.add(_geno_key(a["genotype"]))
|
||||
@@ -560,7 +578,8 @@ def dedup(animals):
|
||||
"nameVariants": sorted(v for v in variants if v),
|
||||
"dob": norm_dob(base["dob"]),
|
||||
"death": sorted(deaths)[0] if deaths else "",
|
||||
"gender": None,
|
||||
# box-colour sex (blue=male, white=female): majority across mentions, else None.
|
||||
"gender": Counter(genders).most_common(1)[0][0] if genders else None,
|
||||
"farbschlag": sorted(farb)[0] if farb else "",
|
||||
"farbschlagVariants": sorted(farb),
|
||||
"genotype": best,
|
||||
@@ -829,7 +848,9 @@ def main():
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
raw_animals = []
|
||||
files = sorted(glob.glob(os.path.join(args.stammbaeume, "*.xlsx")))
|
||||
# Skip Excel lock/owner files ("~$...") that appear while a workbook is open.
|
||||
files = sorted(f for f in glob.glob(os.path.join(args.stammbaeume, "*.xlsx"))
|
||||
if not os.path.basename(f).startswith("~$"))
|
||||
print(f"Stammbaum-Dateien: {len(files)}")
|
||||
for path in files:
|
||||
got = extract_stammbaum(path)
|
||||
|
||||
Reference in New Issue
Block a user