Deployment: - custom-app.compose.yaml: self-contained Compose fuer TrueNAS "Custom App" (absolute Host-Bind-Pfade, postgres:18, pull_policy always, Port 8090) - scripts/truenas-deploy.sh: Host-Skript create/redeploy via midclt (App bleibt unter Apps sichtbar) inkl. Image-Pull + Health-Check - ci.yml Deploy-Job: laeuft auf ubuntu-latest-Runner, kopiert Deploy-Dateien per SSH auf den NAS-Host und triggert truenas-deploy.sh (statt runs-on goldeye) - compose.yaml/.env.example: postgres:18 (Locale-Match zur Quell-DB), Port 8090 - .gitignore: .agents/, tools/rag/, deploy/truenas/.env (Secrets/Scratch) Aufgelaufene Feature-Arbeit (verified/Freeze, Migrationen, Import-Triage): - GerbilOverride/VerifiedGerbil-Endpoints + GerbilSnapshotService + Tests - EF-Migrationen (ShowInChronicle, Stillborn, BirthOrder, ManualFlag, DSGVO) - Frontend VerifizierteTierePage + verified-API + e2e-Spec - diverse Import-/Triage-Skripte und -Tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
118 lines
5.1 KiB
C#
118 lines
5.1 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace GerbilManagerWebAPI.Import.Rpro3
|
|
{
|
|
/// <summary>
|
|
/// Manuelle Entscheidungen der Züchterin zu mehrdeutigen RPRO3-Namensdubletten
|
|
/// (aus den „Fehler melden"-Tickets, Kontext „rpro3-import"). Der automatische
|
|
/// <see cref="Rpro3Dedup"/> ist konservativ und legt nur bei positiver Evidenz zusammen;
|
|
/// diese Overrides erlauben der Züchterin, gezielt zu korrigieren:
|
|
///
|
|
/// • <c>Same</c> — Gruppen von RPRO3-Nummern (rids), die DASSELBE Tier sind
|
|
/// (zwingt ein Merge über Varianten hinweg; ein rid je Variante genügt,
|
|
/// der ganze Cluster wird mitgezogen).
|
|
/// • <c>Different</c>— Gruppen, die VERSCHIEDENE Tiere sind (verhindert ein automatisches
|
|
/// Zusammenlegen über Gruppengrenzen).
|
|
/// • <c>Fields</c> — Feld-Korrekturen am resultierenden Tier (Farbe/Geburtsdatum/Herkunft/
|
|
/// Bestands-Flag/Zusatznotiz), adressiert über IRGENDEINE rid des Clusters.
|
|
///
|
|
/// Datenquelle: <c>Import/Rpro3/rpro3-decisions.json</c> (siehe <see cref="Load"/>).
|
|
/// Stabiler Schlüssel ist die RPRO3-Nummer (rid), nicht der Variantenbuchstabe — Buchstaben
|
|
/// verschieben sich, sobald sich die Clusterbildung ändert.
|
|
/// </summary>
|
|
public sealed class Rpro3Decisions
|
|
{
|
|
public List<Rpro3Decision> Decisions { get; set; } = new();
|
|
|
|
public static Rpro3Decisions Empty { get; } = new();
|
|
|
|
/// <summary>Lädt die Entscheidungen aus JSON; fehlt die Datei oder ist sie leer/kaputt,
|
|
/// kommt eine leere Menge zurück (Import läuft dann mit reinem Auto-Dedup).</summary>
|
|
public static Rpro3Decisions Load(string? path)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
|
return Empty;
|
|
try
|
|
{
|
|
var json = File.ReadAllText(path);
|
|
var d = JsonSerializer.Deserialize<Rpro3Decisions>(json, JsonOpts);
|
|
return d ?? Empty;
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return Empty;
|
|
}
|
|
}
|
|
|
|
private static readonly JsonSerializerOptions JsonOpts = new(JsonSerializerDefaults.Web)
|
|
{
|
|
ReadCommentHandling = JsonCommentHandling.Skip,
|
|
AllowTrailingCommas = true,
|
|
};
|
|
|
|
/// <summary>Alle „Same"-rid-Gruppen (für Force-Merge).</summary>
|
|
public IEnumerable<IReadOnlyList<string>> SameGroups()
|
|
{
|
|
foreach (var d in Decisions)
|
|
foreach (var g in d.Same)
|
|
if (g.Count >= 2) yield return g;
|
|
}
|
|
|
|
/// <summary>rid → numerische Split-Gruppe je Entscheidung. Zwei rids mit unterschiedlicher
|
|
/// Split-Gruppe (gleiche Entscheidung) dürfen NICHT automatisch zusammengelegt werden.</summary>
|
|
public Dictionary<string, int> BuildSplitGroups()
|
|
{
|
|
var map = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
int gid = 0;
|
|
foreach (var d in Decisions)
|
|
{
|
|
if (d.Different.Count < 2) continue;
|
|
foreach (var grp in d.Different)
|
|
{
|
|
gid++;
|
|
foreach (var rid in grp)
|
|
map[rid] = gid;
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
/// <summary>rid → Feld-Override (jede rid eines Clusters zeigt auf denselben Override).</summary>
|
|
public Dictionary<string, Rpro3FieldOverride> BuildFieldIndex()
|
|
{
|
|
var map = new Dictionary<string, Rpro3FieldOverride>(StringComparer.Ordinal);
|
|
foreach (var d in Decisions)
|
|
foreach (var (rid, ov) in d.Fields)
|
|
map[rid] = ov;
|
|
return map;
|
|
}
|
|
}
|
|
|
|
public sealed class Rpro3Decision
|
|
{
|
|
/// <summary>Anzeigename (nur zur Lesbarkeit der JSON; Logik nutzt rids).</summary>
|
|
public string? Name { get; set; }
|
|
/// <summary>Ticket-Id (Rückverfolgbarkeit).</summary>
|
|
public string? Ticket { get; set; }
|
|
public List<List<string>> Same { get; set; } = new();
|
|
public List<List<string>> Different { get; set; } = new();
|
|
public Dictionary<string, Rpro3FieldOverride> Fields { get; set; } = new();
|
|
}
|
|
|
|
public sealed class Rpro3FieldOverride
|
|
{
|
|
public string? Color { get; set; }
|
|
public string? Dob { get; set; } // ISO yyyy-MM-dd
|
|
public string? Origin { get; set; }
|
|
/// <summary>Roher Gencode (Fcode, z. B. „aa Cc[h] DD ee Gg P- spsp"). Überschreibt den
|
|
/// automatisch aus den Cluster-Mitgliedern gewählten Gencode — nötig, wenn sich die
|
|
/// Varianten im Gencode unterscheiden (z. B. C-Locus c[h] vs. c[chm]) und die Züchterin
|
|
/// den korrekten Genotyp vorgibt.</summary>
|
|
public string? Genotype { get; set; }
|
|
public bool? Resident { get; set; }
|
|
/// <summary>Zusatz, der an die Notizen des Tiers angehängt wird (z. B. abweichendes DOB).</summary>
|
|
public string? Note { get; set; }
|
|
}
|
|
}
|