fix: polish live regional workbench labels
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 21:48:51 +02:00
parent b5bcd9b041
commit 36d6a92825
9 changed files with 80 additions and 26 deletions
+1
View File
@@ -14,6 +14,7 @@
- Replaced technical dataset names with theme/source labels and moved benchmark projects, raw source metadata, provider internals, QA evidence and model diagnostics behind advanced disclosures.
- Defaulted Detection Lab to the configured local YOLO asset, active local model profile and measured operating threshold; model quality remains explicitly review-required rather than presented as production-perfect.
- Simplified Quality, Downloads, Status and Beheer around end-user tasks without changing API contracts, migrations, persistence or inference behavior.
- Followed the live browser audit by moving the legacy Mol-only workspace under advanced management, adding friendly regional-boundary dataset names, translating the active model profiles and replacing the empty `waiting` quality status with end-user language.
## Sprint 192 Regional map state correctness (2026-07-14)
@@ -23,9 +23,9 @@ def test_detection_operator_profiles_define_explicit_yolo_candidates_and_promote
assert "f1: 0.5824578631584316" in source
assert "f1: 0.5432865390636915" in source
assert "maxBackgroundDetections: 0" in source
assert "pure-empty gate passed" in source
assert "1,571 fewer false negatives" in source
assert "higher false-positive review load" in source
assert "lege-achtergrondtest is geslaagd" in source
assert "1.571 minder gemiste gebouwen" in source
assert "onterecht gevonden objecten" in source
def test_detection_lab_surfaces_profiles_as_deliberate_operator_actions() -> None:
@@ -34,12 +34,17 @@ def test_technical_projects_and_metadata_are_progressively_disclosed() -> None:
projects = read("frontend/src/components/project/ProjectPanel.tsx")
areas = read("frontend/src/components/project/AreaPanel.tsx")
datasets = read("frontend/src/components/datasets/DatasetPanel.tsx")
dataset_names = read("frontend/src/lib/datasetDisplay.ts")
assert "TECHNICAL_PROJECT_PATTERN" in projects
assert "technische test- en benchmarkruns" in projects
assert "project.name === LEGACY_MOL_PROJECT_NAME" in projects
assert "alternatieve en technische werkruimtes" in projects
assert "Kempen · volledige regionale werkruimte" in projects
assert "area-catalog-disclosure" in areas
assert "dataset-technical-details" in datasets
assert "getDatasetDisplayName" in datasets
assert "dataset.source_metadata?.layer_type" in dataset_names
assert "regional_boundary: 'Grens vervoerregio Kempen'" in dataset_names
def test_configured_yolo_and_active_asset_are_selected_without_hiding_limitations() -> None:
@@ -53,3 +58,13 @@ def test_configured_yolo_and_active_asset_are_selected_without_hiding_limitation
assert 'aria-label="Status gebouwdetectie"' in lab
assert "resultaten blijven controleplichtig" in lab
assert "Modelkalibratie voor beheerders" in lab
def test_visible_ai_and_quality_labels_are_end_user_facing() -> None:
profiles = read("frontend/src/components/detection/detectionProfiles.ts")
quality = read("frontend/src/components/quality/QualityResultsPanel.tsx")
assert "Aanbevolen controleprofiel kleine gebouwen" in profiles
assert "Controleer wel extra op onterecht gevonden objecten" in profiles
assert "qualityStatusLabel" in quality
assert "nog niet uitgevoerd" in quality
+5
View File
@@ -18,6 +18,11 @@ Tested before deployment:
- The false-positive review validator returned the expected `review_required` gate with all 48 selected decisions still `unreviewed`; no unreviewed QA mismatch was promoted into training data.
- Local Docker validation was unavailable because the Windows workstation has no Docker CLI; live container/PostGIS validation remains part of the Tower deployment pass.
Live browser follow-up:
- Tower rebuilt the AI-enabled all-in-one image successfully; container health, PostGIS 3.6, required schema objects, Alembic head `202607140001`, frontend, API proxy and icon checks passed.
- The map opened directly on the complete regional workspace with 29 work Areas, no region selector, the full region selected and 466,078 buildings, 88,332 water objects, 84,504 roads and 415,288 parcels available.
- The browser audit found four remaining presentation leaks: the legacy Mol-only project was still prominent, boundary datasets showed filenames, profile names were English and an empty quality status said `waiting`. These were corrected and protected by the now `561`-test suite.
Next:
- Extend official population and modern land-use time series from Mol to all 28 municipalities, then expose a guided raster-import and configured-detection run over a drawn map selection.
@@ -17,7 +17,7 @@ export interface DetectionOperatorProfile {
export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
{
id: 'small-building-balanced-review',
displayName: 'Recommended small-building review',
displayName: 'Aanbevolen controleprofiel kleine gebouwen',
modelAssetId: 'geointel-building-yolov8s-smallbld-minpx3-img640-ft30-pt',
confidenceThreshold: 0.15,
defaultApproved: true,
@@ -28,13 +28,13 @@ export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
positiveSampleCount: 7,
maxBackgroundDetections: 0,
description:
'Recommended recall-balanced profile for building review, with improved small-building coverage across seven Kempen AOIs.',
'Aanbevolen profiel met een evenwicht tussen gevonden en gemiste kleine gebouwen, gemeten over zeven testgebieden in de Kempen.',
limitationMessage:
'The pure-empty gate passed and persisted QA found 1,571 fewer false negatives than the previous balanced profile; expect a higher false-positive review load.',
'De lege-achtergrondtest is geslaagd en de kwaliteitsmeting vond 1.571 minder gemiste gebouwen dan het vorige profiel. Controleer wel extra op onterecht gevonden objecten.',
},
{
id: 'expanded-balanced-review',
displayName: 'Legacy expanded balanced review',
displayName: 'Voorgaand gebalanceerd controleprofiel',
modelAssetId: 'geointel-building-yolov8s-aoi1024expandedminpx4vis035e50-pt',
confidenceThreshold: 0.15,
defaultApproved: true,
@@ -44,13 +44,13 @@ export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
f1: 0.5432865390636915,
positiveSampleCount: 7,
maxBackgroundDetections: 0,
description: 'Legacy expanded-AOI profile for review sessions where precision matters more than the newest recall gain.',
description: 'Voorgaand profiel voor controles waarbij minder foutieve vondsten belangrijker zijn dan maximale dekking.',
limitationMessage:
'The pure-empty gate passed; this profile has fewer false positives but more persistent small-building misses than the recommended profile.',
'De lege-achtergrondtest is geslaagd. Dit profiel vindt minder onterechte objecten, maar mist meer kleine gebouwen dan het aanbevolen profiel.',
},
{
id: 'conservative-review',
displayName: 'Promoted conservative review',
displayName: 'Conservatief controleprofiel',
modelAssetId: 'geointel-building-yolov8s-aoi1024bg512r3e50-pt',
confidenceThreshold: 0.35,
defaultApproved: true,
@@ -60,8 +60,8 @@ export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
f1: 0.32086574003576274,
positiveSampleCount: 7,
maxBackgroundDetections: 0,
description: 'Legacy high-precision profile for review sessions where fewer false positives matter more than recall.',
description: 'Profiel met hoge precisie voor controles waarbij zo weinig mogelijk foutieve vondsten zwaarder wegen dan volledige dekking.',
limitationMessage:
'Default-approved after the split-background pure-empty gate passed; sparse-context detections remain review-only evidence.',
'Goedgekeurd na de lege-achtergrondtest. Resultaten in dun bebouwde context blijven altijd controlebewijs en geen automatische waarheid.',
},
]
+11 -1
View File
@@ -1,6 +1,16 @@
import type { FormEvent } from 'react'
import type { AreaRead, ProjectRead } from '../../types'
function projectDisplayName(project: ProjectRead | null): string {
if (project?.name === 'Kempen Regional Workbench') {
return 'Kempen · volledige regio'
}
if (project?.name === 'Mol Municipality Workbench') {
return 'Mol · lokale historische werkruimte'
}
return project?.name ?? 'Geen werkruimte actief'
}
interface AreaFormState {
name: string
geometry: string
@@ -41,7 +51,7 @@ export function AreaPanel({
</div>
<span className="count-pill">{areas.length}</span>
</div>
<p className="muted">{selectedProject ? selectedProject.name : 'Geen werkruimte actief'}</p>
<p className="muted">{projectDisplayName(selectedProject)}</p>
<div className="data-selection-summary data-selection-summary-area">
<span>Getoond op de kaart</span>
@@ -2,11 +2,27 @@ 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 REGIONAL_PROJECT_NAME = 'Kempen Regional Workbench'
const LEGACY_MOL_PROJECT_NAME = 'Mol Municipality Workbench'
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
}
function projectDisplayName(project: ProjectRead): string {
if (project.name === REGIONAL_PROJECT_NAME) {
return 'Kempen · volledige regionale werkruimte'
}
if (project.name === LEGACY_MOL_PROJECT_NAME) {
return 'Mol · lokale historische werkruimte'
}
return project.name
}
interface ProjectPanelProps {
projects: ProjectRead[]
selectedProjectId: string | null
@@ -33,9 +49,9 @@ export function ProjectPanel({
onLoadDemoWorkflow,
}: ProjectPanelProps): JSX.Element {
const selectedProject = projects.find((project) => project.id === selectedProjectId)
const regularProjects = projects.filter((project) => !isTechnicalProject(project))
const technicalProjects = projects.filter(isTechnicalProject)
const visibleProjects = selectedProject && isTechnicalProject(selectedProject)
const regularProjects = projects.filter((project) => !isAdvancedProject(project))
const advancedProjects = projects.filter(isAdvancedProject)
const visibleProjects = selectedProject && isAdvancedProject(selectedProject)
? [selectedProject, ...regularProjects]
: regularProjects
@@ -52,7 +68,7 @@ export function ProjectPanel({
<div className="data-selection-summary data-selection-summary-project">
<span>Actieve werkruimte</span>
<strong>{selectedProject?.name ?? 'Geen werkruimte actief'}</strong>
<strong>{selectedProject ? projectDisplayName(selectedProject) : 'Geen werkruimte actief'}</strong>
<small>{selectedProject?.region ?? 'De regionale werkruimte wordt automatisch gekozen.'}</small>
</div>
@@ -68,7 +84,7 @@ export function ProjectPanel({
aria-pressed={project.id === selectedProjectId}
data-testid={`project-select-${project.id}`}
>
<strong>{project.name}</strong>
<strong>{projectDisplayName(project)}</strong>
<span>{project.region || 'Regio niet opgegeven'}</span>
</button>
</li>
@@ -118,11 +134,11 @@ export function ProjectPanel({
</button>
{demoWorkflowMessage ? <p>{demoWorkflowMessage}</p> : null}
</div>
{technicalProjects.length > 0 ? (
{advancedProjects.length > 0 ? (
<details className="technical-run-list">
<summary>{technicalProjects.length} technische test- en benchmarkruns</summary>
<summary>{advancedProjects.length} alternatieve en technische werkruimtes</summary>
<ul className="entity-list">
{technicalProjects.map((project) => (
{advancedProjects.map((project) => (
<li key={project.id}>
<button
className={project.id === selectedProjectId ? 'entity-card entity-card-active' : 'entity-card'}
@@ -131,7 +147,7 @@ export function ProjectPanel({
aria-pressed={project.id === selectedProjectId}
data-testid={`project-select-${project.id}`}
>
<strong>{project.name}</strong>
<strong>{projectDisplayName(project)}</strong>
<span>{project.region || 'Technische run'}</span>
</button>
</li>
@@ -45,6 +45,13 @@ function qualityMetricValue(metric: MetricRead | undefined): string {
return Number.isInteger(value) ? String(value) : value.toFixed(3)
}
function qualityStatusLabel(status: string | null | undefined): string {
if (!status || status === 'waiting') return 'nog niet uitgevoerd'
if (status === 'completed' || status === 'ok') return 'afgerond'
if (status === 'failed') return 'mislukt'
return status
}
function metricByKey(check: QualityCheckRead | null, metricKey: string): MetricRead | undefined {
return check?.metrics.find((metric) => metric.metric_key === metricKey)
}
@@ -169,7 +176,7 @@ export function QualityResultsPanel({
</div>
<div>
<span>Laatste status</span>
<strong>{latestCheck?.status ?? 'waiting'}</strong>
<strong>{qualityStatusLabel(latestCheck?.status)}</strong>
</div>
</div>
</div>
@@ -201,7 +208,7 @@ export function QualityResultsPanel({
</div>
<div>
<span>Laatste vergelijking</span>
<strong>{latestCheck?.status ?? 'waiting'}</strong>
<strong>{qualityStatusLabel(latestCheck?.status)}</strong>
<p className="quality-dataset-name">
Te controleren: {latestCandidateName ?? latestCheck?.candidate_dataset_id ?? 'n.v.t.'} / referentie: {latestReferenceName ?? latestCheck?.reference_dataset_id ?? 'n.v.t.'}
</p>
+1 -1
View File
@@ -12,7 +12,7 @@ const DATASET_LABEL_BY_LAYER: Record<string, string> = {
}
export function getDatasetDisplayName(dataset: DatasetCreateResponse): string {
const layer = (dataset.reference_layer_name ?? dataset.source_metadata?.layer_name ?? '')
const layer = (dataset.reference_layer_name ?? dataset.source_metadata?.layer_name ?? dataset.source_metadata?.layer_type ?? '')
.toString()
.toLowerCase()
const label = DATASET_LABEL_BY_LAYER[layer]