Compare commits

..

12 Commits

Author SHA1 Message Date
0e7ec5ab61 BESTAND-FILTER follow-up: residency toggle on the Gerbil create/edit form
Completes residency end-to-end: a 'Gehört zum eigenen Bestand' checkbox on the
Rennmaus-Formular (default on for new animals) lets the user mark a tier as an
external ancestor by hand — previously isResident was only set at import. Wired
through FormState + formFromGerbil (defaults true) + the CreateGerbil payload.

German via de.ts (LF). e2e: edit Krümel, untick Bestand, save -> detail shows
the 'Extern' badge (proves form field -> payload -> persistence). Gate green:
build, eslint, vitest 78, e2e 86.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:12:32 +02:00
12604fba7c data(import): D4 Victoria Welby resolved (ee[f]) — C's mother → C gets both parents — Julian 2026-06-06 12:12:23 +02:00
311c5461fd data(import): Chelsea (merge to *02.04.2021, correctDob) + Vestra (DD) resolved — Julian 2026-06-06 12:11:41 +02:00
3634d6ef9a data(import): D3 Silvain resolved (ee, Pp) — Julian 2026-06-06 12:09:41 +02:00
aa345aa796 data(import): D3 Milka resolved (Cc[h], EE) — Julian 2026-06-06 12:08:14 +02:00
0317c5d454 Merge feature/extract-band-aware: band-aware Farbschlag + conflict-decisions consumption + GEN-3f backend re-seed (ReseedColorVarietiesGen3f) [god-QA pending] 2026-06-06 12:05:02 +02:00
ea8c3e9966 Merge feature/bestand-filter: Tiere list defaults nur-Bestand + toggle + Extern marker [god-QA pending] 2026-06-06 12:05:02 +02:00
b9033238c9 conflict-decisions: also apply dateOfDeath (D5 death-date resolutions)
god extended conflict-decisions.json with an optional dateOfDeath
(DD.MM.YYYY). apply_conflict_decisions now sets the animal's death date
(normalized) as authoritative when present — clearing D5 death-date
conflicts the same way genotype/farbschlag decisions are applied. No C#
change (death already flows to Gerbil.DateOfDeath). test_extract covers a
Flint dateOfDeath resolution. python green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:02:18 +02:00
594923e10f GEN-3f backend re-seed: ColorVariety catalog 73 -> 61 (CP- names)
Re-synced SeedColorVarieties from Kevin's merged GEN-3f frontend seed
(colorVarietySeed.generated.json, 61 rows) — verified byte-identical
(name + canonicalGenotype + order). Changes are all in the cchm colourpoint
group: 'Siam (Marder-Hell)' -> 'Siam' (het cchmch), CP suffix names ->
CP- prefix (CP-Agouti/CP-Silberagouti/CP-Algierfuchs/CP-Polarfuchs), new
CP-Fuchs/CP-Fuchs-Hell/CP-Blaufuchs, Zobel-Hell kept (het); 12 zero-
occurrence CP/dd siblings removed.

Migration ReseedColorVarietiesGen3f: Up = 38 UpdateData (rename/remap IN
PLACE on the same deterministic Id — references survive) + 12 DeleteData of
the removed rows (0 occurrences in her data per Kevin; Gerbil.ColorVarietyId
is ON DELETE SET NULL, and the live re-import re-matches Farbschlag BY NAME,
so no orphan/hard-delete risk). No AddColumn. has-pending-model-changes
clean. Updated the export test's seed-count threshold (>=70 -> >=60).

Last of the import-side batch on this branch (band-aware Farbschlag +
conflict-decisions + this re-seed) — ready for the single supervised
re-extract + idempotent re-import. dotnet 121/121 + python green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:59:57 +02:00
afd321ec9f BESTAND-FILTER: Tiere-Liste zeigt standardmäßig nur den eigenen Bestand
Die Tiere-Liste (/rennmaeuse) filtert serverseitig auf isResident==true
(eigener Clan = Bestand) und blendet externe Ahnen (nur für den Stammbaum
erfasste Fremdtiere) standardmäßig aus — passend zu Julians Modell
(Kleine Chaoten = Bestand, extern = stammbaum-only).

- Neuer Schalter „Externe Ahnen einblenden“ (Default aus). An -> isResident-
  Filter entfällt, externe Tiere erscheinen mit. Serverseitig (Gridify), damit
  Paging und Trefferzahl korrekt bleiben. „Filter zurücksetzen“ setzt ihn zurück.
- Subtile „Extern“-Markierung (gestricheltes Badge) auf externen Tieren in
  Liste UND Detailseite, mit Tooltip-Erläuterung.
- Gerbil-Typ um isResident (optional bool) ergänzt; CreateGerbil ebenso.

Deutsch via de.ts (LF), mobil + Laptop. e2e-Mock: gerbil()-Factory setzt
isResident=true; Hilde & Max als externe Ahnen (isResident=false). Neue
e2e bestand.spec (Default blendet aus; Toggle zeigt + Extern-Markierung).
Gate grün: build, eslint, vitest 78, e2e 84.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:59:19 +02:00
df13136955 Importer consumes conflict-decisions.json to un-quarantine (HUMANQUESTION D)
god maintains tools/import/conflict-decisions.json as Julian/his wife answer
the D-conflicts. extract.py now consumes it (apply_conflict_decisions): for an
animal matching normalize(name)+dob, it clears the conflict, marks
resolvedByDecision, and — when the decision carries a `genotype` (breeder
notation, parsed via genotype.py) and/or `farbschlag` — treats those as
AUTHORITATIVE. Tolerates a missing/empty/garbled file. Genuinely-unresolved
conflicts stay quarantined.

Loader (ImportService): SourceAnimal.ResolvedByDecision flows through; the
report surfaces Animals.ConflictsResolvedByDecision + a German note.

Result on real data: the 2 current decisions (Firefly D-/PP, WildFire PP)
un-quarantine → Konflikte 21 → 19. As god appends entries the count grows;
nothing else needed from me.

Tests: python test_extract (decision clears conflict + genotype authoritative
+ removes from conflicts list + tolerates missing file) and a C# loader test
(a resolved animal loads and is counted). Folded into the EXTRACT-BANDS branch
so the next re-extract applies band-aware Farbschlag + these decisions in one
pass. No schema change (JSON DTO fields). python + dotnet 121/121 green;
has-pending clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:56:29 +02:00
4aca1d528b EXTRACT band-aware Farbschlag: deep bands (gen>=2) have no colour cell
Julian-confirmed against the real xlsx (Kentucky, Akio Kids): pedigree
blocks have two shapes. EARLY bands (gen 0-1, cols B/E/H) are 5-cell
Name/DOB/Farbschlag/Genotype/Zucht — real Farbschlag present. DEEP bands
(gen>=2, cols K/N/Q...) are 3-cell Name/DOB/Genotype with NO Farbschlag —
the colour is derived from the genotype.

The old extractor grabbed the NEXT block's name or a stray health note
(e.g. "DD-Tumor", "Chronische Ohrenentzündung...") as the deep-band
Farbschlag — source of the D1 'name-in-Farbschlag' conflicts and a big
chunk of farbschlagUnmatched. Fix: gen_of(col)>=2 => never assign a
Farbschlag in that block (Name/DOB/Genotype only); the cell is left for the
next block's name search. Deep-band animals load with empty Farbschlag and
the UI computes 'Errechnet' from the stored genotype (lossless).

Dedup already prefers a real (non-empty) early-band Farbschlag, so an animal
appearing in both an early band (Chesnut -> 'Kohlfuchsschimmel') and deep
bands keeps the real value; Chesnut and Tennessee stay separate (distinct
genotype + name+DOB key).

Result: 248/306 loadable animals now have empty Farbschlag (genotype-
derived), 0 suspicious name/note values remain, 0 Farbschlag-based conflicts
(D1 dissolved). Regression test test_extract.py builds a two-band xlsx and
asserts the deep band yields no Farbschlag + the stray note doesn't bleed.

