Enable governed GRB evolution
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 19:30:46 +02:00
parent 1d515e4901
commit a8b18942f5
12 changed files with 448 additions and 59 deletions
+74 -47
View File
@@ -322,10 +322,8 @@ function temporalSeriesLabel(items: DatasetCreateResponse[]): string {
}
const first = items[0]
const source = first ? getDatasetDisplayName(first) : 'Tijdreeks'
const firstYear = first?.observed_at ? new Date(first.observed_at).getUTCFullYear() : null
const last = items[items.length - 1]
const lastYear = last?.observed_at ? new Date(last.observed_at).getUTCFullYear() : null
return firstYear && lastYear ? `${source} (${firstYear}-${lastYear})` : source
const range = temporalRangeLabel(items)
return range ? `${source} (${range})` : source
}
function listThemeTemporalSeries(datasets: DatasetCreateResponse[], theme: DataTheme): TemporalSeriesGroup[] {
@@ -362,6 +360,27 @@ function formatObservationDate(value: string | null | undefined): string {
return new Intl.DateTimeFormat('nl-BE', { year: 'numeric', month: 'short', day: 'numeric' }).format(new Date(value))
}
function temporalRangeLabel(items: DatasetCreateResponse[]): string | null {
const firstValue = items[0]?.observed_at
const lastValue = items[items.length - 1]?.observed_at
if (!firstValue || !lastValue) {
return null
}
const first = new Date(firstValue)
const last = new Date(lastValue)
if (first.getTime() === last.getTime()) {
return formatObservationDate(firstValue)
}
if (first.getUTCFullYear() !== last.getUTCFullYear()) {
return `${first.getUTCFullYear()}-${last.getUTCFullYear()}`
}
if (first.getUTCMonth() === last.getUTCMonth()) {
const monthAndYear = new Intl.DateTimeFormat('nl-BE', { year: 'numeric', month: 'short' }).format(last)
return `${first.getUTCDate()}-${last.getUTCDate()} ${monthAndYear}`
}
return `${formatObservationDate(firstValue)} - ${formatObservationDate(lastValue)}`
}
function formatDatasetObservation(dataset: DatasetCreateResponse): string {
if (dataset.source_name === 'vmm_flood_hazard') {
return `scenario ${String(dataset.source_metadata?.['climate_context'] ?? '')} · ${String(dataset.source_metadata?.['probability_class'] ?? '')}`
@@ -983,6 +1002,10 @@ export function MapWorkspace({
const activeTemporalSeriesGroup = activeTemporalSeriesGroups.find((group) => group.key === selectedTemporalSeriesKey)
?? activeTemporalSeriesGroups[0]
const activeTemporalSeries = activeTemporalSeriesGroup?.items ?? EMPTY_TEMPORAL_SERIES
const activeSeriesIsDailyGrb = activeTemporalSeries.length >= 2
&& activeTemporalSeries.every((dataset) => dataset.source_name === 'grb')
&& new Date(activeTemporalSeries[activeTemporalSeries.length - 1].observed_at ?? 0).getTime()
- new Date(activeTemporalSeries[0].observed_at ?? 0).getTime() <= 7 * 24 * 60 * 60 * 1000
useEffect(() => {
onSetContextSourceLabel(
@@ -1416,10 +1439,7 @@ export function MapWorkspace({
const evolutionAvailable = temporalGroups.some((group) => group.items.length >= 2)
const available = Boolean(dataset) && (analysisMode === 'current' || evolutionAvailable)
const active = activeThemeId === theme.id
const firstObservation = temporalGroup?.items[0]?.observed_at
const lastObservation = temporalGroup?.items[temporalGroup.items.length - 1]?.observed_at
const firstYear = firstObservation ? new Date(firstObservation).getUTCFullYear() : null
const lastYear = lastObservation ? new Date(lastObservation).getUTCFullYear() : null
const temporalRange = temporalGroup ? temporalRangeLabel(temporalGroup.items) : null
return (
<button
className={active ? 'geo-theme-option geo-theme-option-active' : 'geo-theme-option'}
@@ -1435,7 +1455,7 @@ export function MapWorkspace({
<small>
{analysisMode === 'evolution'
? evolutionAvailable
? `${temporalGroup.items.length} meetmomenten${firstYear && lastYear ? ` · ${firstYear}-${lastYear}` : ''}`
? `${temporalGroup.items.length} meetmomenten${temporalRange ? ` · ${temporalRange}` : ''}`
: dataset
? 'Alleen huidige toestand'
: 'Bron nog niet ingeladen'
@@ -1500,48 +1520,55 @@ export function MapWorkspace({
) : null}
{analysisMode === 'evolution' ? (
<div className="geo-time-controls" aria-label="Meetmomenten vergelijken">
{activeTemporalSeriesGroups.length > 1 ? (
<label className="geo-series-control">
Reeks
<select
value={activeTemporalSeriesGroup?.key ?? ''}
onChange={(event) => {
setSelectedTemporalSeriesKey(event.target.value)
clearTemporalComparison()
}}
>
{activeTemporalSeriesGroups.map((group) => (
<option key={group.key} value={group.key}>{group.label}</option>
<>
<div className="geo-time-controls" aria-label="Meetmomenten vergelijken">
{activeTemporalSeriesGroups.length > 1 ? (
<label className="geo-series-control">
Reeks
<select
value={activeTemporalSeriesGroup?.key ?? ''}
onChange={(event) => {
setSelectedTemporalSeriesKey(event.target.value)
clearTemporalComparison()
}}
>
{activeTemporalSeriesGroups.map((group) => (
<option key={group.key} value={group.key}>{group.label}</option>
))}
</select>
</label>
) : null}
<label>
Van
<select value={earlierDatasetId} onChange={(event) => { setEarlierDatasetId(event.target.value); clearTemporalComparison() }} disabled={activeTemporalSeries.length < 2}>
{activeTemporalSeries.map((dataset) => (
<option key={dataset.id} value={dataset.id}>{formatObservationDate(dataset.observed_at)}</option>
))}
</select>
</label>
<label>
Naar
<select value={laterDatasetId} onChange={(event) => { setLaterDatasetId(event.target.value); clearTemporalComparison() }} disabled={activeTemporalSeries.length < 2}>
{activeTemporalSeries.map((dataset) => (
<option key={dataset.id} value={dataset.id}>{formatObservationDate(dataset.observed_at)}</option>
))}
</select>
</label>
<button
className="primary-action"
type="button"
disabled={!mapSelectionBbox || !earlierDatasetId || !laterDatasetId || temporalComparisonLoading}
onClick={runTemporalComparison}
>
{temporalComparisonLoading ? 'Vergelijken…' : 'Vergelijk periode'}
</button>
</div>
{activeSeriesIsDailyGrb ? (
<p className="geo-data-notice">
Dagelijkse GRB-edities tonen wijzigingen in de officiële registratie. Ze bewijzen niet dat een fysieke verandering exact tussen deze twee kalenderdagen plaatsvond.
</p>
) : null}
<label>
Van
<select value={earlierDatasetId} onChange={(event) => { setEarlierDatasetId(event.target.value); clearTemporalComparison() }} disabled={activeTemporalSeries.length < 2}>
{activeTemporalSeries.map((dataset) => (
<option key={dataset.id} value={dataset.id}>{formatObservationDate(dataset.observed_at)}</option>
))}
</select>
</label>
<label>
Naar
<select value={laterDatasetId} onChange={(event) => { setLaterDatasetId(event.target.value); clearTemporalComparison() }} disabled={activeTemporalSeries.length < 2}>
{activeTemporalSeries.map((dataset) => (
<option key={dataset.id} value={dataset.id}>{formatObservationDate(dataset.observed_at)}</option>
))}
</select>
</label>
<button
className="primary-action"
type="button"
disabled={!mapSelectionBbox || !earlierDatasetId || !laterDatasetId || temporalComparisonLoading}
onClick={runTemporalComparison}
>
{temporalComparisonLoading ? 'Vergelijken…' : 'Vergelijk periode'}
</button>
</div>
</>
) : null}
<label className="geo-scope-select">