Add official source edition probes
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 17:33:40 +02:00
parent fd05c46e2a
commit 595967f892
27 changed files with 1513 additions and 18 deletions
@@ -1,10 +1,22 @@
import type { SourceFreshnessItem, SourceFreshnessReport, SourceFreshnessStatus } from '../../types'
import type {
SourceCatalogComparisonStatus,
SourceCatalogProbeItem,
SourceCatalogProbeReport,
SourceCatalogProbeStatus,
SourceFreshnessItem,
SourceFreshnessReport,
SourceFreshnessStatus,
} from '../../types'
interface SourceFreshnessPanelProps {
report: SourceFreshnessReport | null
loading: boolean
error: string | null
onRefresh: () => void
catalogReport: SourceCatalogProbeReport | null
catalogLoading: boolean
catalogError: string | null
onProbeCatalogs: (force: boolean) => void
}
function statusLabel(status: SourceFreshnessStatus): string {
@@ -14,6 +26,21 @@ function statusLabel(status: SourceFreshnessStatus): string {
return 'lokaal'
}
function catalogStatusLabel(status: SourceCatalogProbeStatus): string {
if (status === 'available') return 'bereikbaar'
if (status === 'degraded') return 'beperkt'
if (status === 'unavailable') return 'niet bereikbaar'
return 'uitgeschakeld'
}
function comparisonLabel(status: SourceCatalogComparisonStatus): string {
if (status === 'same') return 'zelfde editie'
if (status === 'different') return 'verschil controleren'
if (status === 'no_local_data') return 'nog niet lokaal ingeladen'
if (status === 'not_comparable') return 'andere versienotatie'
return 'niet vergelijkbaar'
}
function formatDate(value?: string | null): string {
if (!value) return 'geen datum'
return new Intl.DateTimeFormat('nl-BE', { dateStyle: 'medium' }).format(new Date(value))
@@ -23,6 +50,27 @@ function integrityIssueCount(item: SourceFreshnessItem): number {
return Object.values(item.integrity).reduce((total, value) => total + value, 0)
}
function CatalogRow({ item }: { item: SourceCatalogProbeItem }): JSX.Element {
return (
<div className={`source-catalog-row source-catalog-row-${item.status}`}>
<div className="source-freshness-main">
<div>
<strong>{item.display_name}</strong>
<span>{catalogStatusLabel(item.status)} / {comparisonLabel(item.comparison_status)}</span>
</div>
<span className="source-freshness-status">{item.remote_version ?? 'geen editie'}</span>
</div>
<p>{item.message}</p>
<div className="source-freshness-meta">
<span>Lokaal: {item.local_source_version ?? 'niet aanwezig'}</span>
<span>Lagen: {item.matched_layers.length}/{item.expected_layers.length} bevestigd</span>
{item.remote_modified_at ? <span>Metadata: {formatDate(item.remote_modified_at)}</span> : null}
{item.cached ? <span>cache gebruikt</span> : null}
</div>
</div>
)
}
function SourceRow({ item }: { item: SourceFreshnessItem }): JSX.Element {
const issueCount = integrityIssueCount(item)
return (
@@ -30,7 +78,7 @@ function SourceRow({ item }: { item: SourceFreshnessItem }): JSX.Element {
<div className="source-freshness-main">
<div>
<strong>{item.display_name}</strong>
<span>{item.dataset_count} datasets · {item.version_count} versies</span>
<span>{item.dataset_count} datasets / {item.version_count} versies</span>
</div>
<span className="source-freshness-status">{statusLabel(item.status)}</span>
</div>
@@ -45,7 +93,16 @@ function SourceRow({ item }: { item: SourceFreshnessItem }): JSX.Element {
)
}
export function SourceFreshnessPanel({ report, loading, error, onRefresh }: SourceFreshnessPanelProps): JSX.Element {
export function SourceFreshnessPanel({
report,
loading,
error,
onRefresh,
catalogReport,
catalogLoading,
catalogError,
onProbeCatalogs,
}: SourceFreshnessPanelProps): JSX.Element {
const attentionItems = report?.items.filter((item) => item.status === 'due' || item.status === 'review_required') ?? []
const summary = report?.summary
@@ -55,11 +112,21 @@ export function SourceFreshnessPanel({ report, loading, error, onRefresh }: Sour
<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>
<p>Controleert lokale publicaties en kan op aanvraag de officiële GRB- en orthofoto-editie uitlezen.</p>
</div>
<div className="source-freshness-actions">
<button className="secondary-action" type="button" onClick={onRefresh} disabled={loading}>
{loading ? 'Controleren...' : 'Lokale status vernieuwen'}
</button>
<button
className="primary-action"
type="button"
onClick={() => onProbeCatalogs(Boolean(catalogReport))}
disabled={catalogLoading}
>
{catalogLoading ? 'Officiële bronnen lezen...' : catalogReport ? 'Opnieuw bij bron controleren' : 'Officiële edities controleren'}
</button>
</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}
@@ -89,6 +156,31 @@ export function SourceFreshnessPanel({ report, loading, error, onRefresh }: Sour
</div>
<p className="source-freshness-limitation">{report.limitations.join(' ')}</p>
</details>
<div className="source-catalog-section" aria-live="polite">
<div className="source-catalog-heading">
<div>
<strong>Officiële catalogusedities</strong>
<span>Alleen na jouw expliciete controle; er wordt niets ingeladen of vervangen.</span>
</div>
{catalogReport ? (
<span>{catalogReport.summary.available_count}/{catalogReport.summary.provider_count} bereikbaar</span>
) : null}
</div>
{catalogError ? <p className="inline-error">{catalogError}</p> : null}
{!catalogReport && !catalogError ? (
<p className="source-catalog-empty">Controleer GRB en orthofoto wanneer je een lokale bronversie met de officiële editie wilt vergelijken.</p>
) : null}
{catalogReport ? (
<>
<div className="source-catalog-list">
{catalogReport.items.map((item) => <CatalogRow item={item} key={item.source_name} />)}
</div>
<p className="source-freshness-limitation">
Gecontroleerd {formatDate(catalogReport.generated_at)}. Een verschil vraagt menselijke beoordeling en start nooit automatisch een import.
</p>
</>
) : null}
</div>
</>
) : null}
</section>