fix: separate historical source methodologies
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-15 07:57:59 +02:00
parent c7faea430f
commit 5b1156e989
4 changed files with 50 additions and 7 deletions
+3
View File
@@ -30,6 +30,9 @@
- Added deterministic estimate disclosure for population answers and instructed - Added deterministic estimate disclosure for population answers and instructed
the local model to copy governed values literally instead of inventing the local model to copy governed values literally instead of inventing
averages, rates or derived trends. averages, rates or derived trends.
- Clarified the source inventory so current GRB layers are shown as a different
methodology beside, rather than as part of, the comparable 2013-2025 land-use
time series.
## Sprint 201 Semantic area-selection metrics (2026-07-15) ## Sprint 201 Semantic area-selection metrics (2026-07-15)
@@ -328,3 +328,5 @@ def test_frontend_exposes_source_inventory_timeline_and_ai_window() -> None:
assert "nextAssistantMessageId" in assistant_hook assert "nextAssistantMessageId" in assistant_hook
assert "crypto.randomUUID" not in assistant_hook assert "crypto.randomUUID" not in assistant_hook
assert "Officiële bronnen die hierna kunnen worden ingeladen" in catalog assert "Officiële bronnen die hierna kunnen worden ingeladen" in catalog
assert "vergelijkbare meetmomenten" in catalog
assert "andere bronmethode" in catalog
+4
View File
@@ -8422,6 +8422,10 @@ Validation evidence:
limitation whenever an answer discusses estimated population data, while the limitation whenever an answer discusses estimated population data, while the
model prompt forbids independent averages, rates and derived trends. Focused model prompt forbids independent averages, rates and derived trends. Focused
tests and full readiness passed with 619 backend tests. tests and full readiness passed with 619 backend tests.
- Browser review identified that the source summary visually combined current
GRB geometry with historical 10 m land-use observations. The UI now reports
comparable observations per temporal-series key and labels singleton/current
datasets as a separate source method. Full readiness remained green.
Known limitations: Known limitations:
- Water volume remains unavailable until a governed depth/bathymetry source is - Water volume remains unavailable until a governed depth/bathymetry source is
@@ -66,12 +66,42 @@ function datasetTheme(dataset: DatasetCreateResponse): string | null {
return null return null
} }
function timelineSummary(
temporalCount: number,
temporalSeriesCount: number,
otherMethodCount: number,
firstYear: number | null,
lastYear: number | null,
): string {
if (temporalCount < 2 || !firstYear || !lastYear) {
return 'Alleen de huidige toestand is vergelijkbaar beschikbaar.'
}
const seriesNote = temporalSeriesCount > 1
? ` in ${temporalSeriesCount} afzonderlijke reeksen`
: ''
const methodNote = otherMethodCount > 0
? `plus ${otherMethodCount} andere bronmethode${otherMethodCount === 1 ? '' : 'n'}`
: null
return [
`${temporalCount} vergelijkbare meetmomenten${seriesNote}`,
`${firstYear}-${lastYear}`,
methodNote,
].filter(Boolean).join(' · ')
}
export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.Element { export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.Element {
const ready = datasets.filter((dataset) => dataset.status === 'ready') const ready = datasets.filter((dataset) => dataset.status === 'ready')
const themes = Object.keys(THEME_LABELS).map((theme) => { const themes = Object.keys(THEME_LABELS).map((theme) => {
const matches = ready.filter((dataset) => datasetTheme(dataset) === theme) const matches = ready.filter((dataset) => datasetTheme(dataset) === theme)
const temporal = matches.filter((dataset) => dataset.temporal_series_key && dataset.observed_at) const temporal = matches.filter((dataset) => dataset.temporal_series_key && dataset.observed_at)
const years = temporal.map((dataset) => new Date(dataset.observed_at as string).getUTCFullYear()) const seriesGroups = new Map<string, DatasetCreateResponse[]>()
temporal.forEach((dataset) => {
const seriesKey = dataset.temporal_series_key as string
seriesGroups.set(seriesKey, [...(seriesGroups.get(seriesKey) ?? []), dataset])
})
const comparableSeries = [...seriesGroups.values()].filter((series) => series.length >= 2)
const comparableObservations = comparableSeries.flat()
const years = comparableObservations.map((dataset) => new Date(dataset.observed_at as string).getUTCFullYear())
const latest = [...matches].sort( const latest = [...matches].sort(
(left, right) => new Date(right.observed_at ?? right.imported_at ?? 0).getTime() - new Date(left.observed_at ?? left.imported_at ?? 0).getTime(), (left, right) => new Date(right.observed_at ?? right.imported_at ?? 0).getTime() - new Date(left.observed_at ?? left.imported_at ?? 0).getTime(),
)[0] )[0]
@@ -79,7 +109,9 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
theme, theme,
label: THEME_LABELS[theme], label: THEME_LABELS[theme],
datasetCount: matches.length, datasetCount: matches.length,
temporalCount: temporal.length, temporalCount: comparableObservations.length,
temporalSeriesCount: comparableSeries.length,
otherMethodCount: matches.length - comparableObservations.length,
firstYear: years.length ? Math.min(...years) : null, firstYear: years.length ? Math.min(...years) : null,
lastYear: years.length ? Math.max(...years) : null, lastYear: years.length ? Math.max(...years) : null,
source: latest?.source_name ?? latest?.source ?? null, source: latest?.source_name ?? latest?.source ?? null,
@@ -105,11 +137,13 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
<span>{theme.source ? theme.source.replaceAll('_', ' ') : 'Nog niet ingeladen'}</span> <span>{theme.source ? theme.source.replaceAll('_', ' ') : 'Nog niet ingeladen'}</span>
</div> </div>
<b>{theme.datasetCount > 0 ? 'Beschikbaar' : 'Ontbreekt'}</b> <b>{theme.datasetCount > 0 ? 'Beschikbaar' : 'Ontbreekt'}</b>
<p> <p>{timelineSummary(
{theme.temporalCount >= 2 && theme.firstYear && theme.lastYear theme.temporalCount,
? `${theme.temporalCount} officiële meetmomenten · ${theme.firstYear}-${theme.lastYear}` theme.temporalSeriesCount,
: 'Alleen de huidige toestand is vergelijkbaar beschikbaar.'} theme.otherMethodCount,
</p> theme.firstYear,
theme.lastYear,
)}</p>
</article> </article>
))} ))}
</div> </div>