Files
GerbilManager/GerbilManagerWebAPI/Import/ImportModels.cs
Gulum ea79703dbf 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>
2026-06-06 10:48:30 +02:00

99 lines
3.6 KiB
C#

using System.Text.Json.Serialization;
namespace GerbilManagerWebAPI.Import
{
// ---- Source shapes (tools/import/output JSON, produced by extract.py) ----
public sealed class SourceAnimal
{
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public List<string> NameVariants { get; set; } = new();
public string Dob { get; set; } = "";
public string Death { get; set; } = "";
public string? Gender { get; set; }
public string Farbschlag { get; set; } = "";
public List<string> FarbschlagVariants { get; set; } = new();
public SourceGenotype Genotype { get; set; } = new();
public string Zucht { get; set; } = "";
public List<SourceParentRef> ParentRefs { get; set; } = new();
public List<string> Photos { get; set; } = new();
public List<string> SourceFiles { get; set; } = new();
public bool Conflict { get; set; }
public SourceLitterRef? LitterRef { get; set; }
// GEN-3b normalization: hearing/deaf phenotype flag (null = not stated) and
// provenance/breeding tags (WFNZ/RV/GV/DP) — neither is genotype.
public bool? Deaf { get; set; }
public List<string> Tags { get; set; } = new();
}
public sealed class SourceGenotype
{
public Dictionary<string, List<string>> Mapped8locus { get; set; } = new();
public string RawGenotype { get; set; } = "";
public List<string> UnmappedTokens { get; set; } = new();
}
public sealed class SourceParentRef
{
public string Name { get; set; } = "";
public string Dob { get; set; } = "";
public string RoleGuess { get; set; } = ""; // "father" | "mother"
public string Method { get; set; } = ""; // e.g. "chart-position"
public string Confidence { get; set; } = ""; // "hoch" | "medium" | "niedrig"
}
public sealed class SourceLitterRef
{
public string LitterId { get; set; } = "";
public string Method { get; set; } = "";
public string Confidence { get; set; } = ""; // "hoch" | "niedrig" | (ambiguous => candidates)
public List<string>? Candidates { get; set; }
}
public sealed class SourceLitter
{
public string Id { get; set; } = "";
public string LitterId { get; set; } = "";
public string Date { get; set; } = "";
public string DamName { get; set; } = "";
public string SireName { get; set; } = "";
public int? TotalBorn { get; set; }
public string Zuchtnummer { get; set; } = "";
public string Note { get; set; } = "";
public List<string> Warnings { get; set; } = new();
}
// ---- Report shapes (Julian-readable: counts per category + samples) ----
public sealed record ImportReport(
bool Executed,
LitterSummary Litters,
AnimalSummary Animals,
PhotoSummary Photos,
IReadOnlyList<string> Samples,
IReadOnlyList<string> Notes);
public sealed record LitterSummary(int InSource, int Created, int AlreadyImported, int DerivedFromChart = 0);
public sealed record AnimalSummary(
int InSource,
int Created,
int LinkedToLitter,
int FarbschlagMatched,
int FarbschlagUnmatched,
int AlreadyImported,
QuarantineSummary Quarantined,
int ParentLinksFromChart = 0);
public sealed record QuarantineSummary(
int Conflicts,
int Stubs,
int DateOnlyLinks,
int AmbiguousLinks,
int Total);
public sealed record PhotoSummary(int Attached, int SourceFilesMissing);
}