Files
GerbilManager/GerbilManagerWebAPI/Import/ImportModels.cs
Gulum 358e30db1f IMPORT-BACKFILL: toleranter KC-Matcher + Herkunft-Backfill + FarbschlagWouldRebackfill-Zaehler
1) TOLERANTER KC-MATCHER (extract.py norm_zucht): trailing \b nach '.' greift nicht
   zwischen two non-word-chars (z.B. nach '.' vor ' '). Fix: r\bv\.\s?d\. (kein \b
   am Ende). Alle Schreibvarianten ('Zucht der Kleinen Chaoten', 'kleinen Chaoten',
   'v.d. Kleinen Chaoten', '[ZdkC]') -> canon 'kleinechaote'. +is_clan_zucht() helper.
   +11 Python-Tests (Regression + neue Varianten). FIX-1-Entscheidungs-Matching
   unberuehrt (canon_pair loest v.d. bereits via split_name_zucht).

2) HERKUNFT-BACKFILL (ImportService.cs, fill-NULL-only): POST-Sweep ueber alle
   DB-Tiere mit OriginBreeder==null && IsResident==true. Leitet OriginBreeder aus
   dem Elterntier (FatherId/MotherId -> OriginBreeder) ab; Fallback 'Zucht der
   Kleinen Chaoten'. NIEMALS ueberschreibt nicht-leeren OriginBreeder. Trocken-Lauf
   zaehlt HerkunftBackfilled; Execute schreibt. Fixt Tier 'C' (OriginBreeder null).

3) FARBSCHLAG-WOULD-REBACKFILL (no-op Zaehler): zaehlt bereits-importierte Tiere,
   bei denen der aktuelle Extraktor einen anderen Farbschlag liefert als in der DB.
   Kein Overwrite (Sicherheitsmechanismus ausstehend auf god/Julian-Freigabe);
   Optionen A/B/C an god gemeldet, Empfehlung: Option B (Timestamp-basiert).

4) ResidencySummary um HerkunftBackfilled + FarbschlagWouldRebackfill erweitert.
   ImportReport-Notizen fuer beide neuen Zaehler.

GATE: 133/133 C#-Tests, 57 Python-Tests, ef has-pending=No.
2026-06-06 17:29:02 +02:00

116 lines
4.8 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 string ZuchtCanon { get; set; } = ""; // declension-folded Zucht key (residency rule a)
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();
// Set by extract.py when a human conflict-decision (conflict-decisions.json) un-quarantined
// this animal (its genotype/farbschlag are then authoritative). For reporting.
public bool ResolvedByDecision { get; set; }
}
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,
ResidencySummary? Residency = null);
/// <summary>Bestand (resident) vs external pedigree ancestors; FlippedByParentRule = foreign-
/// Zuchtname animals made resident because they parented a Clan offspring (rule b).
/// HerkunftBackfilled = resident animals whose null OriginBreeder was filled (safe, no overwrite).
/// FarbschlagWouldRebackfill = already-imported animals with a stale ColorVariety that the
/// current extract would update (no-op counter until the safety mechanism is ratified).</summary>
public sealed record ResidencySummary(int Resident, int External, int FlippedByParentRule,
int HerkunftBackfilled = 0, int FarbschlagWouldRebackfill = 0);
public sealed record LitterSummary(int InSource, int Created, int AlreadyImported,
int DerivedFromChart = 0, int DerivedSkipped = 0, int ParentFksDropped = 0,
int ParentFksBackfilled = 0, int WithoutDate = 0);
public sealed record AnimalSummary(
int InSource,
int Created,
int LinkedToLitter,
int FarbschlagMatched,
int FarbschlagUnmatched,
int AlreadyImported,
QuarantineSummary Quarantined,
int ParentLinksFromChart = 0,
int ConflictsResolvedByDecision = 0);
public sealed record QuarantineSummary(
int Conflicts,
int Stubs,
int DateOnlyLinks,
int AmbiguousLinks,
int Total);
public sealed record PhotoSummary(int Attached, int SourceFilesMissing);
}