FEAT-8b: bake Julian's authoritative Wurfchronik semantics into the extractor

- survivedToGoHome: unlabeled Tabelle1 col E detected positionally; value-
  adaptive row parsing recovers it from schema-shifted Tabelle2 rows too
  (118 recovered, 79 confirmed by the E=F-TG-s identity)
- breakdown G -> males/females/stillborn/diedLater ('s' = died before Abgabe)
- validation: E = F - TG - s; 113 mismatches as German review-report warnings
- (name, Zucht) canonicalisation: [brackets] == of/von suffix ([ZdkC] ==
  von den Kleinen Chaoten); Zucht = dedup discriminator (0 splits in data)
- animal->litter matching via DOB+(Vater,Mutter): 95 high-confidence,
  40 date-only, 9 ambiguous; litterRef in animals.json
- regenerated report: 889 raw -> 574 unique (279 dated), 32 conflicts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 00:56:14 +02:00
parent a9778ad1d2
commit 7ea2f97e72
3 changed files with 475 additions and 43 deletions

View File

@@ -19,12 +19,29 @@ See the format analysis in `FEAT-8a-format-spec.md` (Pam's hive workspace).
- Embedded photos (`xl/media`) → `output/photos/<animal-slug>/`, mapped to the
animal by drawing anchor position.
2. **Dedup + review (stage 2)**
- Merge animals on `normalise(name) + DOB` (corroborated by DOB+genotype).
- Merge animals on `normalise(call-name) + DOB`, with the **Zucht as
discriminator** (Julian's ruling: Wurfchronik `[brackets]` ≡ Stammbaum
`of/von <line>` suffix — both are the breeding line; same name+DOB but
different Zucht stays two animals).
- Match animals onto Wurfchronik litters (`litterRef`) via DOB + (Vater,
Mutter) — the Pam-validated build order (chronicle litters are canonical).
- Emit a German-language `output/review-report.md` for the breeder to verify
(merges, **conflicts**, ambiguous/incomplete entries, unmapped genotype tokens).
(merges, **conflicts**, ambiguous/incomplete entries, unmapped genotype
tokens, litter data-quality warnings).
- **Nothing is loaded into the database** — stage 3 (API load) is separate and
waits on DATA-2 + FEAT-1b phase 2.
### Wurfchronik column semantics (Julian, authoritative)
`A` Wurfbezeichnung · `B` Geburtsdatum · `C` Mutter · `D` Vater (`[…]` = Zucht,
`&` = multiple sires) · `E` **survivedToGoHome** (Tabelle1 only, unlabeled —
detected positionally) · `F` Wurfstärke → `totalBorn` · `G` breakdown
`Männchen,Weibchen,TG,s``males/females/stillborn/diedLater` (`s` = died
after birth, before Abgabe) · last column → `note`. Validation: `E` should
equal `F TG s`; mismatches become German warnings in the review report
(data-quality signal, not an import blocker). A few Tabelle2 rows shift these
columns — they are read value-adaptively and flagged with a warning.
Genotypes are mapped to the frozen 8-locus contract (A C D E G P Sp Re) while
preserving everything: `genotype.mapped8locus`, `genotype.rawGenotype` (verbatim),
`genotype.unmappedTokens` (e.g. the `Uw` locus, markers `WFNZ/WP/DP`). A `-`

View File

@@ -83,6 +83,53 @@ def clean_name(raw):
return n
# --- (name, Zucht) canonicalisation -------------------------------------------
# Julian (FEAT-8 ruling): the [brackets] in Wurfchronik names ARE the Zucht
# (breeding line) and are equivalent to the Stammbaum "of/von <line>" suffix.
# Both fold into one canonical (call-name, zucht) pair; the Zucht acts as a
# dedup DISCRIMINATOR (same name+DOB but different Zucht = different animal).
_BRACKET_ZUCHT = re.compile(r"^(.*?)\s*\[([^\]]+)\]\s*$")
_SUFFIX_ZUCHT = re.compile(
r"^(.+?)\s+(?:of|von\s+den|von\s+der|v\.\s?d\.|von)\s+(.+)$", re.IGNORECASE)
# canonical values are post-norm_zucht (word-final 'n' folded: "kleinechaote")
ZUCHT_ALIASES = {
"zdkc": "kleinechaote", # "Zucht der kleinen Chaoten" (home cattery shorthand)
}
def split_name_zucht(raw):
"""'Luna [ZdkC]' -> ('Luna','ZdkC'); 'Pikachu of Black Forest' ->
('Pikachu','Black Forest'); plain names -> (name, '')."""
n = (raw or "").strip()
m = _BRACKET_ZUCHT.match(n)
if m:
return m.group(1).strip(), m.group(2).strip()
m = _SUFFIX_ZUCHT.match(n)
if m:
return m.group(1).strip(), m.group(2).strip()
return n, ""
def norm_zucht(z):
"""Canonical Zucht key: drops Zucht/von/der fillers, folds declension
('kleinen Chaoten' == 'kleine Chaoten'), resolves known shorthands."""
if not z:
return ""
n = z.lower()
n = re.sub(r"\bv\.\s?d\.\b", " ", n)
n = re.sub(r"\b(zucht|privatzucht|der|die|den|des|dem|von|of)\b", " ", n)
n = re.sub(r"[^a-z0-9äöüß ]", " ", n)
words = [w[:-1] if len(w) > 4 and w.endswith("n") else w for w in n.split()]
key = "".join(words)
return ZUCHT_ALIASES.get(key, key)
def canon_pair(raw):
"""Full raw name -> (normalised call-name, canonical zucht)."""
name, zucht = split_name_zucht(raw)
return norm_name(name), norm_zucht(zucht)
# --------------------------------------------------- Stammbaum extraction ----
def parse_detail(text):
"""From a string that contains *DOB and/or genotype, pull (dob, death, geno_str).
@@ -167,6 +214,10 @@ def extract_stammbaum(path):
g = parse_detail(t) if compact else (dob, death, geno)
genodict = gt.parse(geno)
# Zucht: from the name's [tag]/of-von suffix, else from the breeder line
_, zraw = split_name_zucht(name)
if not zraw and breeder:
zraw = breeder
animals.append({
"id": None, # assigned in dedup
"name": name,
@@ -177,6 +228,7 @@ def extract_stammbaum(path):
"farbschlag": farbschlag,
"genotype": genodict,
"breeder": breeder,
"zucht": zraw,
"parentRefs": [],
"photos": [],
"sourceFiles": [fname],
@@ -184,6 +236,7 @@ def extract_stammbaum(path):
"_col": c,
"_row": r,
"_file": fname,
"_zucht": norm_zucht(zraw),
})
# name-pair cells "X & Y" (deepest generation, names only)
@@ -194,12 +247,14 @@ def extract_stammbaum(path):
for part in t.split(" & "):
part = clean_name(part)
if part:
_, zraw = split_name_zucht(part)
animals.append({
"id": None, "name": part, "nameVariants": [],
"dob": "", "death": "", "gender": None, "farbschlag": "",
"genotype": gt.parse(""), "breeder": "", "parentRefs": [],
"photos": [], "sourceFiles": [fname],
"genotype": gt.parse(""), "breeder": "", "zucht": zraw,
"parentRefs": [], "photos": [], "sourceFiles": [fname],
"_gen": gen_of(c), "_col": c, "_row": r, "_file": fname,
"_zucht": norm_zucht(zraw),
})
_reconstruct_parents(animals)
@@ -297,7 +352,19 @@ def extract_wurfchronik(path):
col_ws = find("ws", "wurfstärke", "wurfstaerke")
col_breakdown = find("männchen", "maennchen", "weibchen")
col_zn = find("zuchtnummer")
col_note = find("bemerkung")
col_note = find("bemerkung", "anmerkung")
# Julian (authoritative): Tabelle1 has an UNLABELED numeric column between
# Vater and Wurfstärke = "Überlebende bis zum Abgabedatum" (survivors to
# go-home). Tabelle2 dropped it. Header-based find() cannot see it, so
# detect it positionally.
col_survived = None
if col_sire and col_ws and col_ws - col_sire > 1:
mapped = {col_id, col_date, col_dam, col_sire, col_ws,
col_breakdown, col_zn, col_note}
for c in range(col_sire + 1, col_ws):
if c not in mapped:
col_survived = c
break
sheet_name = os.path.basename(sp)
for r in sorted(rows):
if r == hdr_row: # skip the header row itself
@@ -310,25 +377,86 @@ def extract_wurfchronik(path):
if "jahr" in " ".join(row.values()).lower() and not DOB.search(txt_b):
continue
dob = DOB.search(txt_b)
bd = row.get(col_breakdown, "") if col_breakdown else ""
m = re.findall(r"\d+", bd)
breakdown = {}
if len(m) >= 1:
keys = ["maennchen", "weibchen", "totgeburt", "s"]
for k, val in zip(keys, m):
breakdown[k] = int(val)
lid = row.get(col_id, "")
datestr = dob.group(1) if dob else ""
dam_raw = row.get(col_dam, "") if col_dam else ""
sire_raw = row.get(col_sire, "") if col_sire else ""
_, dam_zucht = split_name_zucht(dam_raw)
# '&' = multiple sires possible (litter with uncertain/dual sire)
sire_names = [s.strip() for s in sire_raw.split("&") if s.strip()]
_, sire_zucht = split_name_zucht(sire_names[0] if sire_names else "")
# Numeric layout is read VALUE-ADAPTIVELY per row: a handful of
# Tabelle2 rows insert an extra numeric column (Überlebende, T1
# order E,F) before WS and shift the breakdown right of its header.
warnings = []
scan_end = (col_note or (col_sire or 4) + 6) + 1
bd_col = None
for c in range((col_sire or 4) + 1, scan_end):
if re.fullmatch(r"\d+(?:[,;]\d+){1,3}", row.get(c, "")):
bd_col = c
break
if bd_col and col_breakdown and bd_col != col_breakdown:
warnings.append(
f"Spaltenschema-Abweichung: Geschlechter-Aufschlüsselung in "
f"Spalte {xu.num_to_col(bd_col)} statt "
f"{xu.num_to_col(col_breakdown)} gefunden")
# single numeric cells between Vater and the breakdown: E (Überlebende)
# and/or F (Wurfstärke), in T1 order
singles = []
for c in range((col_sire or 4) + 1, bd_col or scan_end):
v = row.get(c, "")
if re.fullmatch(r"\d+", v):
singles.append(int(v))
if len(singles) >= 2:
survived, total_born = singles[0], singles[1]
if not col_survived:
warnings.append(
"Spaltenschema-Abweichung: zusätzliche Zahlenspalte als "
"„Überlebende“ interpretiert (bitte prüfen)")
elif len(singles) == 1:
total_born = singles[0]
survived = None
else:
total_born = _to_int(row.get(col_ws)) if col_ws else None
survived = _to_int(row.get(col_survived)) if col_survived else None
# breakdown "2,0,2,0" = Männchen, Weibchen, Totgeburt, später
# verstorben ('s' = died after birth, before Abgabe — Julian).
bd = row.get(bd_col, "") if bd_col else ""
m = re.findall(r"\d+", bd)
males = females = stillborn = died_later = None
if m:
vals = [int(x) for x in m] + [None] * 4
males, females, stillborn, died_later = vals[:4]
# Validation (Julian): Überlebende E should equal F TG s.
# Mismatch = data-quality signal for the review report, NOT a blocker.
if survived is not None and total_born is not None:
expected = total_born - (stillborn or 0) - (died_later or 0)
if survived != expected:
warnings.append(
f"Überlebende ({survived}) ≠ Wurfstärke ({total_born}) "
f" Totgeburten ({stillborn or 0}) später verstorben "
f"({died_later or 0}) = {expected}")
litters.append({
"id": f"{sheet_name.replace('.xml','')}-{lid}-{norm_dob(datestr)}",
"litterId": lid,
"date": datestr,
"damName": row.get(col_dam, "") if col_dam else "",
"sireName": row.get(col_sire, "") if col_sire else "",
"wurfstaerke": _to_int(row.get(col_ws)) if col_ws else None,
"sexBreakdown": breakdown,
"damName": dam_raw,
"damZucht": dam_zucht,
"sireName": sire_raw,
"sireNames": sire_names,
"sireZucht": sire_zucht,
"totalBorn": total_born,
"survivedToGoHome": survived,
"males": males,
"females": females,
"stillborn": stillborn,
"diedLater": died_later,
"breakdownRaw": bd,
"zuchtnummer": row.get(col_zn, "") if col_zn else "",
"note": row.get(col_note, "") if col_note else "",
"warnings": warnings,
"sourceFile": fname,
"sheet": sheet_name,
"row": r,
@@ -345,20 +473,48 @@ def _to_int(s):
# ------------------------------------------------------------- stage 2: dedup
def dedup(animals):
"""Merge by normalise(name)+DOB. Returns (merged, conflicts, orphans)."""
"""Merge by normalise(call-name)+DOB, with the canonical Zucht as
DISCRIMINATOR (Julian: same name+DOB but different Zucht = different
animal). Returns (merged, conflicts, orphans, zucht_splits)."""
groups = {}
orphans = []
for a in animals:
key = (norm_name(a["name"]), norm_dob(a["dob"]))
call, _ = split_name_zucht(a["name"])
key = (norm_name(call), norm_dob(a["dob"]))
if not key[0] or not key[1]:
orphans.append(a)
# orphans still get a stable id but are not merged
key = ("__orphan__", id(a))
groups.setdefault(key, []).append(a)
# split groups whose members carry DIFFERENT (non-empty) canonical Zuchten;
# members without a Zucht merge into the group only if it is unambiguous.
final_groups = []
zucht_splits = []
for key, grp in groups.items():
by_zucht = {}
for a in grp:
by_zucht.setdefault(a.get("_zucht", ""), []).append(a)
nonempty = sorted(z for z in by_zucht if z)
if len(nonempty) <= 1:
final_groups.append(grp)
continue
# genuine split: same call-name+DOB, different Zucht
for z in nonempty:
final_groups.append(by_zucht[z])
if "" in by_zucht:
# Zucht-less mentions cannot be attributed -> own (flagged) entry
final_groups.append(by_zucht[""])
zucht_splits.append({
"name": grp[0]["name"],
"dob": norm_dob(grp[0]["dob"]),
"zuechte": sorted(set(a["zucht"] for a in grp if a.get("zucht"))),
"files": sorted(set(f for a in grp for f in a["sourceFiles"])),
})
merged = []
conflicts = []
for key, grp in groups.items():
for grp in final_groups:
base = dict(grp[0])
variants = set([base["name"]])
files = set(base["sourceFiles"])
@@ -392,6 +548,8 @@ def dedup(animals):
"farbschlagVariants": sorted(farb),
"genotype": best,
"breeder": next((a["breeder"] for a in grp if a["breeder"]), ""),
"zucht": next((a["zucht"] for a in grp if a.get("zucht")), ""),
"zuchtCanon": next((a["_zucht"] for a in grp if a.get("_zucht")), ""),
"parentRefs": _dedup_parentrefs(parent_refs),
"photos": sorted(set(photos)),
"sourceFiles": sorted(files),
@@ -406,7 +564,64 @@ def dedup(animals):
"deaths": sorted(deaths), "files": sorted(files),
})
merged.sort(key=lambda a: (a["dob"], a["name"]))
return merged, conflicts, orphans
# ids must stay unique even when a Zucht split duplicates name+DOB slugs
seen_ids = {}
for a in merged:
if a["id"] in seen_ids:
seen_ids[a["id"]] += 1
a["id"] = f"{a['id']}-{seen_ids[a['id']]}"
else:
seen_ids[a["id"]] = 1
return merged, conflicts, orphans, zucht_splits
# ------------------------------------------- animal <-> litter matching ------
def match_litters(merged, litters):
"""Attach each animal to its Wurfchronik litter (Pam-validated build order:
litters are canonical, animals match onto them via DOB + (Vater, Mutter)).
Sets a['litterRef']; returns match statistics for the report."""
by_date = {}
for l in litters:
d = norm_dob(l["date"])
if d:
by_date.setdefault(d, []).append(l)
stats = {"parents": 0, "dateOnly": 0, "ambiguous": 0}
for a in merged:
if not a["dob"]:
continue
cands = by_date.get(a["dob"])
if not cands:
continue
a_parents = set()
for ref in a["parentRefs"]:
cn, _ = canon_pair(ref["name"])
if cn:
a_parents.add(cn)
def score(l):
s = 0
for nm in [l["damName"]] + l.get("sireNames", []):
cn, _ = canon_pair(nm)
if cn and cn in a_parents:
s += 1
return s
scored = sorted(((score(l), l["id"]) for l in cands), reverse=True)
best_score, best_id = scored[0]
if best_score > 0 and (len(scored) == 1 or scored[1][0] < best_score):
a["litterRef"] = {"litterId": best_id,
"method": "geburtsdatum+eltern", "confidence": "hoch"}
stats["parents"] += 1
elif len(cands) == 1:
a["litterRef"] = {"litterId": cands[0]["id"],
"method": "nur-geburtsdatum", "confidence": "niedrig"}
stats["dateOnly"] += 1
else:
a["litterRef"] = {"litterId": None, "method": "mehrdeutig",
"candidates": [l["id"] for l in cands]}
stats["ambiguous"] += 1
return stats
def _dedup_parentrefs(refs):
@@ -419,12 +634,13 @@ def _dedup_parentrefs(refs):
# ------------------------------------------------------------------ reporting
def write_report(merged, conflicts, orphans, raw_count, litters, photo_count):
keyset = set((a["dob"], norm_name(a["name"])) for a in merged)
def write_report(merged, conflicts, orphans, raw_count, litters, photo_count,
zucht_splits, match_stats):
multi = [a for a in merged if a["mentions"] > 1]
with_dob = [a for a in merged if a["dob"]]
lit_dates = set(norm_dob(l["date"]) for l in litters if l["date"])
joinable = [a for a in merged if a["dob"] and norm_dob(a["dob"]) in lit_dates]
val_warn = [l for l in litters if any("" in w for w in l["warnings"])]
schema_warn = [l for l in litters if any("Abweichung" in w for w in l["warnings"])]
matched = match_stats["parents"] + match_stats["dateOnly"]
L = []
L.append("# FEAT-8b — Import-Vorschau & Prüfbericht (Stammbäume + Wurfchronik)\n")
@@ -439,12 +655,32 @@ def write_report(merged, conflicts, orphans, raw_count, litters, photo_count):
L.append(f"- Mehrdeutige / unvollständige Einträge (ohne Name+Datum): **{len(orphans)}**")
L.append(f"- Fotos zugeordnet: **{photo_count}**")
L.append(f"- Würfe aus der Wurfchronik: **{len(litters)}**")
L.append(f" - Tiere, deren Geburtsdatum zu einem Wurf passt (verknüpfbar): {len(joinable)}\n")
L.append(f" - Tiere mit Wurf verknüpft: **{matched}** "
f"(davon über Geburtsdatum **und** Eltern: {match_stats['parents']}, "
f"nur über Geburtsdatum: {match_stats['dateOnly']}; "
f"mehrdeutig: {match_stats['ambiguous']})")
L.append(f" - Würfe mit Datenqualitäts-Hinweisen: {len(val_warn)} "
f"(+ {len(schema_warn)} Zeilen mit abweichendem Spaltenschema)\n")
L.append("## Zusammenführungs-Schlüssel\n")
L.append("Tiere wurden zusammengeführt über **normalisierter Name + Geburtsdatum**. "
"Namensvarianten (z. B. `v.d.` ↔ `von den`, `gen.`-Spitznamen, Zuchtsuffixe) "
"werden als `nameVariants` erhalten.\n")
L.append("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.\n")
if zucht_splits:
L.append("### Gleicher Name + Geburtsdatum, aber unterschiedliche Zucht "
"(NICHT zusammengeführt — bitte prüfen)\n")
L.append("| Tier | Geburtsdatum | Zuchten | Dateien |")
L.append("|---|---|---|---|")
for s in zucht_splits[:50]:
L.append("| {} | {} | {} | {} |".format(
split_name_zucht(s["name"])[0], s["dob"],
" // ".join(s["zuechte"]),
", ".join(os.path.splitext(f)[0] for f in s["files"])))
L.append("")
L.append("## ⚠️ Konflikte (bitte prüfen)\n")
if conflicts:
@@ -506,6 +742,37 @@ def write_report(merged, conflicts, orphans, raw_count, litters, photo_count):
for t, n in tok.most_common(25):
L.append(f"| `{t}` | {n} | {hint.get(t, '?')} |")
# Wurfchronik data quality (Julian: E sollte = F TG s sein)
L.append("\n## Wurfchronik — Datenqualitäts-Hinweise\n")
L.append("Julians Spaltenregel: **Überlebende bis Abgabe (E) = Wurfstärke (F) "
"Totgeburten (TG) später verstorben (s)**. Bei diesen Würfen geht die "
"Rechnung nicht auf — kein Import-Hindernis, aber ein Hinweis auf "
"Tippfehler oder fehlende Einträge:\n")
if val_warn:
L.append("| Wurf | Datum | Mutter × Vater | Hinweis |")
L.append("|---|---|---|---|")
for l in val_warn[:120]:
L.append("| {} | {} | {} × {} | {} |".format(
l["litterId"], l["date"], l["damName"], l["sireName"],
"; ".join(w for w in l["warnings"] if "" in w)))
if len(val_warn) > 120:
L.append(f"\n… und {len(val_warn) - 120} weitere (siehe `litters.json`).")
else:
L.append("_Keine — alle Würfe sind rechnerisch konsistent._")
L.append("\n### Zeilen mit abweichendem Spaltenschema (automatisch interpretiert)\n")
L.append(f"{len(schema_warn)} Zeilen (überwiegend Tabelle2 ab 2014) tragen eine "
"zusätzliche Zahlenspalte vor der Wurfstärke bzw. eine verschobene "
"Geschlechter-Aufschlüsselung. Sie wurden nach dem Muster von Tabelle1 "
"gelesen (**Überlebende, Wurfstärke, Aufschlüsselung**) — bei "
f"{sum(1 for l in schema_warn if not any('' in w for w in l['warnings']))} "
"davon geht die Rechnung E = F TG s damit exakt auf, was die Lesart "
"bestätigt. Alle betroffenen Zeilen sind in `litters.json` mit "
"`warnings` markiert. Beispiele:\n")
for l in schema_warn[:8]:
L.append(f"- Wurf {l['litterId']} ({l['date']}): E={l['survivedToGoHome']}, "
f"F={l['totalBorn']}, Aufschlüsselung `{l['breakdownRaw']}`")
L.append("\n## Hinweise für den Import (Stufe 3, später)\n")
L.append("- **Wurfchronik = Quelle der Würfe** (Datum, Wurfstärke, Eltern, Zuchtnummer); "
"**Stammbäume = Abstammung + Genotyp + Fotos**. Verknüpfung über Geburtsdatum + Elternnamen.")
@@ -550,7 +817,8 @@ def main():
litters = extract_wurfchronik(args.wurfchronik)
print(f"Wurfchronik: {len(litters)} Würfe")
merged, conflicts, orphans = dedup(raw_animals)
merged, conflicts, orphans, zucht_splits = dedup(raw_animals)
match_stats = match_litters(merged, litters)
photo_count = sum(len(a["photos"]) for a in merged)
# strip private (_) fields from the JSON output
@@ -562,10 +830,15 @@ def main():
with open(os.path.join(OUT, "litters.json"), "w", encoding="utf-8") as f:
json.dump(litters, f, ensure_ascii=False, indent=2)
write_report(merged, conflicts, orphans, len(raw_animals), litters, photo_count)
write_report(merged, conflicts, orphans, len(raw_animals), litters, photo_count,
zucht_splits, match_stats)
print(f"\nRoh: {len(raw_animals)} → eindeutig: {len(merged)} "
f"| Konflikte: {len(conflicts)} | Orphans: {len(orphans)} | Fotos: {photo_count}")
f"| Konflikte: {len(conflicts)} | Zucht-Splits: {len(zucht_splits)} "
f"| Orphans: {len(orphans)} | Fotos: {photo_count}")
print(f"Wurf-Verknüpfung: {match_stats['parents']} (Datum+Eltern), "
f"{match_stats['dateOnly']} (nur Datum), {match_stats['ambiguous']} mehrdeutig "
f"| Wurf-Warnungen: {sum(1 for l in litters if l['warnings'])}")
print(f"Ausgabe in {OUT}")

View File

@@ -5,18 +5,19 @@ _Automatisch erzeugt von `tools/import/extract.py` — **noch nichts in die Date
## Überblick
- Rohe Tier-Einträge aus den Stammbäumen: **889**
- Nach Zusammenführung (eindeutige Tiere): **587**
- davon mit Geburtsdatum: 292
- in mehreren Dateien gefunden (Dubletten zusammengeführt): 142
- Konflikte zur Klärung: **24**
- Nach Zusammenführung (eindeutige Tiere): **574**
- davon mit Geburtsdatum: 279
- in mehreren Dateien gefunden (Dubletten zusammengeführt): 146
- Konflikte zur Klärung: **32**
- Mehrdeutige / unvollständige Einträge (ohne Name+Datum): **310**
- Fotos zugeordnet: **123**
- Würfe aus der Wurfchronik: **752**
- Tiere, deren Geburtsdatum zu einem Wurf passt (verknüpfbar): 152
- Tiere mit Wurf verknüpft: **135** (davon über Geburtsdatum **und** Eltern: 95, nur über Geburtsdatum: 40; mehrdeutig: 9)
- Würfe mit Datenqualitäts-Hinweisen: 113 (+ 138 Zeilen mit abweichendem Spaltenschema)
## Zusammenführungs-Schlüssel
Tiere wurden zusammengeführt über **normalisierter Name + Geburtsdatum**. Namensvarianten (z. B. `v.d.``von den`, `gen.`-Spitznamen, Zuchtsuffixe) werden als `nameVariants` erhalten.
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.
## ⚠️ Konflikte (bitte prüfen)
@@ -24,13 +25,17 @@ Gleiches Tier (Name+Datum), aber widersprüchliche Angaben in verschiedenen Date
| Tier | Geburtsdatum | abweichende Genotypen | abweichende Farbschläge | Sterbedaten | Dateien |
|---|---|---|---|---|---|
| Ella | 10.06.2019 | Aa C D- ee[f] GG P- spsp // Aa Cc[chm] D- ee[f] UwUw P- spsp | Algierfuchsschimmel, hell | 03.02.2023 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Valentino Firehearts Kids |
| ZoneFire | 07.12.2020 | Aa c[chm]c[chm] D- Ee Gg P- Spsp | CP-Agouti Kragenschecke // Kalea von den Kleinen Chaoten | — | Stammbaum von Akio Kids |
| Louis von den Kleinen Chaoten | 15.07.2017 | Aa Cc[] D- Ee Gg P- spsp // Aa Cc[chm] D- Ee Uwuw[d] P- spsp | Roswitha von den Kleinen Chaoten | 01.07.2020 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity |
| Roswitha von den Kleinen Chaoten | 10.09.2018 | aa CC D- ee[f] Gg P- spsp // aa CC D- ee[f] Uwuw[d] P- spsp | — | 05.08.2021 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity |
| Firefly von den Kleinen Chaoten | 18.12.2019 | /+, Aa c[chm]c[chm] D- Ee Gg PP Spsp // Aa c[chm]c[chm] DD Ee Gg PP Spsp | — | 2024 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Fire Kids, Stammbaum von Valentino Firehearts Kids |
| Zuleika von den Kleinen Chaoten | 24.10.2015 | aa c[chm]c[h] D- E G P- spsp // aa c[chm]c[h] D- Ee Gg P- spsp // aa c[chm]c[h] DD Ee Gg P- spsp | — | 24.02.2019 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Valentino Firehearts Kids |
| WildFire von den Kleinen Chaoten | 05.10.2017 | aa c[chm]c[chm] D- Ee gg P- spsp // aa c[chm]c[chm] D- Ee gg PP spsp | — | — | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Fire Kids, Stammbaum von Valentino Firehearts Kids |
| Vestra von den Schlossmäusen | 08.02.2019 | Aa Cc[chm] D- EE GG PP Spsp [WP] // Aa Cc[chm] DD EE GG PP Spsp [WP] | — | 26.05.2023 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Fire Kids, Stammbaum von Valentino Firehearts Kids |
| Flint von den Kleinen Chaoten | 23.12.2017 | aa Cc[chm] D- ee Gg P- spsp | — | 10.05.2021 // 10.05.2022 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity |
| Silenos gen. Adonis v.d. Kleinen Chaoten | 11.10.2015 | aa Cc[chm] D- Ee Gg PP spsp // aa Cc[chm] D- Ee Uwuw[d] PP spsp | — | 18.07.2019 | Stammbaum von Akio Kids, Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Watarus Kids |
| Bruno of Black Forest | 01.06.2022 | aa C- dd Ee Gg P- spsp | Blau // Mystique of Black Forest | — | Stammbaum von Alberto Kids, Stammbaum von Fire Kids, Stammbaum von Stella Kids |
| Milka of LennyLengo | 09.12.2018 | aa C- dd E- Gg P- Spsp // aa Cc[h] dd EE Gg P- Spsp | — | 22.12.2021 | Stammbaum von Alberto Kids, Stammbaum von Stella Kids |
| Hedwig of BGB | 30.10.2019 | aa CC DD E- G- P- Spsp WP // aa CC DD E- G- P- Spsp WP DP (hörend) | — | 30.08.2023 | Stammbaum von Alberto Kids, Stammbaum von Fire Kids, Stammbaum von Stella Kids |
| Silvain von den Kleinen Chaoten | 27.03.2022 | aa c[chm]c[chm] Dd Ee[-] Gg P- Spsp // aa c[chm]c[chm] Dd ee[-] Gg Pp Spsp | — | 31.12.2024 | Stammbaum von Alberto Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity |
@@ -41,10 +46,14 @@ Gleiches Tier (Name+Datum), aber widersprüchliche Angaben in verschiedenen Date
| Molly of Black Forest | 13.09.2021 | /+, Aa Cc[chm] D- Ee gg P- spsp // Aa Cc[chm] Dd Ee gg Pp spsp | — | 03.05.2021 | Stammbaum von Alberto Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity |
| Little Runner's Big Ben | 03.02.2020 | Aa Cc[chm] DD Ee Gg PP Spsp // Aa Cc[chm] DD Ee Gg Pp Spsp | Daja of Little Rose | 14.10.2023 | Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Fire Kids, Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Kohlief, Goldfuchsef Sp von Chrissi, Stammbaum von Valentino Firehearts Kids, Stammbaum von Watarus Kids |
| Daja of Little Rose | 16.05.2021 | aa chmchm D- EE Gg P- // aa chmchm D- EE Gg P- spsp | — | — | Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Fire Kids, Stammbaum von Valentino Firehearts Kids |
| Vance Jr. von den Kleinen Chaoten | 10.04.2022 | aa Cc[hm] Dd Ee gg P- Spsp // aa Cc[hm] Dd Ee gg P- spsp | Kohlfuchs, hell // Velvet von den Kleinen Chaoten | — | Stammbaum von Fire Kids, Stammbaum von Stella Kids |
| Ichika von den Kleinen Chaoten | 19.04.2020 | aa CC D- ee Gg pp spsp // aa CC D- ee[f] Gg pp spsp | — | 27.11.2023 | Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Watarus Kids |
| Victoria Welby gen. Welby v.d. Kleinen Chaoten | 16.01.2023 | Aa CC D- Ee[f] Gg pp Spsp [DP] // Aa CC D- ee[f] Gg pp Spsp [DP] | — | 17.02.2026 | Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Watarus Kids |
| Trogir von den Kleinen Chaoten | 21.03.2022 | Aa CC DD EE GG pp Spsp | Gold Ansatzschecke // Mahima von den Kleinen Chaoten | 29.05.2024 | Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Kohlief, Goldfuchsef Sp von Chrissi, Stammbaum von Watarus Kids |
| Chayton v.d. Kleinen Chaoten (extern SC) | 04.02.2022 | aa Cc[-] D- e[f]e[f] Gg Pp spsp | Orangeschimmel, hell // Victoria Welby gen. Welby v.d. Kleinen Chaoten | 30.04.2024 | Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Kohlief, Goldfuchsef Sp von Chrissi, Stammbaum von Watarus Kids |
| Victoria Welby gen. Welby v.d. Kleinen Chaoten | 16.01.2023 | Aa CC D- Ee[f] Gg pp Spsp [DP] // Aa CC D- ee[f] Gg pp Spsp [DP] | Goldfuchsschimmel Punktschecke DP | 17.02.2026 | Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Kohlief, Goldfuchsef Sp von Chrissi, Stammbaum von Watarus Kids |
| Zac gen. Action von den Kleinen Chaoten | 25.12.2020 | aa C- D- Ee G- Pp Spsp [DP] // aa CC D- Ee G- Pp Spsp [DP] | Belica gen. Emi von den Kleinen Chaoten | 31.01.2025 | Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Kohlief, Goldfuchsef Sp von Chrissi, Stammbaum von Watarus Kids |
| Chelsea von den Kleinen Chaoten | 02.04.2021 | /+, Aa CC Dd ee gg Pp spsp // Aa CC Dd ee gg Pp spsp | — | — | Stammbaum von Goldfuchs Sp (Pikachu) Kids, Stammbaum von Valentino Firehearts Kids |
| Chesnut | 13.11.2019 | aa C- D- ee[f] GG PP spsp | Kohlfuchsschimmel // Tennessee von den Kleinen Chaoten | 22.11.2023 | Stammbaum von Kentucky |
| Ethan von den Kleinen Chaoten | 09.07.2020 | Aa Cc[chm] D- ee[f] Gg Pp Spsp | Ichika von den Kleinen Chaoten // Orangeschimmel, hell Kragenschecke | 30.07.2024 | Stammbaum von Kentucky, Stammbaum von Watarus Kids |
| Quied Soldier of Black Forest | 07.06.2018 | /+, Aa C- D- ee[f] GG Pp Spsp [DP] // Aa C- D- ee[f] GG Pp Spsp DP | Hoshi von den Kleinen Chaoten | — | Stammbaum von Kentucky |
| Hanami von den Kleinen Chaoten | 10.09.2015 | aa Cc[chm] D- Ee gg P- spsp | — | 12.12.2019 // 14.01.2020 | Stammbaum von Kentucky, Stammbaum von Stella Kids |
@@ -143,20 +152,20 @@ Diese Tokens stehen weiter in `rawGenotype`/`unmappedTokens` — Entscheidung (M
| Token | Vorkommen | Bedeutung (Vermutung) |
|---|---|---|
| `[DP]` | 16 | Marker (Dunkelpigment?) |
| `[DP]` | 15 | Marker (Dunkelpigment?) |
| `[WFNZ]` | 13 | Marker |
| `DP` | 10 | Marker |
| `WP` | 8 | Marker |
| `DP` | 9 | Marker |
| `/+` | 8 | ? |
| `WP` | 7 | Marker |
| `Uwuw[d]` | 4 | 9. Locus Uw (nicht im Modell) |
| `[WP]` | 3 | Marker |
| `-g` | 2 | ? |
| `C(C)` | 2 | Schreibweise (C trägt c) |
| `UwUw` | 2 | 9. Locus Uw |
| `[WP]` | 2 | Marker |
| `chmchm` | 2 | Schreibweise (c[chm]c[chm]) |
| `Cc[]` | 1 | ? |
| `-psp` | 1 | ? |
| `G(G)` | 1 | ? |
| `UwUw` | 1 | 9. Locus Uw |
| `uw[d]uw[d]` | 1 | ? |
| `[DP` | 1 | ? |
| `Dea/dea]` | 1 | ? |
@@ -168,6 +177,139 @@ Diese Tokens stehen weiter in `rawGenotype`/`unmappedTokens` — Entscheidung (M
| `(hörend)` | 1 | ? |
| `-DD` | 1 | ? |
## Wurfchronik — Datenqualitäts-Hinweise
Julians Spaltenregel: **Überlebende bis Abgabe (E) = Wurfstärke (F) Totgeburten (TG) später verstorben (s)**. Bei diesen Würfen geht die Rechnung nicht auf — kein Import-Hindernis, aber ein Hinweis auf Tippfehler oder fehlende Einträge:
| Wurf | Datum | Mutter × Vater | Hinweis |
|---|---|---|---|
| J | 27.10.2010 | Jana [little longnoses] × Danny [Privatzucht Maintal] | Überlebende (4) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (1) = 3 |
| S | 13.10.2011 | Beatrice [Kleine Chaoten] × Alvin [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (5) Totgeburten (0) später verstorben (0) = 5 |
| C1 | 24.03.2012 | Gina [Mauzis Renner] × Heaven [Privatzucht Maintal] | Überlebende (1) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| A2 | 27.01.2013 | Gwen gen. Sakura [little Angels] × Jamie [Kleine Chaoten] | Überlebende (6) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (1) = 5 |
| P2 | 16.06.2013 | Mina [Strelitzie] × Feivel [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (5) Totgeburten (0) später verstorben (0) = 5 |
| T2 | 14.07.2013 | Bea [Kleine Chaoten] × Silver [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| O3 | 27.05.2014 | Kallie [Kleine Chaoten] × Digimon [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (5) Totgeburten (3) später verstorben (0) = 2 |
| R3 | 26.06.2014 | Akina [Kleine Chaoten] × Paul [Privatzucht Gießen] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| S3 | 04.07.2014 | Mira [Kleine Chaoten] × JD [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (5) Totgeburten (0) später verstorben (0) = 5 |
| H4 | 25.10.2014 | Milena [Kleine Chaoten] × Benny [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (2) Totgeburten (1) später verstorben (0) = 1 |
| I4 | 31.10.2014 | Lynn [Lenny Lengo] × Derek [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (5) Totgeburten (0) später verstorben (0) = 5 |
| W4 | 14.01.2015 | Nelly [Kleine Chaoten] × Earl [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| E6 | 12.05.2015 | Isa [golden lights] × Dastan | Überlebende (2) ≠ Wurfstärke (3) Totgeburten (2) später verstorben (0) = 1 |
| O6 | 21.06.2015 | Elena [KK-Chaos] × Cash [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| Q6 | 28.06.2015 | Luisa × Cash [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (1) = 2 |
| U6 | 12.07.2015 | Reika [Kleine Chaoten] × Ringo [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (5) Totgeburten (1) später verstorben (0) = 4 |
| A7 | 07.08.2015 | Arizona [Kleine Chaoten] × Marc [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| I7 | 11.09.2015 | Zora [Kleine Chaoten] × BlackFire [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| J7 | 12.09.2015 | Yuzuki (Onelia (Algieref)) × Milon [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (5) Totgeburten (2) später verstorben (0) = 3 |
| P7 | 11.10.2015 | Gin [Kleine Chaoten] × Pan [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (5) Totgeburten (1) später verstorben (0) = 4 |
| Q7 | 13.10.2015 | Cristina [Kleine Chaoten] × Gino [Kleine Chaoten] | Überlebende (6) ≠ Wurfstärke (6) Totgeburten (1) später verstorben (0) = 5 |
| R7 | 15.10.2015 | Sheila × Blue | Überlebende (1) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| Y7 | 24.10.2015 | Xtra Bounty [Schlossmaus] × Ruby [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (4) Totgeburten (2) später verstorben (0) = 2 |
| Z7 | 25.10.2015 | Villow [Kleine Chaoten] × Flocke [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (3) = 3 |
| F8 | 25.11.2015 | Alice [Chalfont Stud] × DArtagnan gen. Dante [Hanse Renner] | Überlebende (3) ≠ Wurfstärke (3) Totgeburten (1) später verstorben (0) = 2 |
| H8 | 01.12.2015 | Isa [golden lights] × Arrow [Privatzucht Gießen] | Überlebende (4) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (1) = 3 |
| R8 | 14.12.2015 | Xylona [Kleine Chaoten] × Joghurt [Privat Frankfurt] | Überlebende (6) ≠ Wurfstärke (7) Totgeburten (0) später verstorben (0) = 7 |
| W8 | 27.12.2015 | Cristina [Kleine Chaoten] × Gino [Kleine Chaoten] | Überlebende (5) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (0) = 6 |
| X8 | 30.12.2015 | Alice [Chalfont Stud] × DArtagnan gen. Dante [Hanse Renner] | Überlebende (4) ≠ Wurfstärke (5) Totgeburten (0) später verstorben (0) = 5 |
| B9 | 03.01.2016 | Elly [Kleine Chaoten] × George gen. Bounty [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| C9 | 09.01.2016 | Ruby [Kleine Chaoten] × Gino [Kleine Chaoten] | Überlebende (5) ≠ Wurfstärke (5) Totgeburten (0) später verstorben (1) = 4 |
| F9 | 22.01.2016 | Lotta [Kleine Chaoten] × Wade [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (0) = 6 |
| H9 | 29.01.2016 | Viven [Kleine Chaoten] × Spike [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (7) Totgeburten (2) später verstorben (0) = 5 |
| L9 | 06.02.2016 | Gin [Kleine Chaoten] × Pan [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (2) später verstorben (0) = 2 |
| M9 | 07.02.2016 | Princess [Kleine Chaoten] × Pudlov [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (1) = 5 |
| X9 | 03.03.2016 | Zora [Kleine Chaoten] × Sammy [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (3) Totgeburten (1) später verstorben (1) = 1 |
| Z9 | 08.03.2016 | Viven [Kleine Chaoten] × Spikie [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (5) Totgeburten (3) später verstorben (0) = 2 |
| B10 | 12.03.2016 | Nancy [little Hobbits] × Milon [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (1) = 2 |
| J10 | 28.05.2016 | Kokiri [Samsimar] × Sammy [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| L10 | 19.06.2016 | Violetta [Kleine Chaoten] × Piet [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| S10 | 06.08.2016 | Lisa [Kleine Chaoten] × Logan [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (5) Totgeburten (2) später verstorben (0) = 3 |
| U10 | 16.08.2016 | Hamster (C*C) [Kleine Chaoten] × Agouti (Mala [Zeko i ptica, Croatia] & Lenny) [Kleine Chaoten] | Überlebende (5) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (0) = 6 |
| A11 | 15.11.2016 | Kimiko [Kleine Chaoten] × Xavier [Black forest] | Überlebende (5) ≠ Wurfstärke (6) Totgeburten (3) später verstorben (0) = 3 |
| B11 | 17.11.2016 | Leyla [colourful furry gerbils] × BlackFire [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| D11 | 20.12.2016 | Amira [Kleine Chaoten] × Golek [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| F11 | | Zarina [Kleine Chaoten] × Sloan [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| H11 | 08.01.2017 | Isa [golden lights] × Jack II [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| J11 | 17.01.2017 | Catelyn Stark [Kleine Chaoten] × Ayron [Kleine Chaoten] [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (4) Totgeburten (2) später verstorben (0) = 2 |
| O11 | 24.02.2017 | Hanami [Kleine Chaoten] × Xavier [Black Forest] | Überlebende (4) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (1) = 3 |
| Q11 | 02.03.2017 | Leyla [colourful furry gerbils] × BlackFire [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (3) Totgeburten (1) später verstorben (0) = 2 |
| R11 | 07.03.2017 | Little Lilly [Kleine Chaoten] × Edward [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (3) Totgeburten (2) später verstorben (0) = 1 |
| B12 | 30.04.2017 | Aki [Kleine Chaoten] × Athmos [Kleine Chaoten] | Überlebende (5) ≠ Wurfstärke (5) Totgeburten (2) später verstorben (0) = 3 |
| H12 | 10.07.2017 | Jela [Kleine Chaoten] × Maxi King [Lenny lengo] | Überlebende (2) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (2) = 4 |
| O12 | 24.08.2017 | Varda [Kleine Chaoten] × Lenny [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| Y12 | 13.12.2017 | Hanami [Kleine Chaoten] × Robb Stark [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (3) Totgeburten (2) später verstorben (0) = 1 |
| A13 | 17.12.2017 | Montana [Kleine Chaoten] × Eric [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| G13 | 20.02.2018 | Sam [Kleine Chaoten] × Polly [Diepholzer Fellnasen] | Überlebende (3) ≠ Wurfstärke (3) Totgeburten (1) später verstorben (0) = 2 |
| L13 | 28.03.2018 | Yella [Kleine Chaoten] × Quambo [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (0) = 6 |
| T14 | 09.04.2019 | Enya [Kleine Chaoten] × Brandon Stark [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (5) Totgeburten (2) später verstorben (0) = 3 |
| Z14 | 02.06.2019 | Vandana [Kleine Chaoten] × Laito [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (3) Totgeburten (1) später verstorben (0) = 2 |
| H15 | 09.07.2019 | Mina [Kleine Chaoten] × Adonis [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| I15 | 14.07.2019 | Naho [Kleine Chaoten] × Wilbur [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| J15 | 16.07.2019 | Chevrolet Corvette [Topolino] × South Dakoto [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| Y15 | 25.10.2019 | Nisha [Black Forest] × Gus [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| F16 | 12.12.2019 | Jill [Black Forest] × John Dewey [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (5) Totgeburten (0) später verstorben (0) = 5 |
| R16 | 05.02.2020 | Naho [Kleine Chaoten] × Oscar [Black Forest] | Überlebende (4) ≠ Wurfstärke (5) Totgeburten (0) später verstorben (0) = 5 |
| U16 | 13.02.2020 | Oyama [Kleine Chaoten] × Ramzi [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (4) Totgeburten (1) später verstorben (1) = 2 |
| Z16 | 28.02.2020 | Hana [Kleine Chaoten] × Zadar [Zeko i ptica, Croatia] | Überlebende (2) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (1) = 1 |
| O17 | 09.05.2020 | Nisha [Black Forest] × Zenon von Elea [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (1) Totgeburten (1) später verstorben (0) = 0 |
| W17 | 07.06.2020 | Inory [Kleine Chaoten] × Jeremy [Kleine Chaoten] | Überlebende (4) ≠ Wurfstärke (5) Totgeburten (2) später verstorben (0) = 3 |
| Y17 | 20.06.2020 | Honshu [golden lights] × Baiko [Kleine Chaoten] | Überlebende (2) ≠ Wurfstärke (4) Totgeburten (1) später verstorben (0) = 3 |
| F18 | 06.07.2020 | Nisha [Black Forest] × Zenon von Elea [Kleine Chaoten] | Überlebende (3) ≠ Wurfstärke (3) Totgeburten (1) später verstorben (0) = 2 |
| W18 | 24.12.2020 | Belica gen. Emi [Kleine Chaoten] × Martin [Kleine Chaoten] | Überlebende (6) ≠ Wurfstärke (7) Totgeburten (0) später verstorben (0) = 7 |
| N19 | 13.05.2021 | Tennesee [Kleine Chaoten] × Chesnut [Kleine Chaoten] | Überlebende (1) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| 10 | 23.02.2015 | Kia [Kleine Chaoten] × Arrow [Privatzucht Gießen] | Überlebende (0) ≠ Wurfstärke (1) Totgeburten (0) später verstorben (0) = 1 |
| 11 | 25.02.2015 | Raya [bunten Fellnasen] × Hanse Renner's DArtagnan gen. Dante | Überlebende (0) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| 12 | 01.03.2015 | Bleach [Kleine Chaoten] [Kleine Chaoten] × JD [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| 14 | 02.04.2015 | Nancy [Kleine Chaoten] × Zorro [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (1) Totgeburten (0) später verstorben (0) = 1 |
| 15 | 03.04.2015 | Eliza [Kleine Chaoten] × Jack Jr. [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| 17 | 05.04.2015 | Skarlett [Kleine Chaoten] × Hiro [golden lights] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 18 | 07.04.2015 | Olympia [Kleine Chaoten] × Shinichi [Privatzucht Mücke] | Überlebende (0) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (0) = 6 |
| 21 | 01.05.2015 | Milena [Kleine Chaoten] × Browny [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| 28 | 29.06.2015 | Dusty × Harumi [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (7) Totgeburten (0) später verstorben (0) = 7 |
| 29 | 30.06.2015 | Meredith [Kleine Chaoten] × Bailey [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| 30 | 30.06.2015 | Qamikaze Queen gen. Quenny [Schlossmaus] × Sammy [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 31 | 01.07.2015 | Kazumi [Kleine Chaoten] × Kazu [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (1) Totgeburten (0) später verstorben (0) = 1 |
| 33 | 15.07.2015 | Mia [bunte Fellnasen] × Dargonet [Hanse Renner] | Überlebende (0) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| 37 | 28.08.2015 | Genna [golden lights] × Arrow [Privatzucht Gießen] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 38 | 15.09.2015 | Sheila [of Lenny Lengo] × Blue [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| 39 | 15.09.2015 | Schwarz?? [Kleine Chaoten] × Poseidon [Hanse Renner] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| 40 | 31.10.2015 | Abiya [Kleine Chaoten] × Dastan [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (0) = 6 |
| 41 | 02.11.2015 | A Sp (Zobel) × Janosch [Black forest] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 43 | 22.12.2015 | Zoey (CP-Sa) [Kleine Chaoten] × Janosch [Black forest] | Überlebende (2) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (4) = 0 |
| 44 | 12.01.2016 | Akina [Kleine Chaoten] × Poseidon [Hanse Renner | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 46 | 05.02.2016 | Zora [Kleine Chaoten] × Sammy [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 47 | 06.02.2016 | Zuleika [Kleine Chaoten] × Xtra Bounty [Schlossmaus] | Überlebende (0) ≠ Wurfstärke (1) Totgeburten (0) später verstorben (0) = 1 |
| 53 | 03.06.2016 | Sa Sp × Rudi | Überlebende (0) ≠ Wurfstärke (1) Totgeburten (0) später verstorben (0) = 1 |
| 54 | 11.06.2016 | Nella [Kleine Chaoten] × Arrow [Privatzucht Gießen] | Überlebende (0) ≠ Wurfstärke (1) Totgeburten (0) später verstorben (0) = 1 |
| 58 | 27.11.2016 | Villow [Kleine Chaoten] × Theodore [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 61 | 31.12.2016 | Nina [Skylers Rennmausclan] × Henry [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| 62 | 07.01.2017 | Villow [Kleine Chaoten] × Theodore [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (1) Totgeburten (0) später verstorben (0) = 1 |
| 65 | 13.02.2017 | Princess Jr. [Kleine Chaoten] × Pudlov [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 67 | 14.03.2017 | Lenni [Kleine Chaoten] × BlackFire [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (6) Totgeburten (0) später verstorben (0) = 6 |
| 68 | 17.04.2017 | Elfenbein/Apricot SP [Kleine Chaoten] × Agouti [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| 75 | 01.06.2017 | Sherry(Marlou&Zelos) [Kleine Chaoten] × Luke [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 81 | 21.05.2018 | Umi [Kleine Chaoten] × Theodore [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 83 | 30.07.2018 | Pirinna [Kleine Chaoten] × Iain [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (4) Totgeburten (0) später verstorben (0) = 4 |
| 85 | 21.10.2018 | Sherry [Kleine Chaoten] × Yoshua [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 101 | 18.02.2020 | Oria [Kleine Chaoten] × Opheus [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (1) = 1 |
| 108 | 22.03.2020 | Oyama [Kleine Chaoten] × Ramzi [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (3) Totgeburten (0) später verstorben (0) = 3 |
| 110 | 25.03.2020 | Hollie gen. Panini [Kleine Chaoten] × Kazuya [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (5) Totgeburten (4) später verstorben (0) = 1 |
| 115 | 13.05.2020 | Lacey [Kleine Chaoten] × Ares [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
| 123 | 27.07.2020 | Yasna [Kleine Chaoten] × Ramzi [Kleine Chaoten] | Überlebende (0) ≠ Wurfstärke (2) Totgeburten (0) später verstorben (0) = 2 |
### Zeilen mit abweichendem Spaltenschema (automatisch interpretiert)
138 Zeilen (überwiegend Tabelle2 ab 2014) tragen eine zusätzliche Zahlenspalte vor der Wurfstärke bzw. eine verschobene Geschlechter-Aufschlüsselung. Sie wurden nach dem Muster von Tabelle1 gelesen (**Überlebende, Wurfstärke, Aufschlüsselung**) — bei 99 davon geht die Rechnung E = F TG s damit exakt auf, was die Lesart bestätigt. Alle betroffenen Zeilen sind in `litters.json` mit `warnings` markiert. Beispiele:
- Wurf 6 (03.11.2014): E=2, F=4, Aufschlüsselung `1,3,2,0`
- Wurf 9 (04.02.2015): E=0, F=5, Aufschlüsselung `2,3,5,0`
- Wurf 10 (23.02.2015): E=0, F=1, Aufschlüsselung ``
- Wurf 11 (25.02.2015): E=0, F=4, Aufschlüsselung ``
- Wurf 12 (01.03.2015): E=0, F=2, Aufschlüsselung ``
- Wurf 13 (29.03.2015): E=0, F=6, Aufschlüsselung `2,4,6,0`
- Wurf 14 (02.04.2015): E=0, F=1, Aufschlüsselung ``
- Wurf 15 (03.04.2015): E=0, F=2, Aufschlüsselung ``
## Hinweise für den Import (Stufe 3, später)
- **Wurfchronik = Quelle der Würfe** (Datum, Wurfstärke, Eltern, Zuchtnummer); **Stammbäume = Abstammung + Genotyp + Fotos**. Verknüpfung über Geburtsdatum + Elternnamen.