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
|
- 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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user