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:
2026-06-22 16:34:33 +02:00
parent 2befe53df9
commit aa473b764e
8 changed files with 423 additions and 15 deletions

View File

@@ -12,6 +12,11 @@ import { de } from '../strings/de'
import type { EntityProvenance } from '../api/types'
import './provenanceDialog.css'
/** Leading marker the Python merge prefixes onto discard ("verworfen") history
* lines (data thrown away with reason + replacement). Kept in sync with
* DISCARD_MARK in tools/import/merge_and_resolve.py. */
const DISCARD_PREFIX = '⚠ '
interface ProvenanceDialogProps {
open: boolean
onClose: () => void
@@ -92,12 +97,27 @@ export default function ProvenanceDialog({ open, onClose, provenance, entityLabe
{p.history.length > 0 && (
<section className="provenance__section">
<div className="provenance__section-title">{t.historyTitle}</div>
{p.history.some((s) => s.startsWith(DISCARD_PREFIX)) && (
<p className="provenance__discard-legend">{t.discardLegend}</p>
)}
<ol className="provenance__history">
{p.history.map((step, i) => (
<li key={`${i}-${step}`} className="provenance__history-step">
{step}
</li>
))}
{p.history.map((step, i) => {
// Discard lines (Python prefixes them with „⚠ “) are styled
// distinctly so a thrown-away value stands out from accepted facts.
const isDiscard = step.startsWith(DISCARD_PREFIX)
return (
<li
key={`${i}-${step}`}
className={
isDiscard
? 'provenance__history-step provenance__history-step--discard'
: 'provenance__history-step'
}
>
{step}
</li>
)
})}
</ol>
</section>
)}

View File

@@ -163,6 +163,19 @@
word-break: break-word;
}
/* Verworfene Werte (Datenherkunft): hervorgehoben, damit klar wird, dass hier
Daten aus einer Quelle verworfen wurden — mit Grund und Ersatz. */
.provenance__history-step--discard {
color: var(--color-danger, #9a3412);
font-style: italic;
}
.provenance__discard-legend {
margin: 0 0 0.5rem;
font-size: 0.78rem;
color: var(--color-text-muted, #888);
}
.provenance__actions {
display: flex;
justify-content: flex-end;

View File

@@ -896,6 +896,9 @@ export const de = {
},
/** Überschrift des chronologischen, dateibezogenen Verlaufs (Primärinhalt). */
historyTitle: 'Verlauf',
/** Legende für die mit „⚠“ markierten Verlaufszeilen, in denen Daten aus
* einer Quelle verworfen wurden (mit Grund und verwendetem Ersatz). */
discardLegend: '⚠ markiert verworfene Quelldaten (mit Grund und verwendetem Ersatz).',
/** Überschriften / Feldbeschriftungen. */
sourceFilesTitle: 'Quelldateien',
sourceFilesCount: (n: number) => (n === 1 ? 'aus 1 Quelle' : `aus ${n} Quellen`),