Add governed source freshness audit
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-16 15:05:42 +02:00
parent 2723043e3d
commit 1e0e7b6499
22 changed files with 1148 additions and 0 deletions
@@ -0,0 +1,96 @@
import type { SourceFreshnessItem, SourceFreshnessReport, SourceFreshnessStatus } from '../../types'
interface SourceFreshnessPanelProps {
report: SourceFreshnessReport | null
loading: boolean
error: string | null
onRefresh: () => void
}
function statusLabel(status: SourceFreshnessStatus): string {
if (status === 'current') return 'actueel'
if (status === 'due') return 'controle nodig'
if (status === 'review_required') return 'nakijken'
return 'lokaal'
}
function formatDate(value?: string | null): string {
if (!value) return 'geen datum'
return new Intl.DateTimeFormat('nl-BE', { dateStyle: 'medium' }).format(new Date(value))
}
function integrityIssueCount(item: SourceFreshnessItem): number {
return Object.values(item.integrity).reduce((total, value) => total + value, 0)
}
function SourceRow({ item }: { item: SourceFreshnessItem }): JSX.Element {
const issueCount = integrityIssueCount(item)
return (
<div className={`source-freshness-row source-freshness-row-${item.status}`}>
<div className="source-freshness-main">
<div>
<strong>{item.display_name}</strong>
<span>{item.dataset_count} datasets · {item.version_count} versies</span>
</div>
<span className="source-freshness-status">{statusLabel(item.status)}</span>
</div>
<p>{item.reason}</p>
<div className="source-freshness-meta">
<span>Laatste import: {formatDate(item.latest_imported_at)}</span>
{item.latest_source_version ? <span>Editie: {item.latest_source_version}</span> : null}
{item.historical_series ? <span>Historische reeks</span> : null}
{issueCount ? <span>{issueCount} integriteitsafwijking{issueCount === 1 ? '' : 'en'}</span> : null}
</div>
</div>
)
}
export function SourceFreshnessPanel({ report, loading, error, onRefresh }: SourceFreshnessPanelProps): JSX.Element {
const attentionItems = report?.items.filter((item) => item.status === 'due' || item.status === 'review_required') ?? []
const summary = report?.summary
return (
<section className="source-freshness-panel" aria-label="Bronactualiteit en versie-integriteit">
<div className="source-freshness-header">
<div>
<p className="eyebrow">Bronbeheer</p>
<h2>Actualiteit en versiecontrole</h2>
<p>Controleert lokale publicaties, versies en bestanden zonder externe bronnen automatisch te wijzigen.</p>
</div>
<button className="secondary-action" type="button" onClick={onRefresh} disabled={loading}>
{loading ? 'Controleren...' : 'Opnieuw controleren'}
</button>
</div>
{error ? <p className="inline-error">{error}</p> : null}
{!report && !error ? <p className="empty-state">{loading ? 'Bronstatus wordt gecontroleerd...' : 'Geen projectbronstatus beschikbaar.'}</p> : null}
{report && summary ? (
<>
<div className="source-freshness-summary" aria-label="Samenvatting broncontrole">
<span><strong>{summary.current_count}</strong> actueel</span>
<span><strong>{summary.due_count}</strong> controle nodig</span>
<span><strong>{summary.review_required_count}</strong> nakijken</span>
<span><strong>{summary.integrity_issue_count}</strong> integriteitsfouten</span>
</div>
{attentionItems.length ? (
<div className="source-freshness-attention" aria-label="Bronnen die aandacht vragen">
{attentionItems.map((item) => <SourceRow item={item} key={item.source_name} />)}
</div>
) : (
<p className="source-freshness-ok">Alle externe bronpublicaties hebben geldige lokale versie-evidentie.</p>
)}
<details className="source-freshness-details">
<summary>
<span>Alle {summary.source_count} bronnen bekijken</span>
<strong>gecontroleerd {formatDate(report.generated_at)}</strong>
</summary>
<div className="source-freshness-list">
{report.items.map((item) => <SourceRow item={item} key={item.source_name} />)}
</div>
<p className="source-freshness-limitation">{report.limitations.join(' ')}</p>
</details>
</>
) : null}
</section>
)
}