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
using Dutch task language and placing UUIDs, paths, checksums and runtime
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
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)
@@ -102,8 +102,9 @@ def test_detection_results_table_uses_bounded_local_pagination() -> None:
assert "Paginering van gevonden objecten" in lab
assert "Vorige resultatenpagina" in lab
assert "Volgende resultatenpagina" in lab
assert "formatSourceTilePath(detection.source_tile_path)" in lab
assert 'title={detection.source_tile_path ?? undefined}' in lab
assert "<th>Herkomst</th>" in lab
assert "Luchtbeeldtegel" in lab
assert 'title={detection.source_tile_path ?? undefined}' not in lab
assert "pagination-toolbar" in styles
assert ".source-tile-cell" in styles
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")
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")
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")
premium_css = (ROOT / "frontend/src/styles/premium.css").read_text(encoding="utf-8")
assert "OverviewWorkspace" in app_source
assert "onArchiveProject={archiveProject}" in app_source
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 "Werkruimte archiveren" 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
without creating multiple narrow nested columns.
Validation evidence is recorded after the complete compile, pytest, typecheck,
build, readiness, migration, deployment, live cleanup and browser acceptance
chain has completed.
Validation:
- The local readiness gate passed 907 backend tests, backend compilation,
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.'
}
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 {
const status = run.status === 'completed' ? 'afgerond' : run.status
const timestamp = run.finished_at ?? run.created_at
const dateLabel = timestamp
? new Intl.DateTimeFormat('nl-BE', {
@@ -34,7 +55,7 @@ function formatDetectionRunLabel(run: DetectionRunRead): string {
timeStyle: 'short',
}).format(new Date(timestamp))
: 'datum onbekend'
return `${run.model_name || 'Gebouwdetectie'} · ${status} · ${dateLabel}`
return `${persistedDetectionModelLabel(run.model_name)} · ${detectionStatusLabel(run.status)} · ${dateLabel}`
}
interface CalibrationRow {
@@ -753,18 +774,16 @@ export function DetectionLab({
<th>Type</th>
<th>Zekerheid</th>
<th>Model</th>
<th>Beeldtegel</th>
<th>Herkomst</th>
</tr>
</thead>
<tbody>
{visibleDetectionItems.map((detection) => (
<tr key={detection.id}>
<td>{detection.class_name}</td>
<td>{detectionClassLabel(detection.class_name)}</td>
<td>{detection.confidence.toFixed(2)}</td>
<td>{detection.model_name}</td>
<td className="source-tile-cell" title={detection.source_tile_path ?? undefined}>
{formatSourceTilePath(detection.source_tile_path)}
</td>
<td>{persistedDetectionModelLabel(detection.model_name)}</td>
<td className="source-tile-cell">Luchtbeeldtegel</td>
</tr>
))}
</tbody>
@@ -1119,11 +1138,3 @@ function downloadJsonFile(filename: string, payload: unknown): void {
function formatNullableNumber(value: number | null, digits: number): string {
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
? new Intl.DateTimeFormat('nl-BE', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(timestamp))
: 'datum onbekend'
const status = run.status === 'completed' ? 'afgerond' : run.status
return `${run.model_name || 'Segmentatie'} · ${status} · ${dateLabel}`
return `${persistedSegmentationModelLabel(run.model_name)} · ${analysisStatusLabel(run.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({
@@ -371,17 +392,17 @@ export function SegmentationLab({
<th>Zekerheid</th>
<th>Oppervlakte m²</th>
<th>Model</th>
<th>Brontegel</th>
<th>Herkomst</th>
</tr>
</thead>
<tbody>
{segmentationItems.map((segmentation) => (
<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.area_m2?.toFixed(2) ?? 'n.v.t.'}</td>
<td>{segmentation.model_name}</td>
<td>{segmentation.tile_index ?? 'n.v.t.'}</td>
<td>{persistedSegmentationModelLabel(segmentation.model_name)}</td>
<td>{segmentation.tile_index == null ? 'n.v.t.' : 'Luchtbeeldtegel'}</td>
</tr>
))}
</tbody>