fix: align Mol explorer primary metrics
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-14 13:27:28 +02:00
parent 2958c25b6f
commit b999c54e22
3 changed files with 42 additions and 20 deletions
@@ -23,6 +23,8 @@ def test_map_first_explorer_is_the_default_product_flow() -> None:
assert "Bron nog niet ingeladen" in workspace assert "Bron nog niet ingeladen" in workspace
assert "useMapThemeSelectionInsights" in workspace assert "useMapThemeSelectionInsights" in workspace
assert "datasetsApi.selectVectorFeatures" in read("frontend/src/hooks/useMapThemeSelectionInsights.ts") assert "datasetsApi.selectVectorFeatures" in read("frontend/src/hooks/useMapThemeSelectionInsights.ts")
assert "activeSelectionResult" in workspace
assert "bboxesEqual(mapSelectionBbox, selectedAreaBbox)" in workspace
def test_map_rectangle_drag_is_wired_to_automatic_analysis() -> None: def test_map_rectangle_drag_is_wired_to_automatic_analysis() -> None:
+2 -2
View File
@@ -735,7 +735,7 @@ function App(): JSX.Element {
</aside> </aside>
<main className="workbench-main" id="workspace-main" tabIndex={-1}> <main className="workbench-main" id="workspace-main" tabIndex={-1}>
<div className="workspace-heading"> {activeWorkspace !== 'map' ? <div className="workspace-heading">
<div className="workspace-heading-copy"> <div className="workspace-heading-copy">
<p className="eyebrow">{selectedProject?.region ?? 'Mol, Kempen'}</p> <p className="eyebrow">{selectedProject?.region ?? 'Mol, Kempen'}</p>
<h2>{activeWorkspaceItem.label}</h2> <h2>{activeWorkspaceItem.label}</h2>
@@ -754,7 +754,7 @@ function App(): JSX.Element {
</button> </button>
) : null} ) : null}
</div> </div>
</div> </div> : null}
<div className="workspace-command-bar" aria-label="Workspace shortcuts"> <div className="workspace-command-bar" aria-label="Workspace shortcuts">
<div className="workspace-nav-cluster"> <div className="workspace-nav-cluster">
{workspaceNavItems.slice(0, 5).map((item) => ( {workspaceNavItems.slice(0, 5).map((item) => (
+38 -18
View File
@@ -106,6 +106,19 @@ function selectionAreaSquareMetres(bbox: VectorSelectionBBox | null): number | n
return Math.max(0, widthMetres * heightMetres) return Math.max(0, widthMetres * heightMetres)
} }
function bboxesEqual(left: VectorSelectionBBox | null, right: VectorSelectionBBox | null): boolean {
if (!left || !right) {
return false
}
const tolerance = 1e-9
return (
Math.abs(left.min_x - right.min_x) < tolerance &&
Math.abs(left.min_y - right.min_y) < tolerance &&
Math.abs(left.max_x - right.max_x) < tolerance &&
Math.abs(left.max_y - right.max_y) < tolerance
)
}
function formatArea(areaSquareMetres: number | null): string { function formatArea(areaSquareMetres: number | null): string {
if (areaSquareMetres === null) { if (areaSquareMetres === null) {
return 'Nog niet geselecteerd' return 'Nog niet geselecteerd'
@@ -469,14 +482,29 @@ export function MapWorkspace({
) )
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0] const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
const activeThemeDataset = themeDatasetMap[activeTheme.id] const activeThemeDataset = themeDatasetMap[activeTheme.id]
const selectedAreaSquareMetres = useMemo(() => selectionAreaSquareMetres(mapSelectionBbox), [mapSelectionBbox]) const themeResults = useMemo(
const selectedResultTotal = mapSelectionResult?.total_feature_count ?? mapSelectionResult?.feature_count ?? 0 () =>
themeInsights.flatMap((insight) => {
const theme = DATA_THEMES.find((candidate) => candidate.id === insight.themeId)
return theme ? [{ theme, dataset: insight.dataset, result: insight.result }] : []
}),
[themeInsights],
)
const activeSelectionResult = themeResults.find((item) => item.theme.id === activeThemeId)?.result ?? mapSelectionResult
const selectedAreaSquareMetres = useMemo(
() =>
bboxesEqual(mapSelectionBbox, selectedAreaBbox) && selectedMapArea?.area_m2
? selectedMapArea.area_m2
: selectionAreaSquareMetres(mapSelectionBbox),
[mapSelectionBbox, selectedAreaBbox, selectedMapArea?.area_m2],
)
const selectedResultTotal = activeSelectionResult?.total_feature_count ?? activeSelectionResult?.feature_count ?? 0
const selectedDensity = selectedAreaSquareMetres && selectedAreaSquareMetres > 0 const selectedDensity = selectedAreaSquareMetres && selectedAreaSquareMetres > 0
? selectedResultTotal / (selectedAreaSquareMetres / 1_000_000) ? selectedResultTotal / (selectedAreaSquareMetres / 1_000_000)
: null : null
const selectedResultProperties = useMemo(() => { const selectedResultProperties = useMemo(() => {
const keys = new Map<string, Set<string>>() const keys = new Map<string, Set<string>>()
for (const feature of mapSelectionResult?.geojson.features ?? []) { for (const feature of activeSelectionResult?.geojson.features ?? []) {
for (const [key, value] of Object.entries(feature.properties ?? {})) { for (const [key, value] of Object.entries(feature.properties ?? {})) {
if (value === null || value === undefined || typeof value === 'object' || key.endsWith('_id')) { if (value === null || value === undefined || typeof value === 'object' || key.endsWith('_id')) {
continue continue
@@ -492,26 +520,18 @@ export function MapWorkspace({
.filter(([, values]) => values.size > 0) .filter(([, values]) => values.size > 0)
.slice(0, 8) .slice(0, 8)
.map(([key, values]) => ({ key, values: Array.from(values) })) .map(([key, values]) => ({ key, values: Array.from(values) }))
}, [mapSelectionResult]) }, [activeSelectionResult])
const themeResults = useMemo(
() =>
themeInsights.flatMap((insight) => {
const theme = DATA_THEMES.find((candidate) => candidate.id === insight.themeId)
return theme ? [{ theme, dataset: insight.dataset, result: insight.result }] : []
}),
[themeInsights],
)
useEffect(() => { useEffect(() => {
setBboxInput(bboxToInputState(mapSelectionBbox)) setBboxInput(bboxToInputState(mapSelectionBbox))
}, [mapSelectionBbox]) }, [mapSelectionBbox])
useEffect(() => { useEffect(() => {
if (selectedMapDatasetId || !themeDatasetMap.buildings) { if (advancedMode || !activeThemeDataset || (selectedMapDataset && datasetMatchesTheme(selectedMapDataset, activeTheme))) {
return return
} }
onOpenDatasetInMap(themeDatasetMap.buildings) onOpenDatasetInMap(activeThemeDataset)
}, [onOpenDatasetInMap, selectedMapDatasetId, themeDatasetMap.buildings]) }, [activeTheme, activeThemeDataset, advancedMode, onOpenDatasetInMap, selectedMapDataset])
useEffect(() => { useEffect(() => {
if (!selectedMapDataset) { if (!selectedMapDataset) {
@@ -883,7 +903,7 @@ export function MapWorkspace({
</div> </div>
<div> <div>
<span>{activeTheme.shortLabel}</span> <span>{activeTheme.shortLabel}</span>
<strong>{mapSelectionResult ? resultCountLabel(mapSelectionResult) : 'Geen resultaat'}</strong> <strong>{activeSelectionResult ? resultCountLabel(activeSelectionResult) : 'Geen resultaat'}</strong>
</div> </div>
<div> <div>
<span>Dichtheid</span> <span>Dichtheid</span>
@@ -912,8 +932,8 @@ export function MapWorkspace({
})} })}
</div> </div>
{mapSelectionResult?.truncated ? ( {activeSelectionResult?.truncated ? (
<p className="geo-data-notice">De telling is volledig; op de kaart en in de tabel worden maximaal {mapSelectionResult.limit.toLocaleString('nl-BE')} objecten getoond.</p> <p className="geo-data-notice">De telling is volledig; op de kaart en in de tabel worden maximaal {activeSelectionResult.limit.toLocaleString('nl-BE')} objecten getoond.</p>
) : null} ) : null}
{mapSelectionError ? <p className="error">{mapSelectionError}</p> : null} {mapSelectionError ? <p className="error">{mapSelectionError}</p> : null}
{themeResultsError ? <p className="error">{themeResultsError}</p> : null} {themeResultsError ? <p className="error">{themeResultsError}</p> : null}