feat(scope): make Belgium and North Sea operational default
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-22 02:11:48 +02:00
parent 46884cbbc9
commit 0aff8e3b8c
61 changed files with 2499 additions and 194 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react'
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { PRIMARY_FOCUS_CENTER } from '../config/primaryFocus'
import { NATIONAL_MAP_CENTER, NATIONAL_MAP_ZOOM } from '../config/primaryFocus'
import { featureCollectionBounds } from '../lib/geojsonBounds'
import type { MapImageOverlay, MapViewportState, VectorSelectionBBox } from '../types'
@@ -221,8 +221,8 @@ function GeoMap({
const map = new maplibregl.Map({
container: containerRef.current,
style: defaultMapStyle(),
center: PRIMARY_FOCUS_CENTER,
zoom: 11,
center: NATIONAL_MAP_CENTER,
zoom: NATIONAL_MAP_ZOOM,
attributionControl: false,
})
const resizeObserver = new ResizeObserver(() => {
@@ -289,7 +289,7 @@ export function SourceCatalogPanel({
{Number(latestBuildingsRegister.source_metadata?.['building_unit_count'] ?? 0).toLocaleString('nl-BE')} eenheden · {' '}
{Number(latestBuildingsRegister.source_metadata?.['linked_address_count'] ?? 0).toLocaleString('nl-BE')} gekoppelde adressen
</span>
<p>Registerstatus en geaggregeerde koppelingen voor Mol; adreslabels en persoonsgegevens worden niet in de kaartlaag getoond.</p>
<p>Registerstatus en geaggregeerde koppelingen binnen de werkelijk ingeladen dekking; adreslabels en persoonsgegevens worden niet in de kaartlaag getoond.</p>
</article>
) : null}
{dhmvDatasets.length > 0 ? (
@@ -297,9 +297,9 @@ export function DetectionLab({
<p>{yoloRuntimeReady ? `${yoloPreflight?.runtime.cuda_available ? 'GPU' : 'CPU'} · lokaal model gevonden` : 'Controleer de modelconfiguratie onder beheer.'}</p>
</div>
<div className="ai-user-summary-card">
<span>Gevalideerde kwaliteit</span>
<span>Validatiescope</span>
<strong>{selectedOperatorProfile ? `F1 ${selectedOperatorProfile.f1.toFixed(3)}` : 'Nog niet gekoppeld'}</strong>
<p>{selectedOperatorProfile ? `${selectedOperatorProfile.positiveSampleCount} testgebieden · resultaten blijven controleplichtig` : 'Kies het goedgekeurde lokale profiel.'}</p>
<p>{selectedOperatorProfile ? selectedOperatorProfile.validationScope : 'Kies een modelprofiel met gedocumenteerd evaluatiebewijs.'}</p>
</div>
<div className={rasterDatasets.length > 0 ? 'ai-user-summary-card ai-user-summary-card-ready' : 'ai-user-summary-card'}>
<span>Beschikbare luchtbeelden</span>
@@ -310,6 +310,15 @@ export function DetectionLab({
<p className="ai-quality-guidance">
{detectionQualityInterpretation(selectedOperatorProfile?.f1)}
</p>
{selectedOperatorProfile && !selectedOperatorProfile.nationallyValidated ? (
<div className="result-state result-state-warning" role="status">
<strong>Nog niet nationaal gevalideerd</strong>
<p>
Dit model is operationeel voor gecontroleerde beeldanalyse, maar de gemeten kwaliteit geldt alleen voor {selectedOperatorProfile.validationScope}.
Resultaten elders in Belgie of op zee vereisen lokale referentiedata en QA voordat ze als betrouwbaar kunnen worden vrijgegeven.
</p>
</div>
) : null}
<DetectionModelManagement
detectionModels={detectionModels}
@@ -10,6 +10,8 @@ export interface DetectionOperatorProfile {
f1: number
positiveSampleCount: number
maxBackgroundDetections: number
validationScope: string
nationallyValidated: boolean
description: string
limitationMessage: string
}
@@ -27,6 +29,8 @@ export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
f1: 0.6068607646002744,
positiveSampleCount: 7,
maxBackgroundDetections: 0,
validationScope: '7 onafhankelijke testgebieden in Mol en de Kempen',
nationallyValidated: false,
description:
'Aanbevolen profiel met een evenwicht tussen gevonden en gemiste kleine gebouwen, opnieuw gemeten over zeven onafhankelijke testgebieden in Mol en de Kempen.',
limitationMessage:
@@ -44,6 +48,8 @@ export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
f1: 0.5432865390636915,
positiveSampleCount: 7,
maxBackgroundDetections: 0,
validationScope: '7 onafhankelijke testgebieden in Mol en de Kempen',
nationallyValidated: false,
description: 'Voorgaand profiel voor controles waarbij minder foutieve vondsten belangrijker zijn dan maximale dekking.',
limitationMessage:
'De lege-achtergrondtest is geslaagd. Dit profiel vindt minder onterechte objecten, maar mist meer kleine gebouwen dan het aanbevolen profiel.',
@@ -60,6 +66,8 @@ export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
f1: 0.32086574003576274,
positiveSampleCount: 7,
maxBackgroundDetections: 0,
validationScope: '7 onafhankelijke testgebieden in Mol en de Kempen',
nationallyValidated: false,
description: 'Profiel met hoge precisie voor controles waarbij zo weinig mogelijk foutieve vondsten zwaarder wegen dan volledige dekking.',
limitationMessage:
'Goedgekeurd na de lege-achtergrondtest. Resultaten in dun bebouwde context blijven altijd controlebewijs en geen automatische waarheid.',
+1 -1
View File
@@ -178,7 +178,7 @@ const DATA_THEMES: DataTheme[] = [
id: 'soil',
label: 'Bodem',
shortLabel: 'Bodemkaart',
description: 'Historische DOV-bodemkartering met bodemtype, textuur en drainageklasse voor Mol.',
description: 'Officiele bodemkartering met bodemtype, textuur en drainageklasse waar de geselecteerde zone door een gekoppelde bron wordt gedekt.',
tokens: ['soil', 'bodem', 'bodemkaart', 'bodemtype', 'dov_soil_map'],
},
{
@@ -4,6 +4,9 @@ import type { AreaRead, ProjectRead } from '../../types'
const AREA_CATALOG_PAGE_SIZE = 12
function projectDisplayName(project: ProjectRead | null): string {
if (project?.name === 'Belgium and North Sea Workbench') {
return 'Belgie en Belgische Noordzee'
}
if (project?.name === 'Kempen Regional Workbench') {
return 'Kempen · volledige regio'
}
@@ -81,7 +84,7 @@ export function AreaPanel({
<div className="data-selection-summary data-selection-summary-area">
<span>Getoond op de kaart</span>
<strong>{selectedArea?.name ?? 'Geen gebied geselecteerd'}</strong>
<small>{selectedArea?.area_m2 ? `${(selectedArea.area_m2 / 1_000_000).toLocaleString('nl-BE', { maximumFractionDigits: 2 })} km2` : 'Kies een gemeente of de volledige regio op de kaart.'}</small>
<small>{selectedArea?.area_m2 ? `${(selectedArea.area_m2 / 1_000_000).toLocaleString('nl-BE', { maximumFractionDigits: 2 })} km2` : 'Kies een gemeente, gewest, zeezone of teken een eigen selectie.'}</small>
</div>
<details className="data-panel-form-block">
@@ -136,7 +139,7 @@ export function AreaPanel({
setSearchQuery(event.target.value)
setPage(1)
}}
placeholder="Bijvoorbeeld Mol"
placeholder="Bijvoorbeeld Brussel, Namen of Noordzee"
/>
</label>
<div className="catalog-pagination" aria-label="Paginering van gebieden">
@@ -2,19 +2,23 @@ import type { FormEvent } from 'react'
import type { ProjectCreate, ProjectRead } from '../../types'
const TECHNICAL_PROJECT_PATTERN = /^(GeoIntel Detection Quality Matrix|GeoIntel hard-negative|GeoIntel training|Mol Building QA)/i
const NATIONAL_PROJECT_NAME = 'Belgium and North Sea Workbench'
const REGIONAL_PROJECT_NAME = 'Kempen Regional Workbench'
const LEGACY_MOL_PROJECT_NAME = 'Mol Municipality Workbench'
const PROTECTED_PROJECT_NAMES = new Set([REGIONAL_PROJECT_NAME, LEGACY_MOL_PROJECT_NAME])
const PROTECTED_PROJECT_NAMES = new Set([NATIONAL_PROJECT_NAME, REGIONAL_PROJECT_NAME, LEGACY_MOL_PROJECT_NAME])
function isTechnicalProject(project: ProjectRead): boolean {
return TECHNICAL_PROJECT_PATTERN.test(project.name)
}
function isAdvancedProject(project: ProjectRead): boolean {
return isTechnicalProject(project) || project.name === LEGACY_MOL_PROJECT_NAME
return isTechnicalProject(project) || project.name === LEGACY_MOL_PROJECT_NAME || project.name === REGIONAL_PROJECT_NAME
}
function projectDisplayName(project: ProjectRead): string {
if (project.name === NATIONAL_PROJECT_NAME) {
return 'Belgie en Belgische Noordzee'
}
if (project.name === REGIONAL_PROJECT_NAME) {
return 'Kempen · volledige regionale werkruimte'
}
@@ -126,7 +130,7 @@ export function ProjectPanel({
<label>
Regio
<input
value={projectForm.region ?? 'Kempen'}
value={projectForm.region ?? 'Belgie en Belgische Noordzee'}
onChange={(event) => onUpdateProjectForm({ ...projectForm, region: event.target.value })}
placeholder="Regio"
/>
@@ -70,6 +70,9 @@ function persistedSegmentationModelLabel(modelName: string | null | undefined):
if (modelName === 'fixture-segmenter') return 'Testsegmentatie'
if (modelName === 'sam-placeholder') return 'SAM-model niet geconfigureerd'
if (modelName === 'yolo-seg-placeholder') return 'YOLO-segmentatie niet geconfigureerd'
if (modelName === 'segmentation-placeholder') return 'Segmentatiemodel niet geconfigureerd'
if (modelName === 'yolo-seg-configured') return 'Lokaal YOLO-segmentatiemodel'
if (modelName === 'sam-configured') return 'Lokaal SAM-model'
return modelName
}