feat(provenance): Datenherkunft als chronologischer, datei-attribuierter Verlauf
Die Herkunft liest sich jetzt wie eine History: pro Feld wird genannt, aus welcher Quelldatei der Wert kam, plus Merge-/Entscheidungs-/Wurfchronik-Schritte. Beispiel: „In ‚X.xlsx' gefunden." → „Geburtsdatum (…) aus ‚X.xlsx'." → „Auch in ‚Y.docx' gefunden → zusammengeführt." → „Eltern über Position erkannt (Quelle …)." Import: build_entity_provenance() um history[] erweitert; ein field_source- Tracking im Gerbil-Dedup hält fest, welcher Datensatz/welche Datei jedes Feld (DOB/Genotyp/Geschlecht/Farbschlag/Sterbedatum) lieferte — auch über Empty-Fill und Mehrheitsentscheid hinweg. Kontakte und Würfe erhalten analoge, datei- attribuierte Verläufe. Kein DB-Migration nötig (history liegt im Provenance-JSON). Frontend: ProvenanceDialog rendert den Verlauf als nummerierte Timeline. Tests: history wird erzeugt, nennt Dateien, überlebt Ingest (dotnet 212, vitest 129, playwright 17, python merge/extract grün). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -213,6 +213,70 @@ mr = m.pick_parent_ref(molly_refs, "mother", "13.09.2021", avoid_name=fr["name"]
|
||||
check("pick(gender): mother = Arya (female)", mr and mr["name"].startswith("Arya"))
|
||||
|
||||
|
||||
# ── Provenance history: chronological, file-attributed German log ──
|
||||
import json as _json
|
||||
|
||||
|
||||
def _hist(prov_json):
|
||||
return _json.loads(prov_json)["history"]
|
||||
|
||||
|
||||
# build_entity_provenance carries history through verbatim.
|
||||
_prov = _json.loads(
|
||||
m.build_entity_provenance(["A.xlsx"], 1, notes=["x"], history=["line one"])
|
||||
)
|
||||
check("build_entity_provenance includes history key", _prov.get("history") == ["line one"])
|
||||
check("build_entity_provenance defaults history to []",
|
||||
_json.loads(m.build_entity_provenance(["A.xlsx"], 1)).get("history") == [])
|
||||
|
||||
# Single-record gerbil history: names the file and the per-field facts.
|
||||
g_single = {
|
||||
"Name": "Picus", "DateOfBirth": "2022-03-27", "Gender": "male",
|
||||
"Genotype": "aa", "ColorVarietyId": None, "DateOfDeath": None,
|
||||
"ImportSource": "Stammbaum von Picus Son.xlsx",
|
||||
"_filename": "Stammbaum von Picus Son.xlsx", "parentRefs": [],
|
||||
}
|
||||
h = m._build_gerbil_history([g_single], g_single, {})
|
||||
check("history: first line names the source file",
|
||||
h[0] == "In „Stammbaum von Picus Son.xlsx“ gefunden.")
|
||||
check("history: dob line names file + formatted date",
|
||||
"Geburtsdatum (27.03.2022) aus „Stammbaum von Picus Son.xlsx“." in h)
|
||||
check("history: gender line is German + file-attributed",
|
||||
"Geschlecht (männlich) aus „Stammbaum von Picus Son.xlsx“." in h)
|
||||
check("history: genotype line file-attributed",
|
||||
"Genotyp aus „Stammbaum von Picus Son.xlsx“." in h)
|
||||
|
||||
# Merged gerbil: a field sourced from a DIFFERENT file is attributed to THAT file.
|
||||
g_best = {
|
||||
"Name": "Solice", "DateOfBirth": "2022-03-27", "Gender": "male",
|
||||
"Genotype": None, "ColorVarietyId": None, "DateOfDeath": None,
|
||||
"ImportSource": "Stammbaum von Picus Son.xlsx",
|
||||
"_filename": "Stammbaum von Picus Son.xlsx", "parentRefs": [],
|
||||
}
|
||||
g_other = {
|
||||
"Name": "Solice", "DateOfBirth": "2022-03-27", "Gender": "male",
|
||||
"Genotype": "aa", "ColorVarietyId": None, "DateOfDeath": None,
|
||||
"ImportSource": "Wurfchronik-Detail.docx",
|
||||
"_filename": "Wurfchronik-Detail.docx", "parentRefs": [],
|
||||
}
|
||||
# Genotype was filled from g_other → its line must name the docx file.
|
||||
g_best["Genotype"] = "aa"
|
||||
fs = {"DateOfBirth": g_best, "Gender": g_best, "Genotype": g_other}
|
||||
h2 = m._build_gerbil_history([g_best, g_other], g_best, fs)
|
||||
check("history(merge): genotype attributed to the file that supplied it",
|
||||
"Genotyp aus „Wurfchronik-Detail.docx“." in h2)
|
||||
check("history(merge): dob attributed to primary file",
|
||||
"Geburtsdatum (27.03.2022) aus „Stammbaum von Picus Son.xlsx“." in h2)
|
||||
check("history(merge): merge line names the absorbed file",
|
||||
"Auch in „Wurfchronik-Detail.docx“ gefunden → Datensätze zusammengeführt." in h2)
|
||||
check("history(merge): Wurfchronik line present",
|
||||
"Angaben aus der Wurfchronik übernommen." in h2)
|
||||
|
||||
# Date formatting helper.
|
||||
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")
|
||||
|
||||
|
||||
if check.failed:
|
||||
print(f"\n{check.failed} test(s) FAILED")
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user