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>
This commit is contained in:
2026-06-06 11:55:45 +02:00
parent 4aca1d528b
commit df13136955
6 changed files with 101 additions and 7 deletions

View File

@@ -71,6 +71,29 @@ 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"},
]}, open(dec_path, "w", encoding="utf-8"))
merged = [{"id": "x1", "name": "Firefly von den Kleinen Chaoten", "dob": "18.12.2019",
"conflict": True, "farbschlag": "",
"genotype": {"mapped8locus": {"D": ["D", "D"]}, "rawGenotype": "DD", "unmappedTokens": []}}]
conflicts = [{"id": "x1", "name": "Firefly von den Kleinen Chaoten", "dob": "18.12.2019"}]
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 removes entry from conflicts list", conflicts == [])
check("apply_conflict_decisions returns resolved count", n == 1)
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"))