Extractor-only (no C# change). python test_genotype + test_extract green;
dotnet 118/118. Batches with Kevin's GEN-3f seed for one re-import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:56:29 +02:00
21 changed files with 2388 additions and 200 deletions

View File

@@ -104,7 +104,7 @@ namespace GerbilManager.Tests
Assert.Equal(1, root.GetProperty("gerbils").GetArrayLength());
Assert.Equal(1, root.GetProperty("litters").GetArrayLength());
Assert.Equal(1, root.GetProperty("contacts").GetArrayLength());
Assert.True(root.GetProperty("colorVarieties").GetArrayLength() >= 70); // HasData-Seed
Assert.True(root.GetProperty("colorVarieties").GetArrayLength() >= 60); // HasData-Seed (61 nach GEN-3f)
var g = root.GetProperty("gerbils")[0];
Assert.Equal("Aa CC Dd EE GG Pp Spsp rere", g.GetProperty("genotype").GetString());
Assert.Equal("Stammbaum von Akio Kids.xlsx", g.GetProperty("importSource").GetString());

View File

@@ -278,6 +278,33 @@ namespace GerbilManager.Tests
finally { try { Directory.Delete(dir, recursive: true); } catch { } }
}
[Fact]
public async Task Decision_resolved_animal_loads_and_is_counted()
{
// extract.py clears the conflict + sets resolvedByDecision when a human conflict-decision
// un-quarantines an animal; the loader must then LOAD it and surface the count.
var dir = Path.Combine(Path.GetTempPath(), "dec-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(dir);
try
{
File.WriteAllText(Path.Combine(dir, "litters.json"), "[]");
File.WriteAllText(Path.Combine(dir, "animals.json"), """
[
{"id":"firefly","name":"Firefly","dob":"18.12.2019","death":"","farbschlag":"Agouti",
"genotype":{"mapped8locus":{"A":["A","a"],"D":["D","?"]},"rawGenotype":"Aa D-","unmappedTokens":[]},
"conflict":false,"resolvedByDecision":true}
]
""");
using var db = NewDb();
var report = await new ImportService(db, dir, dir).RunAsync(execute: true);
Assert.Equal(1, report.Animals.ConflictsResolvedByDecision);
Assert.True(await db.Gerbils.AnyAsync(g => g.ExternalRef == "firefly")); // loaded, not quarantined
Assert.Equal(0, report.Animals.Quarantined.Conflicts);
}
finally { try { Directory.Delete(dir, recursive: true); } catch { } }
}
[Fact]
public void ComposeGenotype_strips_carets_and_fills_missing_loci()
{

View File

@@ -256,8 +256,6 @@ public class ApplicationContext : DbContext
("C-Separator", "aa CC DD ee gg pp spsp rere"),
("Elfenbein", "AA CC DD EE gg pp spsp rere"),
("Kohlfuchs", "aa CC DD ee GG PP spsp rere"),
("Marder", "aa cchmcchm DD EE GG PP spsp rere"),
("Siam (Marder-Hell)", "aa cchmcchm DD EE GG PP spsp rere"),
("Polarfuchs", "AA CC DD ee gg PP spsp rere"),
("Saphir", "aa CC DD EE GG pp spsp rere"),
("Orangeschimmel", "AA CC DD efef GG PP spsp rere"),
@@ -267,44 +265,34 @@ public class ApplicationContext : DbContext
("Silberagouti dd", "AA CC dd EE gg PP spsp rere"),
("Kohlfuchs dd", "aa CC dd ee GG PP spsp rere"),
("Anthrazit dd", "aa CC dd EE gg PP spsp rere"),
("Agouti CP-Hell", "AA cchmcchm DD EE GG PP spsp rere"),
("Blaufuchs CP", "aa cchmcchm DD ee gg PP spsp rere"),
("Silberschimmel", "AA CC DD efef gg PP spsp rere"),
("Polarfuchsschimmel", "AA CC DD efef gg PP spsp rere"),
("Algierfuchsschimmel", "AA CC DD efef GG PP spsp rere"),
("Polarfuchs-Hell CP", "AA cchmcchm DD ee gg PP spsp rere"),
("Kohlfuchsschimmel", "aa CC DD efef GG PP spsp rere"),
("Blaufuchsschimmel", "aa CC DD efef gg PP spsp rere"),
("Kohlfuchs, hell", "aa CC DD ee GG PP spsp rere"),
("Goldfuchs, hell", "AA CC DD ee GG pp spsp rere"),
("Goldfuchsschimmel", "AA CC DD efef GG pp spsp rere"),
("Gold-Hell", "AA CC DD EE GG pp spsp rere"),
("Siam (Marder-Hell) dd", "aa cchmcchm dd EE GG PP spsp rere"),
("Marder dd", "aa cchmcchm dd EE GG PP spsp rere"),
("Zobel-Hell", "aa cchmcchm DD EE gg PP spsp rere"),
("Silberagouti dd CP", "AA cchmcchm dd EE gg PP spsp rere"),
("Silberagouti-Hell dd CP", "AA cchmcchm dd EE gg PP spsp rere"),
("Agouti dd CP", "AA cchmcchm dd EE GG PP spsp rere"),
("Agouti-Hell dd CP", "AA cchmcchm dd EE GG PP spsp rere"),
("Blaufuchs, hell", "aa CC DD ee gg PP spsp rere"),
("Rotfuchsschimmel", "aa CC DD efef GG pp spsp rere"),
("Polarfuchs, hell", "AA CC DD ee gg PP spsp rere"),
("Kohlfuchsschimmel, hell", "aa CC DD efef GG PP spsp rere"),
("Rotfuchs, hell", "aa CC DD ee GG pp spsp rere"),
("Zobel dd", "aa cchmcchm dd EE gg PP spsp rere"),
("Kohlfuchs-Hell", "aa CC DD ee GG PP spsp rere"),
("Kohlfuchs CP", "aa cchmcchm DD ee GG PP spsp rere"),
("Algierfuchs CP", "AA cchmcchm DD ee GG PP spsp rere"),
("Silberagouti CP", "AA cchmcchm DD EE gg PP spsp rere"),
("Agouti CP", "AA cchmcchm DD EE GG PP spsp rere"),
("Algierfuchs-Hell CP", "AA cchmcchm DD ee GG PP spsp rere"),
("Kohlfuchs,hell CP", "aa cchmcchm DD ee GG PP spsp rere"),
("Polarfuchs CP", "AA cchmcchm DD ee gg PP spsp rere"),
("Algierfuchs, hell", "AA CC DD ee GG PP spsp rere"),
("Topas dd", "AA CC dd EE GG pp spsp rere"),
("Zobel-Hell dd", "aa cchmcchm dd EE gg PP spsp rere"),
("Kohlfuchsschimmel CP", "aa cchmcchm DD efef GG PP spsp rere"),
("Blaufuchs dd", "aa CC dd ee gg pp spsp rere"),
("Marder", "aa cchmcchm DD EE GG PP spsp rere"),
("Siam", "aa cchmch DD EE GG PP spsp rere"),
("Zobel-Hell", "aa cchmch DD EE gg PP spsp rere"),
("CP-Agouti", "AA cchmcchm DD EE GG PP spsp rere"),
("CP-Silberagouti", "AA cchmcchm DD EE gg PP spsp rere"),
("CP-Algierfuchs", "AA cchmcchm DD ee GG PP spsp rere"),
("CP-Polarfuchs", "AA cchmcchm DD ee gg PP spsp rere"),
("CP-Fuchs", "AA cchmcchm dd ee GG PP spsp rere"),
("CP-Fuchs-Hell", "AA cchmch dd ee GG PP spsp rere"),
("CP-Blaufuchs", "AA cchmcchm dd ee gg PP spsp rere"),
("CP-Orangeschimmel", "AA cchmcchm DD efef GG PP spsp rere"),
};

View File

@@ -27,6 +27,10 @@ namespace GerbilManagerWebAPI.Import
// 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
@@ -92,7 +96,8 @@ namespace GerbilManagerWebAPI.Import
int FarbschlagUnmatched,
int AlreadyImported,
QuarantineSummary Quarantined,
int ParentLinksFromChart = 0);
int ParentLinksFromChart = 0,
int ConflictsResolvedByDecision = 0);
public sealed record QuarantineSummary(
int Conflicts,

View File

@@ -404,6 +404,9 @@ namespace GerbilManagerWebAPI.Import
notes.Add($"Stammbaum-Diagramm: {parentLinksAdded} Tiere über Eltern-Verknüpfung einem (abgeleiteten) Wurf zugeordnet ({derivedLitters} abgeleitete Würfe).");
notes.Add($"FK-Integrität: {litterParentFksDropped} Eltern-Verknüpfung(en) verworfen (Elternteil nicht ladbar), {derivedLittersSkipped} abgeleitete Würfe übersprungen (kein ladbares Elternteil). Bei 0/0 ist /import/execute FK-sicher.");
notes.Add($"Bestand/Herkunft: {residentTotal} im Bestand (Clan Kleine Chaoten), {externalTotal} externe Ahnen ({flippedByParentRule} davon über die Eltern-Regel als Bestand erkannt).");
int conflictsResolvedByDecision = loadable.Count(a => a.ResolvedByDecision);
if (conflictsResolvedByDecision > 0)
notes.Add($"Konfliktauflösungen: {conflictsResolvedByDecision} Tier(e) anhand von conflict-decisions.json un-quarantänet (Genotyp/Farbschlag der Züchterin ist maßgeblich).");
if (!execute) notes.Add("DRY-RUN: nichts gespeichert. /import/execute lädt die konfliktfreien Daten.");
return new ImportReport(
@@ -412,7 +415,7 @@ namespace GerbilManagerWebAPI.Import
Animals: new AnimalSummary(
animals.Count, animalsCreated, linked, fbMatched, fbUnmatched, animalsExisting,
new QuarantineSummary(conflicts, stubs, dateOnly, ambiguous, conflicts + stubs),
parentLinksAdded),
parentLinksAdded, conflictsResolvedByDecision),
Photos: new PhotoSummary(photosAttached, photosMissing),
Samples: samples,
Notes: notes,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,632 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace GerbilManagerWebAPI.Migrations
{
/// <inheritdoc />
public partial class ReseedColorVarietiesGen3f : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000062"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000063"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000064"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000065"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000066"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000067"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000068"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000069"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000070"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000071"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000072"));
migrationBuilder.DeleteData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000073"));
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000024"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD ee gg PP spsp rere", "Polarfuchs" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000025"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD EE GG pp spsp rere", "Saphir" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000026"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef GG PP spsp rere", "Orangeschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000027"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD EE GG pp spsp rere", "Topas" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000028"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD EE GG pp spsp rere", "Platin-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000029"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC dd EE GG PP spsp rere", "Agouti dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000030"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC dd EE gg PP spsp rere", "Silberagouti dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000031"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC dd ee GG PP spsp rere", "Kohlfuchs dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000032"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC dd EE gg PP spsp rere", "Anthrazit dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000033"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef gg PP spsp rere", "Silberschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000034"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef gg PP spsp rere", "Polarfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000035"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef GG PP spsp rere", "Algierfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000036"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD efef GG PP spsp rere", "Kohlfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000037"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD efef gg PP spsp rere", "Blaufuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000038"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD ee GG PP spsp rere", "Kohlfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000039"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD ee GG pp spsp rere", "Goldfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000040"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef GG pp spsp rere", "Goldfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000041"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD EE GG pp spsp rere", "Gold-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000042"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD ee gg PP spsp rere", "Blaufuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000043"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD efef GG pp spsp rere", "Rotfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000044"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD ee gg PP spsp rere", "Polarfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000045"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD efef GG PP spsp rere", "Kohlfuchsschimmel, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000046"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD ee GG pp spsp rere", "Rotfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000047"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD ee GG PP spsp rere", "Kohlfuchs-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000048"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD ee GG PP spsp rere", "Algierfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000049"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC dd EE GG pp spsp rere", "Topas dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000050"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC dd ee gg pp spsp rere", "Blaufuchs dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000051"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm DD EE GG PP spsp rere", "Marder" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000052"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmch DD EE GG PP spsp rere", "Siam" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000053"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmch DD EE gg PP spsp rere", "Zobel-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000054"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm DD EE GG PP spsp rere", "CP-Agouti" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000055"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm DD EE gg PP spsp rere", "CP-Silberagouti" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000056"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm DD ee GG PP spsp rere", "CP-Algierfuchs" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000057"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm DD ee gg PP spsp rere", "CP-Polarfuchs" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000058"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm dd ee GG PP spsp rere", "CP-Fuchs" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000059"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmch dd ee GG PP spsp rere", "CP-Fuchs-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000060"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm dd ee gg PP spsp rere", "CP-Blaufuchs" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000061"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm DD efef GG PP spsp rere", "CP-Orangeschimmel" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000024"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm DD EE GG PP spsp rere", "Marder" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000025"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm DD EE GG PP spsp rere", "Siam (Marder-Hell)" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000026"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD ee gg PP spsp rere", "Polarfuchs" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000027"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD EE GG pp spsp rere", "Saphir" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000028"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef GG PP spsp rere", "Orangeschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000029"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD EE GG pp spsp rere", "Topas" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000030"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD EE GG pp spsp rere", "Platin-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000031"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC dd EE GG PP spsp rere", "Agouti dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000032"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC dd EE gg PP spsp rere", "Silberagouti dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000033"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC dd ee GG PP spsp rere", "Kohlfuchs dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000034"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC dd EE gg PP spsp rere", "Anthrazit dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000035"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm DD EE GG PP spsp rere", "Agouti CP-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000036"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm DD ee gg PP spsp rere", "Blaufuchs CP" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000037"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef gg PP spsp rere", "Silberschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000038"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef gg PP spsp rere", "Polarfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000039"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef GG PP spsp rere", "Algierfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000040"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm DD ee gg PP spsp rere", "Polarfuchs-Hell CP" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000041"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD efef GG PP spsp rere", "Kohlfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000042"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD efef gg PP spsp rere", "Blaufuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000043"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD ee GG PP spsp rere", "Kohlfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000044"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD ee GG pp spsp rere", "Goldfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000045"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD efef GG pp spsp rere", "Goldfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000046"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD EE GG pp spsp rere", "Gold-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000047"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm dd EE GG PP spsp rere", "Siam (Marder-Hell) dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000048"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm dd EE GG PP spsp rere", "Marder dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000049"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm DD EE gg PP spsp rere", "Zobel-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000050"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm dd EE gg PP spsp rere", "Silberagouti dd CP" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000051"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm dd EE gg PP spsp rere", "Silberagouti-Hell dd CP" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000052"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm dd EE GG PP spsp rere", "Agouti dd CP" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000053"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA cchmcchm dd EE GG PP spsp rere", "Agouti-Hell dd CP" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000054"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD ee gg PP spsp rere", "Blaufuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000055"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD efef GG pp spsp rere", "Rotfuchsschimmel" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000056"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "AA CC DD ee gg PP spsp rere", "Polarfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000057"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD efef GG PP spsp rere", "Kohlfuchsschimmel, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000058"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD ee GG pp spsp rere", "Rotfuchs, hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000059"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm dd EE gg PP spsp rere", "Zobel dd" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000060"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa CC DD ee GG PP spsp rere", "Kohlfuchs-Hell" });
migrationBuilder.UpdateData(
table: "ColorVarieties",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000061"),
columns: new[] { "CanonicalGenotype", "Name" },
values: new object[] { "aa cchmcchm DD ee GG PP spsp rere", "Kohlfuchs CP" });
migrationBuilder.InsertData(
table: "ColorVarieties",
columns: new[] { "Id", "CanonicalGenotype", "Name", "SortOrder" },
values: new object[,]
{
{ new Guid("00000000-0000-0000-0000-000000000062"), "AA cchmcchm DD ee GG PP spsp rere", "Algierfuchs CP", 61 },
{ new Guid("00000000-0000-0000-0000-000000000063"), "AA cchmcchm DD EE gg PP spsp rere", "Silberagouti CP", 62 },
{ new Guid("00000000-0000-0000-0000-000000000064"), "AA cchmcchm DD EE GG PP spsp rere", "Agouti CP", 63 },
{ new Guid("00000000-0000-0000-0000-000000000065"), "AA cchmcchm DD ee GG PP spsp rere", "Algierfuchs-Hell CP", 64 },
{ new Guid("00000000-0000-0000-0000-000000000066"), "aa cchmcchm DD ee GG PP spsp rere", "Kohlfuchs,hell CP", 65 },
{ new Guid("00000000-0000-0000-0000-000000000067"), "AA cchmcchm DD ee gg PP spsp rere", "Polarfuchs CP", 66 },
{ new Guid("00000000-0000-0000-0000-000000000068"), "AA CC DD ee GG PP spsp rere", "Algierfuchs, hell", 67 },
{ new Guid("00000000-0000-0000-0000-000000000069"), "AA CC dd EE GG pp spsp rere", "Topas dd", 68 },
{ new Guid("00000000-0000-0000-0000-000000000070"), "aa cchmcchm dd EE gg PP spsp rere", "Zobel-Hell dd", 69 },
{ new Guid("00000000-0000-0000-0000-000000000071"), "aa cchmcchm DD efef GG PP spsp rere", "Kohlfuchsschimmel CP", 70 },
{ new Guid("00000000-0000-0000-0000-000000000072"), "aa CC dd ee gg pp spsp rere", "Blaufuchs dd", 71 },
{ new Guid("00000000-0000-0000-0000-000000000073"), "AA cchmcchm DD efef GG PP spsp rere", "CP-Orangeschimmel", 72 }
});
}
}
}

