Finish audit UI and live validation closure
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user