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:
@@ -78,6 +78,46 @@ def read_cells(z, sheet_path, ss=None):
|
||||
return cells
|
||||
|
||||
|
||||
def cell_fill_sex(z, sheet_path):
|
||||
"""{(colnum, row): 'male' | 'female'} from the cell's box fill colour.
|
||||
|
||||
Breeder convention (Julian, 2026-06-06): a BLUE box = männlich (male), a WHITE box =
|
||||
weiblich (female). In these Stammbaum templates every coloured box uses one solid theme
|
||||
fill (Office accent5 = blue); unfilled/none cells render white. So: a cell whose style
|
||||
uses a real solid fill -> 'male'; an unfilled (none/gray125) cell -> 'female'.
|
||||
"""
|
||||
try:
|
||||
st = z.read("xl/styles.xml").decode("utf-8")
|
||||
except KeyError:
|
||||
return {}
|
||||
fills = re.search(r"<fills.*?</fills>", st, re.S)
|
||||
colored = set()
|
||||
if fills:
|
||||
for i, fb in enumerate(re.findall(r"<fill>(.*?)</fill>", fills.group(0), re.S)):
|
||||
if 'patternType="solid"' in fb and re.search(r"<fgColor\s", fb) and "gray125" not in fb:
|
||||
colored.add(i) # fillId of a real solid colour (blue)
|
||||
xfs = re.search(r"<cellXfs.*?</cellXfs>", st, re.S)
|
||||
idx2fill = {}
|
||||
if xfs:
|
||||
for i, xf in enumerate(re.findall(r"<xf\b([^>]*?)/?>", xfs.group(0))):
|
||||
m = re.search(r'fillId="(\d+)"', xf)
|
||||
idx2fill[i] = int(m.group(1)) if m else 0
|
||||
raw = z.read(sheet_path).decode("utf-8")
|
||||
out = {}
|
||||
for m in re.finditer(r"<c\s+([^>]*?)>", raw):
|
||||
a = dict(_ATTR.findall(m.group(1)))
|
||||
ref = a.get("r")
|
||||
if not ref:
|
||||
continue
|
||||
mm = re.match(r"([A-Z]+)(\d+)", ref)
|
||||
if not mm:
|
||||
continue
|
||||
s = int(a.get("s", "0"))
|
||||
out[(col_to_num(mm.group(1)), int(mm.group(2)))] = (
|
||||
"male" if idx2fill.get(s, 0) in colored else "female")
|
||||
return out
|
||||
|
||||
|
||||
def header_row(cells):
|
||||
"""Return {colnum: header_text} for the topmost row that has text."""
|
||||
if not cells:
|
||||
|
||||
Reference in New Issue
Block a user