View File

@@ -343,352 +343,268 @@ namespace GerbilManagerWebAPI.Migrations
new
{
Id = new Guid("00000000-0000-0000-0000-000000000024"),
CanonicalGenotype = "aa cchmcchm DD EE GG PP spsp rere",
Name = "Marder",
CanonicalGenotype = "AA CC DD ee gg PP spsp rere",
Name = "Polarfuchs",
SortOrder = 23
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000025"),
CanonicalGenotype = "aa cchmcchm DD EE GG PP spsp rere",
Name = "Siam (Marder-Hell)",
CanonicalGenotype = "aa CC DD EE GG pp spsp rere",
Name = "Saphir",
SortOrder = 24
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000026"),
CanonicalGenotype = "AA CC DD ee gg PP spsp rere",
Name = "Polarfuchs",
CanonicalGenotype = "AA CC DD efef GG PP spsp rere",
Name = "Orangeschimmel",
SortOrder = 25
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000027"),
CanonicalGenotype = "aa CC DD EE GG pp spsp rere",
Name = "Saphir",
CanonicalGenotype = "AA CC DD EE GG pp spsp rere",
Name = "Topas",
SortOrder = 26
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000028"),
CanonicalGenotype = "AA CC DD efef GG PP spsp rere",
Name = "Orangeschimmel",
CanonicalGenotype = "aa CC DD EE GG pp spsp rere",
Name = "Platin-Hell",
SortOrder = 27
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000029"),
CanonicalGenotype = "AA CC DD EE GG pp spsp rere",
Name = "Topas",
CanonicalGenotype = "AA CC dd EE GG PP spsp rere",
Name = "Agouti dd",
SortOrder = 28
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000030"),
CanonicalGenotype = "aa CC DD EE GG pp spsp rere",
Name = "Platin-Hell",
CanonicalGenotype = "AA CC dd EE gg PP spsp rere",
Name = "Silberagouti dd",
SortOrder = 29
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000031"),
CanonicalGenotype = "AA CC dd EE GG PP spsp rere",
Name = "Agouti dd",
CanonicalGenotype = "aa CC dd ee GG PP spsp rere",
Name = "Kohlfuchs dd",
SortOrder = 30
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000032"),
CanonicalGenotype = "AA CC dd EE gg PP spsp rere",
Name = "Silberagouti dd",
CanonicalGenotype = "aa CC dd EE gg PP spsp rere",
Name = "Anthrazit dd",
SortOrder = 31
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000033"),
CanonicalGenotype = "aa CC dd ee GG PP spsp rere",
Name = "Kohlfuchs dd",
CanonicalGenotype = "AA CC DD efef gg PP spsp rere",
Name = "Silberschimmel",
SortOrder = 32
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000034"),
CanonicalGenotype = "aa CC dd EE gg PP spsp rere",
Name = "Anthrazit dd",
CanonicalGenotype = "AA CC DD efef gg PP spsp rere",
Name = "Polarfuchsschimmel",
SortOrder = 33
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000035"),
CanonicalGenotype = "AA cchmcchm DD EE GG PP spsp rere",
Name = "Agouti CP-Hell",
CanonicalGenotype = "AA CC DD efef GG PP spsp rere",
Name = "Algierfuchsschimmel",
SortOrder = 34
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000036"),
CanonicalGenotype = "aa cchmcchm DD ee gg PP spsp rere",
Name = "Blaufuchs CP",
CanonicalGenotype = "aa CC DD efef GG PP spsp rere",
Name = "Kohlfuchsschimmel",
SortOrder = 35
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000037"),
CanonicalGenotype = "AA CC DD efef gg PP spsp rere",
Name = "Silberschimmel",
CanonicalGenotype = "aa CC DD efef gg PP spsp rere",
Name = "Blaufuchsschimmel",
SortOrder = 36
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000038"),
CanonicalGenotype = "AA CC DD efef gg PP spsp rere",
Name = "Polarfuchsschimmel",
CanonicalGenotype = "aa CC DD ee GG PP spsp rere",
Name = "Kohlfuchs, hell",
SortOrder = 37
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000039"),
CanonicalGenotype = "AA CC DD efef GG PP spsp rere",
Name = "Algierfuchsschimmel",
CanonicalGenotype = "AA CC DD ee GG pp spsp rere",
Name = "Goldfuchs, hell",
SortOrder = 38
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000040"),
CanonicalGenotype = "AA cchmcchm DD ee gg PP spsp rere",
Name = "Polarfuchs-Hell CP",
CanonicalGenotype = "AA CC DD efef GG pp spsp rere",
Name = "Goldfuchsschimmel",
SortOrder = 39
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000041"),
CanonicalGenotype = "aa CC DD efef GG PP spsp rere",
Name = "Kohlfuchsschimmel",
CanonicalGenotype = "AA CC DD EE GG pp spsp rere",
Name = "Gold-Hell",
SortOrder = 40
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000042"),
CanonicalGenotype = "aa CC DD efef gg PP spsp rere",
Name = "Blaufuchsschimmel",
CanonicalGenotype = "aa CC DD ee gg PP spsp rere",
Name = "Blaufuchs, hell",
SortOrder = 41
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000043"),
CanonicalGenotype = "aa CC DD ee GG PP spsp rere",
Name = "Kohlfuchs, hell",
CanonicalGenotype = "aa CC DD efef GG pp spsp rere",
Name = "Rotfuchsschimmel",
SortOrder = 42
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000044"),
CanonicalGenotype = "AA CC DD ee GG pp spsp rere",
Name = "Goldfuchs, hell",
CanonicalGenotype = "AA CC DD ee gg PP spsp rere",
Name = "Polarfuchs, hell",
SortOrder = 43
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000045"),
CanonicalGenotype = "AA CC DD efef GG pp spsp rere",
Name = "Goldfuchsschimmel",
CanonicalGenotype = "aa CC DD efef GG PP spsp rere",
Name = "Kohlfuchsschimmel, hell",
SortOrder = 44
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000046"),
CanonicalGenotype = "AA CC DD EE GG pp spsp rere",
Name = "Gold-Hell",
CanonicalGenotype = "aa CC DD ee GG pp spsp rere",
Name = "Rotfuchs, hell",
SortOrder = 45
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000047"),
CanonicalGenotype = "aa cchmcchm dd EE GG PP spsp rere",
Name = "Siam (Marder-Hell) dd",
CanonicalGenotype = "aa CC DD ee GG PP spsp rere",
Name = "Kohlfuchs-Hell",
SortOrder = 46
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000048"),
CanonicalGenotype = "aa cchmcchm dd EE GG PP spsp rere",
Name = "Marder dd",
CanonicalGenotype = "AA CC DD ee GG PP spsp rere",
Name = "Algierfuchs, hell",
SortOrder = 47
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000049"),
CanonicalGenotype = "aa cchmcchm DD EE gg PP spsp rere",
Name = "Zobel-Hell",
CanonicalGenotype = "AA CC dd EE GG pp spsp rere",
Name = "Topas dd",
SortOrder = 48
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000050"),
CanonicalGenotype = "AA cchmcchm dd EE gg PP spsp rere",
Name = "Silberagouti dd CP",
CanonicalGenotype = "aa CC dd ee gg pp spsp rere",
Name = "Blaufuchs dd",
SortOrder = 49
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000051"),
CanonicalGenotype = "AA cchmcchm dd EE gg PP spsp rere",
Name = "Silberagouti-Hell dd CP",
CanonicalGenotype = "aa cchmcchm DD EE GG PP spsp rere",
Name = "Marder",
SortOrder = 50
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000052"),
CanonicalGenotype = "AA cchmcchm dd EE GG PP spsp rere",
Name = "Agouti dd CP",
CanonicalGenotype = "aa cchmch DD EE GG PP spsp rere",
Name = "Siam",
SortOrder = 51
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000053"),
CanonicalGenotype = "AA cchmcchm dd EE GG PP spsp rere",
Name = "Agouti-Hell dd CP",
CanonicalGenotype = "aa cchmch DD EE gg PP spsp rere",
Name = "Zobel-Hell",
SortOrder = 52
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000054"),
CanonicalGenotype = "aa CC DD ee gg PP spsp rere",
Name = "Blaufuchs, hell",
CanonicalGenotype = "AA cchmcchm DD EE GG PP spsp rere",
Name = "CP-Agouti",
SortOrder = 53
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000055"),
CanonicalGenotype = "aa CC DD efef GG pp spsp rere",
Name = "Rotfuchsschimmel",
CanonicalGenotype = "AA cchmcchm DD EE gg PP spsp rere",
Name = "CP-Silberagouti",
SortOrder = 54
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000056"),
CanonicalGenotype = "AA CC DD ee gg PP spsp rere",
Name = "Polarfuchs, hell",
CanonicalGenotype = "AA cchmcchm DD ee GG PP spsp rere",
Name = "CP-Algierfuchs",
SortOrder = 55
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000057"),
CanonicalGenotype = "aa CC DD efef GG PP spsp rere",
Name = "Kohlfuchsschimmel, hell",
CanonicalGenotype = "AA cchmcchm DD ee gg PP spsp rere",
Name = "CP-Polarfuchs",
SortOrder = 56
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000058"),
CanonicalGenotype = "aa CC DD ee GG pp spsp rere",
Name = "Rotfuchs, hell",
CanonicalGenotype = "AA cchmcchm dd ee GG PP spsp rere",
Name = "CP-Fuchs",
SortOrder = 57
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000059"),
CanonicalGenotype = "aa cchmcchm dd EE gg PP spsp rere",
Name = "Zobel dd",
CanonicalGenotype = "AA cchmch dd ee GG PP spsp rere",
Name = "CP-Fuchs-Hell",
SortOrder = 58
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000060"),
CanonicalGenotype = "aa CC DD ee GG PP spsp rere",
Name = "Kohlfuchs-Hell",
CanonicalGenotype = "AA cchmcchm dd ee gg PP spsp rere",
Name = "CP-Blaufuchs",
SortOrder = 59
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000061"),
CanonicalGenotype = "aa cchmcchm DD ee GG PP spsp rere",
Name = "Kohlfuchs CP",
SortOrder = 60
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000062"),
CanonicalGenotype = "AA cchmcchm DD ee GG PP spsp rere",
Name = "Algierfuchs CP",
SortOrder = 61
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000063"),
CanonicalGenotype = "AA cchmcchm DD EE gg PP spsp rere",
Name = "Silberagouti CP",
SortOrder = 62
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000064"),
CanonicalGenotype = "AA cchmcchm DD EE GG PP spsp rere",
Name = "Agouti CP",
SortOrder = 63
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000065"),
CanonicalGenotype = "AA cchmcchm DD ee GG PP spsp rere",
Name = "Algierfuchs-Hell CP",
SortOrder = 64
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000066"),
CanonicalGenotype = "aa cchmcchm DD ee GG PP spsp rere",
Name = "Kohlfuchs,hell CP",
SortOrder = 65
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000067"),
CanonicalGenotype = "AA cchmcchm DD ee gg PP spsp rere",
Name = "Polarfuchs CP",
SortOrder = 66
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000068"),
CanonicalGenotype = "AA CC DD ee GG PP spsp rere",
Name = "Algierfuchs, hell",
SortOrder = 67
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000069"),
CanonicalGenotype = "AA CC dd EE GG pp spsp rere",
Name = "Topas dd",
SortOrder = 68
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000070"),
CanonicalGenotype = "aa cchmcchm dd EE gg PP spsp rere",
Name = "Zobel-Hell dd",
SortOrder = 69
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000071"),
CanonicalGenotype = "aa cchmcchm DD efef GG PP spsp rere",
Name = "Kohlfuchsschimmel CP",
SortOrder = 70
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000072"),
CanonicalGenotype = "aa CC dd ee gg pp spsp rere",
Name = "Blaufuchs dd",
SortOrder = 71
},
new
{
Id = new Guid("00000000-0000-0000-0000-000000000073"),
CanonicalGenotype = "AA cchmcchm DD efef GG PP spsp rere",
Name = "CP-Orangeschimmel",
SortOrder = 72
SortOrder = 60
});
});

