feat(provenance): verworfene Daten mit Grund + Ersatz im Verlauf zeigen
Der Herkunfts-Verlauf nennt jetzt auch, wenn ein Wert aus einer Quelle VERWORFEN wurde — mit Grund und was stattdessen verwendet wurde (⚠-Markierung), z. B.: - „⚠ Vater-Kandidat ‚Danielle' (*04.03.2020) verworfen — falsches Geschlecht für die Vaterrolle; ‚Hagrid Rubeus' verwendet." (Molly) - „⚠ Geschlecht weiblich aus ‚Wurfchronik.docx' verworfen — abweichend; männlich aus ‚Alberto Kids.xlsx' verwendet (Mehrheit)." (Solice) - „⚠ Vater ‚Jasper Jacob' (*2018) verworfen — unplausibel (7 Jahre älter); kein Ersatz." / DOB-Remap: „⚠ Geburtsdatum 22.08.2018 verworfen — per manueller Entscheidung korrigiert; 14.07.2019 verwendet." (Kazuya) Import: generischer _format_discard()-Helfer; Verwerfungen aus Mehrheitsentscheid, pick_parent_ref-Ablehnungen (explain_pick_rejections), Rollen-Normalisierung, Eltern-Alter-Sanity-Check und DOB-Remaps werden über _discarded/_prov_args an die Tiere gehängt; Gerbil-Provenance wird in einem finalen Pass gebaut (nachdem alle Verwerfungen feststehen). Frontend hebt ⚠-Zeilen ab (+ Legende). Tests grün (python merge/extract, vitest 129, playwright 8, dotnet 212). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -277,6 +277,74 @@ check("_de_date: ISO → DD.MM.YYYY", m._de_date("2022-03-27") == "27.03.2022")
|
||||
check("_de_date: passes through non-ISO", m._de_date("unbekannt") == "unbekannt")
|
||||
|
||||
|
||||
# ── Discard history: a discarded source value records reason + replacement ──
|
||||
# _format_discard: majority-vote conflict (losing value + file → winner + file).
|
||||
_d_mehr = m._format_discard({
|
||||
"label": "Geburtsdatum", "value": "14.06.2015", "file": "A.xlsx",
|
||||
"reason": "abweichend", "replacement": "14.06.2017", "repl_file": "B.xlsx",
|
||||
"replacement_note": "Mehrheit",
|
||||
})
|
||||
check("discard: starts with warning marker", _d_mehr.startswith(m.DISCARD_MARK))
|
||||
check("discard(majority): names losing value + its file",
|
||||
"Geburtsdatum 14.06.2015 aus „A.xlsx“ verworfen" in _d_mehr)
|
||||
check("discard(majority): states the reason", "— abweichend" in _d_mehr)
|
||||
check("discard(majority): names replacement + its file + note",
|
||||
"14.06.2017 aus „B.xlsx“ verwendet (Mehrheit)." in _d_mehr)
|
||||
|
||||
# _format_discard: a parent dropped with NO replacement.
|
||||
_d_noerepl = m._format_discard({
|
||||
"text": "Vater „Jayjay“ (*19.06.2013) verworfen — unplausibel (9 Jahre älter "
|
||||
"als das Kind); kein Ersatz",
|
||||
})
|
||||
check("discard(text): verbatim text gets the warning marker",
|
||||
_d_noerepl == m.DISCARD_MARK + "Vater „Jayjay“ (*19.06.2013) verworfen — "
|
||||
"unplausibel (9 Jahre älter als das Kind); kein Ersatz")
|
||||
|
||||
# explain_pick_rejections: a wrong-sex father candidate is explained (Molly case).
|
||||
_picks = m.explain_pick_rejections(
|
||||
molly_refs, "father", "13.09.2021", fr, gender_of=gof,
|
||||
)
|
||||
_pick_father = next((d for d in _picks if "Danielle" in (d.get("value") or "")), None)
|
||||
check("pick-reject: wrong-sex father candidate is recorded",
|
||||
_pick_father is not None)
|
||||
check("pick-reject: reason = wrong sex for the father role",
|
||||
_pick_father and "falsches Geschlecht für die Vaterrolle" in _pick_father["reason"])
|
||||
check("pick-reject: replacement names the chosen Hagrid",
|
||||
_pick_father and "Hagrid" in (_pick_father.get("replacement") or ""))
|
||||
|
||||
# explain_pick_rejections: an age-impossible candidate is explained.
|
||||
_age_refs = [pref("Old", "father", "2010-01-01"), pref("Dad", "father", "2021-01-01")]
|
||||
_chosen = m.pick_parent_ref(_age_refs, "father", "2022-03-27")
|
||||
_age_picks = m.explain_pick_rejections(_age_refs, "father", "2022-03-27", _chosen)
|
||||
check("pick-reject(age): age-impossible candidate recorded with reason",
|
||||
any("unplausibles Alter" in d["reason"] for d in _age_picks))
|
||||
|
||||
# _build_gerbil_history threads field_discards (after merge) and parent_discards
|
||||
# (after the parent line) into the timeline.
|
||||
g_disc = {
|
||||
"Name": "Solice", "DateOfBirth": "2022-03-27", "Gender": "male",
|
||||
"Genotype": None, "ColorVarietyId": None, "DateOfDeath": None,
|
||||
"ImportSource": "Stammbaum.xlsx", "_filename": "Stammbaum.xlsx",
|
||||
"parentRefs": [],
|
||||
"_discarded": [{"text": "Vater „Jayjay“ verworfen — unplausibel; kein Ersatz"}],
|
||||
}
|
||||
h_disc = m._build_gerbil_history(
|
||||
[g_disc], g_disc, {},
|
||||
field_discards=[{
|
||||
"label": "Geschlecht", "value": "weiblich", "file": "Wurfchronik.docx",
|
||||
"reason": "abweichend", "replacement": "männlich", "repl_file": "Stammbaum.xlsx",
|
||||
"replacement_note": "Mehrheit",
|
||||
}],
|
||||
parent_discards=g_disc["_discarded"],
|
||||
)
|
||||
check("history: field-discard line present (majority vote)",
|
||||
any("Geschlecht weiblich aus „Wurfchronik.docx“ verworfen" in s for s in h_disc))
|
||||
check("history: parent-discard line present (dropped parent)",
|
||||
any("Vater „Jayjay“ verworfen" in s for s in h_disc))
|
||||
check("history: discard lines carry the warning marker",
|
||||
all(s.startswith(m.DISCARD_MARK) for s in h_disc if "verworfen" in s))
|
||||
|
||||
|
||||
if check.failed:
|
||||
print(f"\n{check.failed} test(s) FAILED")
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user