Finish audit UI and live validation closure
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-17 14:36:55 +02:00
parent dbc28c8916
commit 75ab8f64ab
6 changed files with 87 additions and 27 deletions
+5
View File
@@ -19,8 +19,13 @@
- Reduced end-user noise across Map, Quality, Detection and Segmentation by - Reduced end-user noise across Map, Quality, Detection and Segmentation by
using Dutch task language and placing UUIDs, paths, checksums and runtime using Dutch task language and placing UUIDs, paths, checksums and runtime
diagnostics behind technical disclosures. diagnostics behind technical disclosures.
- Replaced persisted AI run/model/class/tile internals with friendly labels in
the ordinary result tables while retaining the original provenance in
persistence and technical views.
- Added widescreen layout guardrails for AI workspaces and regression coverage - Added widescreen layout guardrails for AI workspaces and regression coverage
for project lifecycle, packaged cleanup and the new component boundaries. for project lifecycle, packaged cleanup and the new component boundaries.
- Hardened the packaged archive command for direct container execution and
added a subprocess regression for the documented operator invocation.
## Sprint 233 Operational correctness and result completion (2026-07-17) ## Sprint 233 Operational correctness and result completion (2026-07-17)
@@ -102,8 +102,9 @@ def test_detection_results_table_uses_bounded_local_pagination() -> None:
assert "Paginering van gevonden objecten" in lab assert "Paginering van gevonden objecten" in lab
assert "Vorige resultatenpagina" in lab assert "Vorige resultatenpagina" in lab
assert "Volgende resultatenpagina" in lab assert "Volgende resultatenpagina" in lab
assert "formatSourceTilePath(detection.source_tile_path)" in lab assert "<th>Herkomst</th>" in lab
assert 'title={detection.source_tile_path ?? undefined}' in lab assert "Luchtbeeldtegel" in lab
assert 'title={detection.source_tile_path ?? undefined}' not in lab
assert "pagination-toolbar" in styles assert "pagination-toolbar" in styles
assert ".source-tile-cell" in styles assert ".source-tile-cell" in styles
assert "detectionItems.map((detection)" not in lab assert "detectionItems.map((detection)" not in lab
@@ -152,12 +152,17 @@ def test_frontend_lifecycle_and_component_boundaries_are_wired() -> None:
app_source = (ROOT / "frontend/src/App.tsx").read_text(encoding="utf-8") app_source = (ROOT / "frontend/src/App.tsx").read_text(encoding="utf-8")
project_panel = (ROOT / "frontend/src/components/project/ProjectPanel.tsx").read_text(encoding="utf-8") project_panel = (ROOT / "frontend/src/components/project/ProjectPanel.tsx").read_text(encoding="utf-8")
detection_lab = (ROOT / "frontend/src/components/detection/DetectionLab.tsx").read_text(encoding="utf-8") detection_lab = (ROOT / "frontend/src/components/detection/DetectionLab.tsx").read_text(encoding="utf-8")
segmentation_lab = (ROOT / "frontend/src/components/segmentation/SegmentationLab.tsx").read_text(encoding="utf-8")
map_workspace = (ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8") map_workspace = (ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8")
premium_css = (ROOT / "frontend/src/styles/premium.css").read_text(encoding="utf-8") premium_css = (ROOT / "frontend/src/styles/premium.css").read_text(encoding="utf-8")
assert "OverviewWorkspace" in app_source assert "OverviewWorkspace" in app_source
assert "onArchiveProject={archiveProject}" in app_source assert "onArchiveProject={archiveProject}" in app_source
assert "DetectionModelManagement" in detection_lab assert "DetectionModelManagement" in detection_lab
assert "persistedDetectionModelLabel" in detection_lab
assert "Lokaal gebouwmodel" in detection_lab
assert "persistedSegmentationModelLabel" in segmentation_lab
assert "Testsegmentatie" in segmentation_lab
assert "from './mapWorkspaceUtils'" in map_workspace assert "from './mapWorkspaceUtils'" in map_workspace
assert "Werkruimte archiveren" in project_panel assert "Werkruimte archiveren" in project_panel
assert "PROTECTED_PROJECT_NAMES" in project_panel assert "PROTECTED_PROJECT_NAMES" in project_panel
+20 -3
View File
@@ -9987,6 +9987,23 @@ Implemented:
- Added ultrawide AI-workspace constraints so controls use the available width - Added ultrawide AI-workspace constraints so controls use the available width
without creating multiple narrow nested columns. without creating multiple narrow nested columns.
Validation evidence is recorded after the complete compile, pytest, typecheck, Validation:
build, readiness, migration, deployment, live cleanup and browser acceptance - The local readiness gate passed 907 backend tests, backend compilation,
chain has completed. documentation and API contract audits, the single Alembic head
`202607160001`, frontend TypeScript typecheck and the production build.
- The first live dry-run correctly made no database change but exposed that a
direct `/app/scripts/archive_technical_projects.py` invocation resolved the
wrong Python package. The script now bootstraps the packaged `/app` backend
root, and a subprocess regression executes the documented command shape.
- The corrected all-in-one deployment became healthy, reported PostGIS 3.6,
passed required schema/index checks and retained Alembic head
`202607160001`.
- The live cleanup dry-run matched 1,091 allowlisted technical projects and
preserved both canonical workspaces. Apply archived the same 1,091 rows.
The active-project API now reports exactly two rows (`Kempen Regional
Workbench` and `Mol Municipality Workbench`), the archived API reports
1,091, and a repeated dry-run reports zero remaining matches.
- Live browser inspection found no console errors or document overflow at
3440x1440. Map and Detection workspaces keep a bounded 1,680 px operational
canvas, technical disclosures are collapsed and persisted AI internals now
use friendly labels in the ordinary result tables.
@@ -25,8 +25,29 @@ function detectionQualityInterpretation(f1: number | null | undefined): string {
return 'Onvoldoende betrouwbaar voor operationeel gebruik.' return 'Onvoldoende betrouwbaar voor operationeel gebruik.'
} }
function detectionStatusLabel(status: string): string {
if (status === 'completed') return 'afgerond'
if (status === 'success') return 'geslaagd'
if (status === 'failed') return 'mislukt'
if (status === 'running') return 'bezig'
if (status === 'queued') return 'in wachtrij'
return status.replace(/_/g, ' ')
}
function persistedDetectionModelLabel(modelName: string | null | undefined): string {
if (!modelName) return 'Gebouwdetectie'
if (modelName === 'yolo-configured') return 'Lokaal gebouwmodel'
if (modelName === 'manual-fixture-detector') return 'Testmodel'
if (modelName === 'yolo-placeholder') return 'Niet-geconfigureerd gebouwmodel'
return modelName
}
function detectionClassLabel(className: string): string {
if (className.toLowerCase() === 'building') return 'Gebouw'
return className
}
function formatDetectionRunLabel(run: DetectionRunRead): string { function formatDetectionRunLabel(run: DetectionRunRead): string {
const status = run.status === 'completed' ? 'afgerond' : run.status
const timestamp = run.finished_at ?? run.created_at const timestamp = run.finished_at ?? run.created_at
const dateLabel = timestamp const dateLabel = timestamp
? new Intl.DateTimeFormat('nl-BE', { ? new Intl.DateTimeFormat('nl-BE', {
@@ -34,7 +55,7 @@ function formatDetectionRunLabel(run: DetectionRunRead): string {
timeStyle: 'short', timeStyle: 'short',
}).format(new Date(timestamp)) }).format(new Date(timestamp))
: 'datum onbekend' : 'datum onbekend'
return `${run.model_name || 'Gebouwdetectie'} · ${status} · ${dateLabel}` return `${persistedDetectionModelLabel(run.model_name)} · ${detectionStatusLabel(run.status)} · ${dateLabel}`
} }
interface CalibrationRow { interface CalibrationRow {
@@ -753,18 +774,16 @@ export function DetectionLab({
<th>Type</th> <th>Type</th>
<th>Zekerheid</th> <th>Zekerheid</th>
<th>Model</th> <th>Model</th>
<th>Beeldtegel</th> <th>Herkomst</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{visibleDetectionItems.map((detection) => ( {visibleDetectionItems.map((detection) => (
<tr key={detection.id}> <tr key={detection.id}>
<td>{detection.class_name}</td> <td>{detectionClassLabel(detection.class_name)}</td>
<td>{detection.confidence.toFixed(2)}</td> <td>{detection.confidence.toFixed(2)}</td>
<td>{detection.model_name}</td> <td>{persistedDetectionModelLabel(detection.model_name)}</td>
<td className="source-tile-cell" title={detection.source_tile_path ?? undefined}> <td className="source-tile-cell">Luchtbeeldtegel</td>
{formatSourceTilePath(detection.source_tile_path)}
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@@ -1119,11 +1138,3 @@ function downloadJsonFile(filename: string, payload: unknown): void {
function formatNullableNumber(value: number | null, digits: number): string { function formatNullableNumber(value: number | null, digits: number): string {
return typeof value === 'number' && Number.isFinite(value) ? value.toFixed(digits) : 'n.v.t.' return typeof value === 'number' && Number.isFinite(value) ? value.toFixed(digits) : 'n.v.t.'
} }
function formatSourceTilePath(path: string | null | undefined): string {
if (!path) {
return 'n.v.t.'
}
const parts = path.replace(/\\/g, '/').split('/').filter(Boolean)
return parts[parts.length - 1] ?? path
}
@@ -53,8 +53,29 @@ function segmentationRunLabel(run: SegmentationRunRead): string {
const dateLabel = timestamp const dateLabel = timestamp
? new Intl.DateTimeFormat('nl-BE', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(timestamp)) ? new Intl.DateTimeFormat('nl-BE', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(timestamp))
: 'datum onbekend' : 'datum onbekend'
const status = run.status === 'completed' ? 'afgerond' : run.status return `${persistedSegmentationModelLabel(run.model_name)} · ${analysisStatusLabel(run.status)} · ${dateLabel}`
return `${run.model_name || 'Segmentatie'} · ${status} · ${dateLabel}` }
function analysisStatusLabel(status: string): string {
if (status === 'completed') return 'afgerond'
if (status === 'success') return 'geslaagd'
if (status === 'failed') return 'mislukt'
if (status === 'running') return 'bezig'
if (status === 'queued') return 'in wachtrij'
return status.replace(/_/g, ' ')
}
function persistedSegmentationModelLabel(modelName: string | null | undefined): string {
if (!modelName) return 'Segmentatie'
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'
return modelName
}
function segmentationClassLabel(className: string): string {
if (className.toLowerCase() === 'building') return 'Gebouw'
return className
} }
export function SegmentationLab({ export function SegmentationLab({
@@ -371,17 +392,17 @@ export function SegmentationLab({
<th>Zekerheid</th> <th>Zekerheid</th>
<th>Oppervlakte m²</th> <th>Oppervlakte m²</th>
<th>Model</th> <th>Model</th>
<th>Brontegel</th> <th>Herkomst</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{segmentationItems.map((segmentation) => ( {segmentationItems.map((segmentation) => (
<tr key={segmentation.id}> <tr key={segmentation.id}>
<td>{segmentation.class_name}</td> <td>{segmentationClassLabel(segmentation.class_name)}</td>
<td>{segmentation.confidence?.toFixed(2) ?? 'n.v.t.'}</td> <td>{segmentation.confidence?.toFixed(2) ?? 'n.v.t.'}</td>
<td>{segmentation.area_m2?.toFixed(2) ?? 'n.v.t.'}</td> <td>{segmentation.area_m2?.toFixed(2) ?? 'n.v.t.'}</td>
<td>{segmentation.model_name}</td> <td>{persistedSegmentationModelLabel(segmentation.model_name)}</td>
<td>{segmentation.tile_index ?? 'n.v.t.'}</td> <td>{segmentation.tile_index == null ? 'n.v.t.' : 'Luchtbeeldtegel'}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>