feat(import): RPRO3-Dubletten-Entscheidungen der Züchterin als Override
Manuelle "gleich/verschieden?"-Entscheidungen aus den rpro3-import-Tickets fließen jetzt in den RennmausPro-III-Import ein: - Rpro3Decisions (Modell + JSON-Loader): same (Force-Merge), different (Force-Split), fields (Farbe/DOB/Herkunft/resident/Notiz). Schlüssel = rid. - Rpro3Dedup.Run(animals, decisions): honoriert die Overrides nach dem automatischen Dedup (eine rid zieht ihren Cluster mit). - Rpro3ImportService: lädt Import/Rpro3/rpro3-decisions.json, wendet Feld-Overrides im Plan-Builder an. - rpro3-decisions.json: 14 bestätigte Entscheidungen (Eiji, Momo, Samuel, Female, Kennedy, Fegur, Tuli, Bura, Mister X, Zoey, Max, Akiro, Merlin, Snickers). - tools/import/rpro3_lookup.py: Triage-Helfer (Farbe/Gencode/Eltern/ Nachzucht+Partner aus _rpro3.db) für die Rückfragen an die Züchterin. - 4 neue Tests (Force-Merge/Force-Split/Loader); Suite 268 grün. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,20 +20,30 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
{
|
||||
private readonly ApplicationContext _db;
|
||||
private readonly string _photoRoot;
|
||||
private readonly Rpro3Decisions _decisions;
|
||||
private Dictionary<string, Rpro3FieldOverride>? _fieldIndex;
|
||||
|
||||
public Rpro3ImportService(ApplicationContext db, IConfiguration config, IWebHostEnvironment? env)
|
||||
{
|
||||
_db = db;
|
||||
var contentRoot = env?.ContentRootPath ?? Directory.GetCurrentDirectory();
|
||||
_photoRoot = config["Photos:RootPath"] ?? Path.Combine(contentRoot, "photo-storage");
|
||||
// Manuelle Dubletten-Entscheidungen der Züchterin (siehe Rpro3Decisions). Pfad
|
||||
// überschreibbar per Config; Default: neben dem Importer-Code (wird mit ins Output kopiert).
|
||||
var decPath = config["Rpro3:DecisionsPath"]
|
||||
?? Path.Combine(contentRoot, "Import", "Rpro3", "rpro3-decisions.json");
|
||||
_decisions = Rpro3Decisions.Load(decPath);
|
||||
}
|
||||
|
||||
private Dictionary<string, Rpro3FieldOverride> FieldIndex =>
|
||||
_fieldIndex ??= _decisions.BuildFieldIndex();
|
||||
|
||||
// ───────────────────────── ANALYZE ─────────────────────────
|
||||
|
||||
public async Task<Rpro3AnalyzeResult> AnalyzeAsync(
|
||||
Rpro3Data data, bool photosProvided, IReadOnlySet<string>? availablePhotoFiles)
|
||||
{
|
||||
var dedup = Rpro3Dedup.Run(data.Animals);
|
||||
var dedup = Rpro3Dedup.Run(data.Animals, _decisions);
|
||||
var plan = BuildPlan(data, dedup);
|
||||
|
||||
// Abgleich gegen Bestand: Match über separator-insensitiven NameSearch + DOB-Toleranz.
|
||||
@@ -122,7 +132,7 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
public async Task<Rpro3ExecuteResult> ExecuteAsync(
|
||||
Rpro3Data data, string workDir, bool photosProvided, IReadOnlyDictionary<string, string>? photoSourcePaths)
|
||||
{
|
||||
var dedup = Rpro3Dedup.Run(data.Animals);
|
||||
var dedup = Rpro3Dedup.Run(data.Animals, _decisions);
|
||||
var plan = BuildPlan(data, dedup);
|
||||
|
||||
// Change-Tracker leeren: ExecuteDelete/Update umgehen den Tracker; bei wiederholtem
|
||||
@@ -407,6 +417,19 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
var origin = members.Select(m => m.Origin).FirstOrDefault(o => Rpro3Dedup.NormValue(o).Length > 0) ?? rep.Origin;
|
||||
var herkId = members.Select(m => m.OriginHerkId).FirstOrDefault(h => h is not null and not 1);
|
||||
|
||||
// Manuelle Feld-Korrektur der Züchterin (Rpro3Decisions): adressiert über IRGENDEINE
|
||||
// rid des Clusters. Überschreibt das automatisch gewählte Feld; "Note" wird angehängt.
|
||||
Rpro3FieldOverride? ov = null;
|
||||
foreach (var m in members)
|
||||
if (FieldIndex.TryGetValue(m.Rid, out ov)) break;
|
||||
bool? residentOverride = ov?.Resident;
|
||||
if (ov is not null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ov.Color)) farbe = ov.Color;
|
||||
if (!string.IsNullOrWhiteSpace(ov.Origin)) origin = ov.Origin;
|
||||
if (!string.IsNullOrWhiteSpace(ov.Dob) && DateOnly.TryParse(ov.Dob, out var od)) dob = od;
|
||||
}
|
||||
|
||||
var gp = new GerbilPlan
|
||||
{
|
||||
Id = id,
|
||||
@@ -419,7 +442,7 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
Genotype = CleanGenotype(fcode),
|
||||
OriginBreeder = herkId == 1 ? "eigene Zucht" : (Rpro3Dedup.NormValue(origin).Length > 0 ? origin : null),
|
||||
OriginContactId = HerkContactId(herkId),
|
||||
IsResident = resident,
|
||||
IsResident = residentOverride ?? resident,
|
||||
IsCastrated = members.Any(m => m.IsCastrated),
|
||||
MotherRid = rep.MidRaw,
|
||||
FatherRid = rep.PidRaw,
|
||||
@@ -440,6 +463,8 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
}
|
||||
|
||||
gp.Notes = BuildNotes(members);
|
||||
if (!string.IsNullOrWhiteSpace(ov?.Note))
|
||||
gp.Notes = string.IsNullOrWhiteSpace(gp.Notes) ? ov!.Note : $"{gp.Notes}\n{ov!.Note}";
|
||||
gp.Provenance = BuildProvenance(data, members, dedup);
|
||||
plan.Gerbils[id] = gp;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user