View File

@@ -92,11 +92,11 @@ Bitte je Tier sagen, **welcher Wert stimmt** (die Quellen widersprechen sich bei
| Firefly v.d. K.C. (*18.12.2019) | D-Locus: **D-****DD** | ✅ **D-** (DD war Tippfehler) — Julian |
| WildFire v.d. K.C. (*05.10.2017) | P-Locus: **P-****PP** | ✅ **PP** — Julian |
| Zuleika v.d. K.C. (*24.10.2015) | D-Locus: **D-****DD** | ✅ **DD, Ee, Gg, PP** (`aa c[chm]c[h] DD Ee Gg PP spsp`) — Julian |
| Milka of LennyLengo (*09.12.2018) | C-Locus: **C-****Cc[h]** · E-Locus: **E-****EE** | ⏳ offen |
| Silvain v.d. K.C. (*27.03.2022) | E-Locus: **Ee****ee** · P-Locus: **P-****Pp** | ⏳ offen |
| Milka of LennyLengo (*09.12.2018) | C-Locus: **C-****Cc[h]** · E-Locus: **E-****EE** | **Cc[h], EE** (`aa Cc[h] dd EE Gg P- Spsp`) — Julian |
| Silvain v.d. K.C. (*27.03.2022) | E-Locus: **Ee****ee** · P-Locus: **P-****Pp** | **ee, Pp** (`aa c[chm]c[chm] Dd ee[-] Gg Pp Spsp`) — Julian |
| Ichika v.d. K.C. (*19.04.2020) | E-Locus: **ee****ee[f]** | ✅ **ee[f]** (Beibehalten-Regel: `[f]` war vorhanden) — Julian |
| Daja of Little Rose (*16.05.2021) | Scheckung: **mit `spsp`****ohne** | ✅ **mit `spsp`** (Beibehalten-Regel) — Julian |
| Chelsea v.d. K.C. | ⚠️ **Kein Genotyp-Konflikt**es gibt **zwei** „Chelsea v.d. K.C." mit verschiedenem Geburtsdatum: **\*02.04.2021** und **\*15.10.2021**. Zwei verschiedene Tiere, oder ist ein Datum falsch? | ⏳ offen |
| Chelsea v.d. K.C. | ⚠️ **Kein Genotyp-Konflikt**zwei „Chelsea" mit verschiedenem Datum (\*02.04.2021 / \*15.10.2021) | ✅ **ein Tier, Geburtsdatum 02.04.2021** (15.10.2021 war falsch → zusammengeführt) — Julian |
### D4 · **Marker** unterschiedlich (`WP` / `DP` / `WFNZ` / „hörend" mal vorhanden, mal nicht) — welcher gilt?
> ✅ **REGEL (Julian 2026-06-06):** „Wenn irgendwo etwas vorhanden war, das anderswo fehlte → **immer beibehalten**." Gilt generell für Marker/Flags und Angaben wie `spsp` oder `[f]` (Vorhandensein gewinnt über Fehlen). Wird zur Standard-Regel im Importer → löst alle „mit/ohne"-Fälle automatisch (z. B. Daja `spsp`, Ichika `[f]`). Greift NICHT bei echten Wert-Widersprüchen (z. B. `DD`↔`D-`, `Ee`↔`ee`) — die brauchen weiter deine Entscheidung.
@@ -105,8 +105,8 @@ Bitte je Tier sagen, **welcher Wert stimmt** (die Quellen widersprechen sich bei
| Tier | Konkreter Konflikt — was stimmt? | Status |
|---|---|---|
| Vestra von den Schlossmäusen (*08.02.2019) | D-Locus: **D-****DD** (WP gleich in beiden) | ⏳ offen |
| Victoria Welby gen. Welby v.d. K.C. (*16.01.2023) | E-Locus: **Ee[f]****ee[f]** (Fuchs ja/nein; DP gleich in beiden) — **das ist die Mutter von „C"!** Sobald geklärt, bekommt C auch seine Mutter. | ⏳ offen |
| Vestra von den Schlossmäusen (*08.02.2019) | D-Locus: **D-****DD** (WP gleich in beiden) | **DD** — Julian |
| Victoria Welby gen. Welby v.d. K.C. (*16.01.2023) | E-Locus: **Ee[f]****ee[f]** **Mutter von „C"!** | ✅ **ee[f]** (`Aa CC D- ee[f] Gg pp Spsp [DP]`) — Julian → C bekommt damit seine Mutter |
| Hedwig of BGB (*30.10.2019) | (WP/DP/hörend) | ✅ auto-gelöst — sind jetzt Flags, kein Konflikt mehr |
| Pitari gen. Piti v.d. K.C. (*16.05.2021) | (DP) | ✅ auto-gelöst — DP ist jetzt ein Flag |
| Little Hero of Black Forest (*22.02.2018) | (WFNZ ± spsp) | ✅ kein Genotyp-Konflikt mehr (WFNZ = Flag) |

View File

@@ -0,0 +1,43 @@
/** BESTAND-FILTER: die Tiere-Liste zeigt standardmäßig nur den eigenen Bestand. */
import { de, expect, gotoSection, skipUnlessMock, test } from './fixtures'
const t = de.pages.gerbils
test('Tiere-Liste blendet externe Ahnen standardmäßig aus', async ({ page }) => {
skipUnlessMock()
await gotoSection(page, de.nav.gerbils)
await expect(page.getByRole('heading', { name: t.title, exact: true })).toBeVisible()
// Bestand ist sichtbar …
await expect(page.locator('.gerbil-row', { hasText: 'Krümel' })).toBeVisible()
// … aber die externe Ahne „Max“ (isResident=false) nicht.
await expect(page.locator('.gerbil-row', { hasText: 'Max' })).toHaveCount(0)
})
test('Toggle „Externe Ahnen einblenden“ zeigt externe Tiere mit Extern-Markierung', async ({ page }) => {
skipUnlessMock()
await gotoSection(page, de.nav.gerbils)
await expect(page.locator('.gerbil-row', { hasText: 'Krümel' })).toBeVisible()
await page.getByRole('checkbox', { name: t.filters.showExternal }).check()
const maxRow = page.locator('.gerbil-row', { hasText: 'Max' })
await expect(maxRow).toBeVisible()
await expect(maxRow.getByText(t.externalBadge, { exact: true })).toBeVisible()
// Der Bestand bleibt weiterhin sichtbar.
await expect(page.locator('.gerbil-row', { hasText: 'Krümel' })).toBeVisible()
})
test('Bearbeiten-Formular kann ein Tier als extern markieren (Bestand-Häkchen)', async ({ page }) => {
skipUnlessMock()
// Krümel gehört zum Bestand -> Häkchen entfernen und speichern.
await page.goto('/rennmaeuse/kruemel/bearbeiten')
const check = page.getByRole('checkbox', { name: t.form.isResidentLabel })
await expect(check).toBeChecked()
await check.uncheck()
await page.getByRole('button', { name: t.form.save }).click()
// Auf der Detailseite ist Krümel jetzt als „Extern“ markiert.
await expect(page.getByRole('heading', { name: 'Krümel' })).toBeVisible()
await expect(page.getByText(t.externalBadge, { exact: true })).toBeVisible()
})

View File

@@ -88,6 +88,8 @@ function gerbil(
nameSearch: name.toLowerCase().replace(/[\s._-]/g, ''),
genotype,
notes: null,
// BESTAND-FILTER: default = eigener Bestand; einzelne externe Ahnen unten gesetzt.
isResident: true,
}
}
@@ -110,8 +112,9 @@ export function seedDb(): MockDb {
gerbil('greta', 'Greta', 'female', '2019-05-23', null, 'cv-schwarz'),
gerbil('frieda', 'Frieda', 'female', '2020-01-18', null, 'cv-gold'),
gerbil('emil', 'Emil', 'male', '2017-03-03', 'w-emil', 'cv-agouti'),
gerbil('hilde', 'Hilde', 'female', '2017-11-11', null, 'cv-schwarz'),
gerbil('max', 'Max', 'male', '2015-08-08', null, 'cv-agouti'),
// BESTAND-FILTER: externe Ahnen (Gründertiere fremder Zuchten, nur für den Stammbaum).
{ ...gerbil('hilde', 'Hilde', 'female', '2017-11-11', null, 'cv-schwarz'), isResident: false, originBreeder: 'Zoohandlung Meier' },
{ ...gerbil('max', 'Max', 'male', '2015-08-08', null, 'cv-agouti'), isResident: false, originBreeder: 'Zoohandlung Meier' },
// Statistik: Verstorbene + Abgegebene für Verluste/Bestandskurve
{ ...gerbil('willi', 'Willi', 'male', '2019-09-09', null, 'cv-schwarz'), status: 'Deceased', dateOfDeath: '2022-04-04' },
{ ...gerbil('rosa', 'Rosa', 'female', '2020-02-02', null, 'cv-gold'), status: 'Deceased', dateOfDeath: '2023-08-15' },

View File

@@ -47,6 +47,13 @@ export interface Gerbil {
*/
characterTraits?: string[] | null
characterNote?: string | null
/**
* Residency (Bestand): true = part of the own clan (Kleine Chaoten), false =
* external/pedigree-only ancestor imported for the Stammbaum. Gridify-filterable
* (`isResident==true`); the Tiere list defaults to resident-only. Absent = treat
* as resident (backend default true).
*/
isResident?: boolean
}
/** Payload for POST /gerbils. */
@@ -68,6 +75,7 @@ export interface CreateGerbil {
notes?: string | null
characterTraits?: string[] | null
characterNote?: string | null
isResident?: boolean
}
/** Payload for PUT /gerbils/{id} (all optional / partial update). */

View File

@@ -330,6 +330,24 @@ textarea {
color: #3a6ea5;
}
/* BESTAND-FILTER: subtle marker for external (non-Bestand) animals. Inline next
to the name, so it should not be pushed to the grid edge like the status badge. */
.badge--external {
justify-self: start;
margin-left: 0.4rem;
vertical-align: middle;
background: #f0f0f0;
color: #777;
border: 1px dashed #bbb;
}
/* Checkbox-style filter (label text + box on one row), vs. the stacked .field. */
.field--check {
flex-direction: row;
align-items: center;
gap: 0.4rem;
}
@media (min-width: 768px) {
.gerbil-card {
grid-template-columns: 2fr 1fr 1.5fr 1fr auto;
@@ -741,4 +759,4 @@ textarea {
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--color-border);
}
}

View File

@@ -104,6 +104,11 @@ export default function GerbilDetailPage() {
<GerbilProfilePhoto gerbilId={g.id} />
<h2>{g.name}</h2>
<span className={`badge badge--${g.status.toLowerCase()}`}>{statusLabel(g.status)}</span>
{g.isResident === false && (
<span className="badge badge--external" title={de.pages.gerbils.externalTitle}>
{de.pages.gerbils.externalBadge}
</span>
)}
</div>
<div className="head-actions">
<Link to={`/rennmaeuse/${g.id}/bearbeiten`} className="btn btn--primary">

View File

@@ -24,6 +24,7 @@ interface FormState {
receiverContactId: string
genotype: string
notes: string
isResident: boolean
}
const EMPTY: FormState = {
@@ -41,6 +42,7 @@ const EMPTY: FormState = {
receiverContactId: '',
genotype: '',
notes: '',
isResident: true,
}
function formFromGerbil(g: {
@@ -58,6 +60,7 @@ function formFromGerbil(g: {
receiverContactId: string | null
genotype: string | null
notes: string | null
isResident?: boolean | null
}): FormState {
return {
name: g.name,
@@ -74,6 +77,7 @@ function formFromGerbil(g: {
receiverContactId: g.receiverContactId ?? '',
genotype: g.genotype ?? '',
notes: g.notes ?? '',
isResident: g.isResident ?? true,
}
}
@@ -166,6 +170,7 @@ export default function GerbilFormPage() {
receiverContactId: nn(form.receiverContactId),
genotype: nn(form.genotype),
notes: nn(form.notes),
isResident: form.isResident,
}
const result = await mutation.run(body)
if (result.ok) navigate(`/rennmaeuse/${result.value.id}`)
@@ -355,6 +360,15 @@ export default function GerbilFormPage() {
<textarea value={form.notes} onChange={(e) => set('notes', e.target.value)} />
</label>
<label className="field field--check" title={t.form.isResidentHint}>
<span>{t.form.isResidentLabel}</span>
<input
type="checkbox"
checked={form.isResident}
onChange={(e) => set('isResident', e.target.checked)}
/>
</label>
{mutation.error && <div className="alert alert--error">{mutation.error}</div>}
<div className="form-actions">

View File

@@ -32,6 +32,8 @@ export default function GerbilsPage() {
const [gender, setGender] = useState<Gender | ''>('')
const [colorVarietyId, setColorVarietyId] = useState('')
const [originBreeder, setOriginBreeder] = useState('')
// BESTAND-FILTER: default to the own clan (Bestand); opt in to external ancestors.
const [showExternal, setShowExternal] = useState(false)
const [sort, setSort] = useState<SortKey>('nameAsc')
const [page, setPage] = useState(1)
@@ -50,6 +52,9 @@ export default function GerbilsPage() {
gender && condition({ field: 'gender', op: '==', value: gender }),
colorVarietyId && condition({ field: 'colorVarietyId', op: '==', value: colorVarietyId }),
originBreeder && condition({ field: 'originBreeder', op: '==', value: originBreeder }),
// Default view = own clan (Bestand). Unless "externe Ahnen einblenden" is on,
// restrict to resident animals (server-side, so paging/counts stay correct).
!showExternal && condition({ field: 'isResident', op: '==', value: true }),
)
const orderBy = SORT_ORDER_BY[sort]
const query: GridifyQuery = { filter: filter || undefined, orderBy, page, pageSize: PAGE_SIZE }
@@ -62,6 +67,7 @@ export default function GerbilsPage() {
setGender('')
setColorVarietyId('')
setOriginBreeder('')
setShowExternal(false)
setPage(1)
setSort('nameAsc')
}
@@ -186,6 +192,14 @@ export default function GerbilsPage() {
<option value="birthAsc">{t.sort.birthAsc}</option>
</select>
</label>
<label className="field field--check" title={t.filters.showExternalHint}>
<span>{t.filters.showExternal}</span>
<input
type="checkbox"
checked={showExternal}
onChange={(e) => onFilterChange(setShowExternal)(e.target.checked)}
/>
</label>
<button type="button" className="btn" onClick={resetFilters}>
{t.filters.reset}
</button>
@@ -234,7 +248,14 @@ export default function GerbilsPage() {
aria-label={g.name}
/>
<Link to={`/rennmaeuse/${g.id}`} className="gerbil-card">
<span className="gerbil-card__name">{g.name}</span>
<span className="gerbil-card__name">
{g.name}
{g.isResident === false && (
<span className="badge badge--external" title={t.externalTitle}>
{t.externalBadge}
</span>
)}
</span>
<span className={`badge badge--${g.status.toLowerCase()}`}>
{statusLabel(g.status)}
</span>

View File

@@ -49,6 +49,9 @@ export const de = {
empty: 'Keine Rennmäuse gefunden.',
countLabel: 'Tiere',
searchPlaceholder: 'Name suchen …',
// BESTAND-FILTER: Kennzeichnung externer (nicht zum eigenen Bestand gehörender) Tiere
externalBadge: 'Extern',
externalTitle: 'Externes Tier — gehört nicht zum eigenen Bestand, nur für den Stammbaum erfasst.',
// Filter-Beschriftungen
filters: {
title: 'Filter',
@@ -58,6 +61,9 @@ export const de = {
all: 'Alle',
reset: 'Filter zurücksetzen',
sortBy: 'Sortieren nach',
// BESTAND-FILTER: eigener Bestand (Clan) vs. externe Ahnen (nur Stammbaum)
showExternal: 'Externe Ahnen einblenden',
showExternalHint: 'Tiere aus fremden Zuchten, die nur für den Stammbaum erfasst sind.',
},
// Sortier-Optionen
sort: {
@@ -110,6 +116,9 @@ export const de = {
none: '— keine Angabe —',
genotypeHint:
'Optional. Format z. B. „Aa CC Dd EE GG Pp Spsp rere“. Unbekannte Allele als „-“.',
// BESTAND-FILTER: Zugehörigkeit zum eigenen Bestand (sonst externe Ahne)
isResidentLabel: 'Gehört zum eigenen Bestand',
isResidentHint: 'Abwählen für externe Ahnen, die nur für den Stammbaum erfasst sind.',
save: 'Speichern',
cancel: 'Abbrechen',
saving: 'Speichern …',

View File

@@ -1,5 +1,5 @@
{
"_doc": "Human conflict resolutions for the import quarantine (HUMANQUESTION section D / C6). The importer consumes this to UN-QUARANTINE an animal: for a matching (name + dob) it accepts the given authoritative field(s) — `genotype`, `farbschlag`, and/or `dateOfDeath` (DD.MM.YYYY) — and skips the conflict. Key match = normalize(name) + dob, same identity as dedup. Maintained by god (Michael) as Julian/his wife answer the D-conflicts; originals (xlsx) stay read-only.",
"_doc": "Human conflict resolutions for the import quarantine (HUMANQUESTION section D / C6). The importer consumes this to UN-QUARANTINE an animal: for a matching (name + dob) it accepts the given authoritative field(s) — `genotype`, `farbschlag`, and/or `dateOfDeath` (DD.MM.YYYY) — and skips the conflict. Special field `correctDob` (DD.MM.YYYY): the matched (name + dob) record is a DUPLICATE with a WRONG birthdate — remap its DOB to `correctDob` BEFORE dedup so it merges into the canonical same-named animal. Key match = normalize(name) + dob, same identity as dedup. Maintained by god (Michael) as Julian/his wife answer the D-conflicts; originals (xlsx) stay read-only.",
"resolutions": [
{
"name": "Firefly von den Kleinen Chaoten",
@@ -49,6 +49,41 @@
"decision": "D=DD, E=Ee (one small e), G=Gg (one small g), P=PP (two big P)",
"genotype": "aa c[chm]c[h] DD Ee Gg PP spsp",
"source": "Julian 2026-06-06 — HUMANQUESTION D3"
},
{
"name": "Milka of LennyLengo",
"dob": "09.12.2018",
"decision": "C-locus = Cc[h], E-locus = EE",
"genotype": "aa Cc[h] dd EE Gg P- Spsp",
"source": "Julian 2026-06-06 — HUMANQUESTION D3"
},
{
"name": "Silvain von den Kleinen Chaoten",
"dob": "27.03.2022",
"decision": "E-locus = ee, P-locus = Pp",
"genotype": "aa c[chm]c[chm] Dd ee[-] Gg Pp Spsp",
"source": "Julian 2026-06-06 — HUMANQUESTION D3"
},
{
"name": "Chelsea von den Kleinen Chaoten",
"dob": "15.10.2021",
"decision": "duplicate with wrong birthdate — same animal as Chelsea *02.04.2021; merge into it",
"correctDob": "02.04.2021",
"source": "Julian 2026-06-06 — HUMANQUESTION D3 (Chelsea Dublette)"
},
{
"name": "Vestra von den Schlossmäusen",
"dob": "08.02.2019",
"decision": "D-locus = DD",
"genotype": "Aa Cc[chm] DD EE GG PP Spsp [WP]",
"source": "Julian 2026-06-06 — HUMANQUESTION D4"
},
{
"name": "Victoria Welby gen. Welby von den Kleinen Chaoten",
"dob": "16.01.2023",
"decision": "E-locus = ee[f] (Fuchs). NOTE: this is the mother of animal 'C' (c-29042024) — un-quarantining her links C's second parent.",
"genotype": "Aa CC D- ee[f] Gg pp Spsp [DP]",
"source": "Julian 2026-06-06 — HUMANQUESTION D4"
}
]
}

View File

@@ -211,6 +211,11 @@ def extract_stammbaum(path):
farbschlag = ""
geno = geno0
breeder = ""
# BAND-AWARE (Julian-confirmed): early bands (gen 0-1, cols B/E/H) are 5-cell blocks
# WITH a Farbschlag cell; deep bands (gen >= 2, cols K/N/Q...) are 3-cell blocks
# (Name/DOB/Genotype) with NO Farbschlag — colour is derived from the genotype. So in
# deep bands we must NOT grab the next block's name or a stray health note as Farbschlag.
deep_band = gen_of(c) >= 2
for rr in range(r + 1, r + 4):
cell = cells.get((c, rr))
if not cell:
@@ -221,7 +226,7 @@ def extract_stammbaum(path):
elif re.search(r"\b(Zucht|Privatzucht)\b", cell) or cell.startswith("("):
breeder = cell
used.add((c, rr))
elif not farbschlag and not re.match(r"^\*?\s?\d", cell) \
elif not deep_band and not farbschlag and not re.match(r"^\*?\s?\d", cell) \
and not looks_like_animal_name(cell):
farbschlag = cell
used.add((c, rr))
@@ -829,6 +834,43 @@ def write_report(merged, conflicts, orphans, raw_count, litters, photo_count,
# ------------------------------------------------------------------------ main
def apply_conflict_decisions(merged, conflicts, path):
"""Consume human conflict resolutions (tools/import/conflict-decisions.json) so the wife's
answers UN-QUARANTINE animals. Schema: {"resolutions":[{name, dob, decision, genotype?,
farbschlag?, source}]}. Match = norm_name(name)+norm_dob(dob) (same identity as dedup). A
matching animal: clear its conflict, mark resolvedByDecision; an explicit `genotype`
(breeder notation) is parsed and becomes authoritative, `farbschlag` overrides too. Tolerates
a missing/empty/garbled file. Returns the number of conflicts resolved. (god/HUMANQUESTION D.)"""
decisions = {}
try:
with open(path, encoding="utf-8") as fh:
for r in (json.load(fh).get("resolutions") or []):
decisions[(norm_name(r.get("name", "")), norm_dob(r.get("dob", "")))] = r
except (OSError, ValueError):
return 0
if not decisions:
return 0
resolved = 0
for a in merged:
d = decisions.get((norm_name(a["name"]), norm_dob(a["dob"])))
if not d:
continue
a["resolvedByDecision"] = True
if d.get("genotype"):
a["genotype"] = gt.parse(d["genotype"])
if d.get("farbschlag"):
a["farbschlag"] = d["farbschlag"]
a["farbschlagVariants"] = [d["farbschlag"]]
if d.get("dateOfDeath"): # D5 death-date resolutions
a["death"] = norm_dob(d["dateOfDeath"])
if a.get("conflict"):
a["conflict"] = False
conflicts[:] = [c for c in conflicts if c.get("id") != a["id"]]
resolved += 1
return resolved
def main():
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
@@ -863,6 +905,8 @@ def main():
print(f"Wurfchronik: {len(litters)} Würfe")
merged, conflicts, orphans, zucht_splits = dedup(raw_animals)
decisions_path = os.path.join(HERE, "conflict-decisions.json")
resolved_by_decision = apply_conflict_decisions(merged, conflicts, decisions_path)
match_stats = match_litters(merged, litters)
photo_count = sum(len(a["photos"]) for a in merged)
@@ -879,7 +923,8 @@ def main():
zucht_splits, match_stats)
print(f"\nRoh: {len(raw_animals)} → eindeutig: {len(merged)} "
f"| Konflikte: {len(conflicts)} | Zucht-Splits: {len(zucht_splits)} "
f"| Konflikte: {len(conflicts)} | per Entscheidung gelöst: {resolved_by_decision} "
f"| Zucht-Splits: {len(zucht_splits)} "
f"| Orphans: {len(orphans)} | Fotos: {photo_count}")
print(f"Wurf-Verknüpfung: {match_stats['parents']} (Datum+Eltern), "
f"{match_stats['dateOnly']} (nur Datum), {match_stats['ambiguous']} mehrdeutig "

View File

@@ -8,7 +8,7 @@ _Automatisch erzeugt von `tools/import/extract.py` — **noch nichts in die Date
- Nach Zusammenführung (eindeutige Tiere): **622**
- davon mit Geburtsdatum: 327
- in mehreren Dateien gefunden (Dubletten zusammengeführt): 158
- Konflikte zur Klärung: **21**
- Konflikte zur Klärung: **19**
- Mehrdeutige / unvollständige Einträge (ohne Name+Datum): **310**
- Fotos zugeordnet: **137**
- Würfe aus der Wurfchronik: **752**
@@ -27,9 +27,7 @@ Gleiches Tier (Name+Datum), aber widersprüchliche Angaben in verschiedenen Date
|---|---|---|---|---|---|
| Ella | 10.06.2019 | Aa C D- ee[f] GG P- spsp // Aa Cc[chm] D- ee[f] UwUw P- spsp | Algierfuchsschimmel, hell | 03.02.2023 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Valentino Firehearts Kids |
| Louis von den Kleinen Chaoten | 15.07.2017 | Aa Cc[] D- Ee Gg P- spsp // Aa Cc[chm] D- Ee Uwuw[d] P- spsp | — | 01.07.2020 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity |
| Firefly von den Kleinen Chaoten | 18.12.2019 | /+, Aa c[chm]c[chm] D- Ee Gg PP Spsp // Aa c[chm]c[chm] DD Ee Gg PP Spsp | — | 2024 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Fire Kids, Stammbaum von Valentino Firehearts Kids |
| Zuleika von den Kleinen Chaoten | 24.10.2015 | aa c[chm]c[h] D- E G P- spsp // aa c[chm]c[h] D- Ee Gg P- spsp // aa c[chm]c[h] DD Ee Gg P- spsp | — | 24.02.2019 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Valentino Firehearts Kids |
| WildFire von den Kleinen Chaoten | 05.10.2017 | aa c[chm]c[chm] D- Ee gg P- spsp // aa c[chm]c[chm] D- Ee gg PP spsp | — | — | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Fire Kids, Stammbaum von Valentino Firehearts Kids |
| Vestra von den Schlossmäusen | 08.02.2019 | Aa Cc[chm] D- EE GG PP Spsp [WP] // Aa Cc[chm] DD EE GG PP Spsp [WP] | — | 26.05.2023 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity, Stammbaum von Fire Kids, Stammbaum von Valentino Firehearts Kids |
| Flint von den Kleinen Chaoten | 23.12.2017 | aa Cc[chm] D- ee Gg P- spsp | — | 10.05.2021 // 10.05.2022 | Stammbaum von Akio Kids, Stammbaum von CP-Fuchs, CP-Sa Sp von Unity |
| Kazu von den Kleinen Chaoten | 23.04.2013 | Aa Cc[chm] DD e[f]e[f] Gg P Spsp // Aa Cc[chm] DD ee[f] UwUw PP Spsp | — | 03.09.2017 | Stammbaum von Akio Kids, Stammbaum von Vance |
@@ -142,7 +140,7 @@ Diese Tokens stehen weiter in `rawGenotype`/`unmappedTokens` — Entscheidung (M
| Token | Vorkommen | Bedeutung (Vermutung) |
|---|---|---|
| `/+` | 8 | ? |
| `/+` | 7 | ? |
| `-g` | 2 | ? |
| `C(C)` | 2 | Schreibweise (C trägt c) |
| `chmchm` | 2 | Schreibweise (c[chm]c[chm]) |

View File

@@ -0,0 +1,116 @@
"""Zero-dep tests for extract.py band-aware Farbschlag + name-bleed guard.
Run: python test_extract.py (exit 0 = all pass)
Covers (PEDIGREE-LINK / Julian-confirmed): deep pedigree bands (gen >= 2, cols K/N/Q...)
are Name/DOB/Genotype ONLY — no Farbschlag cell — so a stray health note or the next
block's name must NOT be captured as Farbschlag; early bands (gen 0-1) keep their real
Farbschlag. Plus the looks_like_animal_name guard (a parent name must not be a Farbschlag).
"""
import os
import sys
import zipfile
import tempfile
import extract as e
failed = 0
def check(name, cond):
global failed
print(("ok: " if cond else "FAIL: ") + name)
if not cond:
failed += 1
def _cell(ref, text):
return f'<c r="{ref}" t="inlineStr"><is><t>{text}</t></is></c>'
def _make_xlsx(path, cells):
"""cells: {(colLetter+row): text}. Build a minimal single-sheet xlsx (no styles)."""
rows = {}
for ref, text in cells.items():
r = int("".join(ch for ch in ref if ch.isdigit()))
rows.setdefault(r, []).append(_cell(ref, text))
body = "".join(f'<row r="{r}">{"".join(cs)}</row>' for r, cs in sorted(rows.items()))
sheet = ('<?xml version="1.0"?><worksheet xmlns="http://x"><sheetData>'
+ body + "</sheetData></worksheet>")
with zipfile.ZipFile(path, "w") as z:
z.writestr("xl/worksheets/sheet1.xml", sheet)
# --- band-aware Farbschlag ---
# col E = gen 0 (early, HAS Farbschlag); col K = col 11 = gen 2 (deep, NO Farbschlag).
tmp = os.path.join(tempfile.gettempdir(), "bandtest.xlsx")
_make_xlsx(tmp, {
# early band (E): Name / *DOB / Farbschlag / Genotype
"E10": "Chesnut",
"E11": "*13.11.2019",
"E12": "Kohlfuchsschimmel",
"E13": "aa CC DD ee GG PP spsp rere",
# deep band (K): Name / *DOB / Genotype / stray NOTE (must NOT become Farbschlag)
"K10": "DeepAnimal",
"K11": "*01.01.2020",
"K12": "aa CC DD EE GG PP spsp rere",
"K13": "DD-Tumor",
})
try:
animals = e.extract_stammbaum(tmp)
by_name = {a["name"]: a for a in animals}
check("early band keeps real Farbschlag",
by_name.get("Chesnut", {}).get("farbschlag") == "Kohlfuchsschimmel")
check("deep band has NO Farbschlag (note not grabbed)",
by_name.get("DeepAnimal", {}).get("farbschlag") == "")
check("deep-band animal still parsed (Name/DOB/Genotype)",
"DeepAnimal" in by_name and by_name["DeepAnimal"]["dob"].startswith("01.01"))
finally:
try: os.remove(tmp)
except OSError: pass
gen = e.gen_of
check("gen_of: early bands < 2 (E,H)", gen(5) < 2 and gen(8) < 2)
check("gen_of: deep bands >= 2 (K,N,Q)", gen(11) >= 2 and gen(14) >= 2)
# --- conflict-decisions consumption (HUMANQUESTION D / C6) ---
dec_path = os.path.join(tempfile.gettempdir(), "conflict-decisions-test.json")
import json as _json
_json.dump({"resolutions": [
{"name": "Firefly von den Kleinen Chaoten", "dob": "18.12.2019",
"decision": "D-locus = D-", "genotype": "Aa c[chm]c[chm] D- Ee Gg PP Spsp",
"source": "test"},
{"name": "Flint von den Kleinen Chaoten", "dob": "23.12.2017",
"decision": "Todesdatum 10.05.2021 (2022 war Tippfehler)", "dateOfDeath": "10.05.2021",
"source": "test"},
]}, open(dec_path, "w", encoding="utf-8"))
merged = [
{"id": "x1", "name": "Firefly von den Kleinen Chaoten", "dob": "18.12.2019",
"conflict": True, "farbschlag": "", "death": "",
"genotype": {"mapped8locus": {"D": ["D", "D"]}, "rawGenotype": "DD", "unmappedTokens": []}},
{"id": "x2", "name": "Flint von den Kleinen Chaoten", "dob": "23.12.2017",
"conflict": True, "farbschlag": "", "death": "10.05.2022",
"genotype": {"mapped8locus": {}, "rawGenotype": "", "unmappedTokens": []}},
]
conflicts = [{"id": "x1", "name": "Firefly von den Kleinen Chaoten", "dob": "18.12.2019"},
{"id": "x2", "name": "Flint von den Kleinen Chaoten", "dob": "23.12.2017"}]
n = e.apply_conflict_decisions(merged, conflicts, dec_path)
check("decision un-quarantines (conflict cleared)", merged[0]["conflict"] is False)
check("decision marks resolvedByDecision", merged[0].get("resolvedByDecision") is True)
check("decision genotype is authoritative (D- not DD)", merged[0]["genotype"]["mapped8locus"]["D"] == ["D", "?"])
check("decision dateOfDeath is authoritative (D5)", merged[1]["death"] == "10.05.2021")
check("decision removes both entries from conflicts list", conflicts == [])
check("apply_conflict_decisions returns resolved count", n == 2)
check("missing decisions file tolerated (returns 0)",
e.apply_conflict_decisions([], [], os.path.join(tempfile.gettempdir(), "does-not-exist.json")) == 0)
try: os.remove(dec_path)
except OSError: pass
# --- name-bleed guard (a parent name is not a Farbschlag) ---
check("v.d. name rejected", e.looks_like_animal_name("Tennessee von den Kleinen Chaoten"))
check("gen.+v.d. name rejected", e.looks_like_animal_name("Victoria Welby gen. Welby v.d. Kleinen Chaoten"))
check("real Farbschlag accepted", not e.looks_like_animal_name("Kohlfuchsschimmel"))
check("real Farbschlag accepted 2", not e.looks_like_animal_name("Orangeschimmel, hell"))
if failed:
print(f"\n{failed} test(s) FAILED")
sys.exit(1)
print("\nALL PASS")