feat: simplify regional workbench language
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { AreaRead, DatasetCreateResponse, ExportRead, ProjectRead, QualityCheckRead } from '../types'
|
||||
import { REGIONAL_WORKSPACE_LABEL, REGIONAL_WORKSPACE_PROJECT_NAME } from '../config/primaryFocus'
|
||||
|
||||
interface StatusItem {
|
||||
key: string
|
||||
@@ -26,6 +27,17 @@ function countReferenceDatasets(datasets: DatasetCreateResponse[]): number {
|
||||
return datasets.filter((dataset) => dataset.dataset_role === 'reference').length
|
||||
}
|
||||
|
||||
function statusLabel(state: StatusItem['state']): string {
|
||||
if (state === 'ready') return 'gereed'
|
||||
if (state === 'warning') return 'aandacht'
|
||||
return 'nog open'
|
||||
}
|
||||
|
||||
function projectLabel(project: ProjectRead | null): string {
|
||||
if (!project) return 'Geen werkruimte'
|
||||
return project.name === REGIONAL_WORKSPACE_PROJECT_NAME ? REGIONAL_WORKSPACE_LABEL : project.name
|
||||
}
|
||||
|
||||
function nextAction(items: StatusItem[]): string {
|
||||
if (!items.some((item) => item.key === 'project' && item.state === 'ready')) {
|
||||
return 'De regionale werkruimte kon niet worden geopend.'
|
||||
@@ -65,7 +77,7 @@ export function WorkbenchStatusStrip({
|
||||
{
|
||||
key: 'project',
|
||||
label: 'Werkruimte',
|
||||
value: selectedProject ? selectedProject.name : 'Geen werkruimte',
|
||||
value: projectLabel(selectedProject),
|
||||
detail: selectedProject ? selectedProject.region : 'Niet geladen',
|
||||
state: selectedProject ? 'ready' : 'waiting',
|
||||
},
|
||||
@@ -114,12 +126,12 @@ export function WorkbenchStatusStrip({
|
||||
<div>
|
||||
<p className="eyebrow">Platformstatus</p>
|
||||
<h2>
|
||||
<span className="sr-only">Workbench status: </span>
|
||||
<span className="sr-only">Status werkruimte: </span>
|
||||
{coreReady ? 'Kaartwerkruimte is gebruiksklaar' : 'Kaartwerkruimte vraagt aandacht'}
|
||||
</h2>
|
||||
<p className="status-next-action">{nextAction(items)}</p>
|
||||
</div>
|
||||
<div className="status-readiness" aria-label={`${readyCount} of ${items.length} workbench stages ready`}>
|
||||
<div className="status-readiness" aria-label={`${readyCount} van ${items.length} onderdelen gereed`}>
|
||||
<strong>{readyCount}/{items.length}</strong>
|
||||
<span>onderdelen gereed</span>
|
||||
</div>
|
||||
@@ -129,7 +141,7 @@ export function WorkbenchStatusStrip({
|
||||
<div className={`status-tile status-tile-${item.state}`} key={item.key}>
|
||||
<div className="status-tile-topline">
|
||||
<span>{item.label}</span>
|
||||
<span className="status-pill">{item.state}</span>
|
||||
<span className="status-pill">{statusLabel(item.state)}</span>
|
||||
</div>
|
||||
<strong title={item.value}>{item.value}</strong>
|
||||
<p>{item.detail}</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Dispatch, FormEvent, SetStateAction } from 'react'
|
||||
import type { AreaRead, DatasetCreateResponse } from '../../types'
|
||||
import { getDatasetDisplayName } from '../../lib/datasetDisplay'
|
||||
import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/datasetDisplay'
|
||||
|
||||
interface DatasetFormState {
|
||||
datasetType: string
|
||||
@@ -63,6 +63,9 @@ function normalizeDatasetRole(dataset: DatasetCreateResponse): 'reference' | 'ca
|
||||
if (dataset.dataset_role === 'reference') {
|
||||
return 'reference'
|
||||
}
|
||||
if (dataset.dataset_role === 'source') {
|
||||
return 'source'
|
||||
}
|
||||
if (dataset.dataset_role === 'derived' || isVectorDatasetType(dataset.dataset_type)) {
|
||||
return 'candidate'
|
||||
}
|
||||
@@ -76,7 +79,7 @@ function datasetRoleLabel(role: string): string {
|
||||
if (role === 'candidate') {
|
||||
return 'Analyse-resultaat'
|
||||
}
|
||||
return 'Eigen bron'
|
||||
return 'Basisbron'
|
||||
}
|
||||
|
||||
function datasetActionHint(dataset: DatasetCreateResponse, role: string): string {
|
||||
@@ -92,6 +95,19 @@ function datasetActionHint(dataset: DatasetCreateResponse, role: string): string
|
||||
return 'Ingeladen gegevensbron voor verdere analyse.'
|
||||
}
|
||||
|
||||
function formatObservationDate(value: string | null | undefined): string {
|
||||
if (!value) return 'huidige toestand'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
return new Intl.DateTimeFormat('nl-BE', { day: 'numeric', month: 'short', year: 'numeric' }).format(date)
|
||||
}
|
||||
|
||||
function datasetKindLabel(dataset: DatasetCreateResponse): string {
|
||||
if (dataset.dataset_type === 'raster') return 'Luchtbeeld'
|
||||
if (isVectorDatasetType(dataset.dataset_type)) return 'Kaartlaag'
|
||||
return 'Gegevensbron'
|
||||
}
|
||||
|
||||
export function DatasetPanel({
|
||||
selectedProjectId,
|
||||
selectedDatasetId,
|
||||
@@ -107,8 +123,24 @@ export function DatasetPanel({
|
||||
onOpenDatasetInMap,
|
||||
onOpenDatasetExport,
|
||||
}: DatasetPanelProps) {
|
||||
const readyDatasets = datasets.filter((dataset) => dataset.status === 'ready').length
|
||||
const selectedDataset = datasets.find((dataset) => dataset.id === selectedDatasetId)
|
||||
const latestDatasetBySeries = new Map<string, DatasetCreateResponse>()
|
||||
datasets.forEach((dataset) => {
|
||||
if (!dataset.temporal_series_key) return
|
||||
const current = latestDatasetBySeries.get(dataset.temporal_series_key)
|
||||
const currentTimestamp = current?.observed_at ? Date.parse(current.observed_at) : Number.NEGATIVE_INFINITY
|
||||
const candidateTimestamp = dataset.observed_at ? Date.parse(dataset.observed_at) : Number.NEGATIVE_INFINITY
|
||||
if (!current || candidateTimestamp > currentTimestamp) {
|
||||
latestDatasetBySeries.set(dataset.temporal_series_key, dataset)
|
||||
}
|
||||
})
|
||||
const primaryDatasets = datasets.filter(
|
||||
(dataset) => !dataset.temporal_series_key || latestDatasetBySeries.get(dataset.temporal_series_key)?.id === dataset.id,
|
||||
)
|
||||
const historicalDatasets = datasets.filter(
|
||||
(dataset) => dataset.temporal_series_key && latestDatasetBySeries.get(dataset.temporal_series_key)?.id !== dataset.id,
|
||||
)
|
||||
const readyDatasets = primaryDatasets.filter((dataset) => dataset.status === 'ready').length
|
||||
const roleSummaries = [
|
||||
{
|
||||
key: 'selected',
|
||||
@@ -120,21 +152,21 @@ export function DatasetPanel({
|
||||
{
|
||||
key: 'reference',
|
||||
label: 'Referentie',
|
||||
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'reference').length,
|
||||
count: primaryDatasets.filter((dataset) => normalizeDatasetRole(dataset) === 'reference').length,
|
||||
hint: 'Officieel',
|
||||
className: 'dataset-role-reference',
|
||||
},
|
||||
{
|
||||
key: 'candidate',
|
||||
label: 'Resultaat',
|
||||
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'candidate').length,
|
||||
count: primaryDatasets.filter((dataset) => normalizeDatasetRole(dataset) === 'candidate').length,
|
||||
hint: 'Afgeleid',
|
||||
className: 'dataset-role-candidate',
|
||||
},
|
||||
{
|
||||
key: 'source',
|
||||
label: 'Eigen bron',
|
||||
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'source').length,
|
||||
label: 'Basisbron',
|
||||
count: primaryDatasets.filter((dataset) => normalizeDatasetRole(dataset) === 'source').length,
|
||||
hint: 'Ingeladen',
|
||||
className: 'dataset-role-source',
|
||||
},
|
||||
@@ -147,7 +179,7 @@ export function DatasetPanel({
|
||||
<p className="eyebrow">Ingeladen gegevens</p>
|
||||
<h2>Bronnen</h2>
|
||||
</div>
|
||||
<span className="count-pill">{readyDatasets}/{datasets.length} beschikbaar</span>
|
||||
<span className="count-pill">{readyDatasets}/{primaryDatasets.length} bronnen beschikbaar</span>
|
||||
</div>
|
||||
|
||||
<div className="data-selection-summary data-selection-summary-dataset">
|
||||
@@ -165,17 +197,17 @@ export function DatasetPanel({
|
||||
value={datasetForm.datasetType}
|
||||
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, datasetType: event.target.value }))}
|
||||
>
|
||||
<option value="vector">vector</option>
|
||||
<option value="geojson">geojson</option>
|
||||
<option value="raster">raster</option>
|
||||
<option value="vector">Kaartlaag</option>
|
||||
<option value="geojson">GeoJSON-kaartlaag</option>
|
||||
<option value="raster">Luchtbeeld of raster</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Bronnaam
|
||||
Naam van de bron
|
||||
<input
|
||||
value={datasetForm.source}
|
||||
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, source: event.target.value }))}
|
||||
placeholder="user_upload"
|
||||
placeholder="bijvoorbeeld eigen luchtbeeld"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
@@ -226,7 +258,7 @@ export function DatasetPanel({
|
||||
<div className="data-panel-list-block dataset-catalog-block">
|
||||
<p className="data-section-label">Beschikbare bronnen</p>
|
||||
<ul className="dataset-list">
|
||||
{datasets.map((dataset) => {
|
||||
{primaryDatasets.map((dataset) => {
|
||||
const datasetRole = normalizeDatasetRole(dataset)
|
||||
const roleLabel = datasetRoleLabel(datasetRole)
|
||||
return (
|
||||
@@ -235,12 +267,12 @@ export function DatasetPanel({
|
||||
<div>
|
||||
<div className="dataset-card-kicker">
|
||||
<span className={`dataset-role-badge dataset-role-${datasetRole}`}>{roleLabel}</span>
|
||||
<span>{dataset.dataset_type}</span>
|
||||
<span>{datasetKindLabel(dataset)}</span>
|
||||
</div>
|
||||
<strong className="dataset-card-title">{getDatasetDisplayName(dataset)}</strong>
|
||||
<div className="entity-meta dataset-card-primary-meta">
|
||||
<span>{(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten</span>
|
||||
<span>{dataset.source_name ?? dataset.source}</span>
|
||||
<span>{getDatasetSourceDisplayName(dataset)} · {formatObservationDate(dataset.observed_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className={dataset.status === 'ready' ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
@@ -305,6 +337,28 @@ export function DatasetPanel({
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
{historicalDatasets.length > 0 ? (
|
||||
<details className="dataset-history-disclosure">
|
||||
<summary>
|
||||
<span>Historische meetmomenten</span>
|
||||
<strong>{historicalDatasets.length} oudere lagen</strong>
|
||||
</summary>
|
||||
<ul className="dataset-history-list">
|
||||
{historicalDatasets.map((dataset) => (
|
||||
<li key={dataset.id}>
|
||||
<div>
|
||||
<strong>{getDatasetDisplayName(dataset)}</strong>
|
||||
<span>{formatObservationDate(dataset.observed_at)} · {(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten</span>
|
||||
</div>
|
||||
<div className="panel-action-row">
|
||||
<button className="secondary-action" type="button" onClick={() => onOpenDatasetInMap(dataset)}>Kaart</button>
|
||||
<button className="secondary-action" type="button" onClick={() => onOpenDatasetExport(dataset)}>Downloaden</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</details>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -16,6 +16,13 @@ import { DETECTION_OPERATOR_PROFILES, type DetectionOperatorProfile } from './de
|
||||
const DETECTION_PAGE_SIZE_OPTIONS = [25, 50, 100] as const
|
||||
const DEFAULT_DETECTION_PAGE_SIZE = 50
|
||||
|
||||
function detectionModelLabel(model: DetectionModelCapability): string {
|
||||
if (model.model_id === 'yolo-configured') return 'Lokaal gebouwmodel'
|
||||
if (model.model_id === 'manual-fixture-detector') return 'Testmodel (alleen voor demo)'
|
||||
if (model.model_id === 'yolo-placeholder') return 'Gebouwmodel nog niet geconfigureerd'
|
||||
return model.display_name
|
||||
}
|
||||
|
||||
interface CalibrationRow {
|
||||
analysisRunId: string
|
||||
qualityCheckId: string
|
||||
@@ -272,7 +279,7 @@ export function DetectionLab({
|
||||
<ul className="model-list">
|
||||
{detectionModels.map((model) => (
|
||||
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
|
||||
<strong>{model.display_name}</strong>
|
||||
<strong>{detectionModelLabel(model)}</strong>
|
||||
<span className={model.configured ? 'status-badge status-badge-ready' : 'status-badge'}>{model.status}</span>
|
||||
<div className="entity-meta">
|
||||
<span>{model.model_id}</span>
|
||||
@@ -560,7 +567,7 @@ export function DetectionLab({
|
||||
<select value={selectedDetectionModelId} onChange={(event) => onSelectModel(event.target.value)}>
|
||||
{detectionModels.map((model) => (
|
||||
<option key={model.model_id} value={model.model_id}>
|
||||
{model.display_name}
|
||||
{detectionModelLabel(model)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -587,7 +594,7 @@ export function DetectionLab({
|
||||
Beeldtegelbestand
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Raster tile manifest path"
|
||||
placeholder="Pad naar de aangemaakte beeldtegels"
|
||||
value={detectionTileManifestPath}
|
||||
onChange={(event) => onSetTileManifestPath(event.target.value)}
|
||||
/>
|
||||
@@ -609,17 +616,17 @@ export function DetectionLab({
|
||||
<div className="ai-lab-state-stack">
|
||||
{detectionRunError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>Detection run failed.</strong>
|
||||
<strong>De beeldanalyse is mislukt.</strong>
|
||||
<p>{detectionRunError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{detectionRunResult ? (
|
||||
<div className="result-summary-card">
|
||||
<p>Status: {detectionRunResult.status}</p>
|
||||
<p>Message: {detectionRunResult.message}</p>
|
||||
<p>Analysis run: {detectionRunResult.analysis_run_id}</p>
|
||||
<p>Job: {detectionRunResult.job_id}</p>
|
||||
<p>Detections: {detectionRunResult.detection_count}</p>
|
||||
<p>Uitleg: {detectionRunResult.message}</p>
|
||||
<p>Analyse: {detectionRunResult.analysis_run_id}</p>
|
||||
<p>Verwerking: {detectionRunResult.job_id}</p>
|
||||
<p>Gevonden objecten: {detectionRunResult.detection_count}</p>
|
||||
{detectionRunResult.error_code ? <p className="error">Code: {detectionRunResult.error_code}</p> : null}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -765,7 +772,7 @@ export function DetectionLab({
|
||||
<option value="">Kies een bewaarde analyse</option>
|
||||
{detectionRuns.map((run) => (
|
||||
<option key={run.id} value={run.id}>
|
||||
{run.model_name || 'detection'} - {run.status} - {run.id}
|
||||
{run.model_name || 'Gebouwdetectie'} · {run.status} · {run.id}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -774,7 +781,7 @@ export function DetectionLab({
|
||||
Type object
|
||||
<input
|
||||
type="text"
|
||||
placeholder="bijvoorbeeld building"
|
||||
placeholder="bijvoorbeeld gebouw"
|
||||
value={detectionClassFilter}
|
||||
onChange={(event) => onSetClassFilter(event.target.value)}
|
||||
/>
|
||||
@@ -811,10 +818,10 @@ export function DetectionLab({
|
||||
<div className="pagination-toolbar" aria-label="Detection result pagination">
|
||||
<p className="pagination-summary" aria-live="polite">
|
||||
<strong>{detectionPageStart + 1}-{detectionPageEnd}</strong>
|
||||
<span>of {detectionItems.length}</span>
|
||||
<span>van {detectionItems.length}</span>
|
||||
</p>
|
||||
<label className="pagination-page-size">
|
||||
Rows
|
||||
Rijen
|
||||
<select
|
||||
value={detectionPageSize}
|
||||
onChange={(event) => {
|
||||
@@ -831,19 +838,19 @@ export function DetectionLab({
|
||||
<button
|
||||
className="secondary-action pagination-button"
|
||||
type="button"
|
||||
aria-label="Previous detection results page"
|
||||
title="Previous page"
|
||||
aria-label="Vorige resultatenpagina"
|
||||
title="Vorige pagina"
|
||||
disabled={currentDetectionPage <= 1}
|
||||
onClick={() => setDetectionResultPage(currentDetectionPage - 1)}
|
||||
>
|
||||
{'<'}
|
||||
</button>
|
||||
<span>Page {currentDetectionPage} of {detectionPageCount}</span>
|
||||
<span>Pagina {currentDetectionPage} van {detectionPageCount}</span>
|
||||
<button
|
||||
className="secondary-action pagination-button"
|
||||
type="button"
|
||||
aria-label="Next detection results page"
|
||||
title="Next page"
|
||||
aria-label="Volgende resultatenpagina"
|
||||
title="Volgende pagina"
|
||||
disabled={currentDetectionPage >= detectionPageCount}
|
||||
onClick={() => setDetectionResultPage(currentDetectionPage + 1)}
|
||||
>
|
||||
@@ -855,10 +862,10 @@ export function DetectionLab({
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Class</th>
|
||||
<th>Confidence</th>
|
||||
<th>Type</th>
|
||||
<th>Zekerheid</th>
|
||||
<th>Model</th>
|
||||
<th>Source tile</th>
|
||||
<th>Beeldtegel</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -879,34 +886,40 @@ export function DetectionLab({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="ai-lab-results-surface calibration-comparison-surface" aria-label="Detection calibration comparison">
|
||||
<details className="secondary-analysis-disclosure detection-technical-evaluation">
|
||||
<summary>
|
||||
<span>Technische modelevaluatie</span>
|
||||
<strong>{calibrationRows.length > 0 || detectionQaResult ? 'resultaten beschikbaar' : 'optioneel'}</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-results-surface calibration-comparison-surface" aria-label="Vergelijking modelkalibratie">
|
||||
<div className="panel-title-row">
|
||||
<div>
|
||||
<h3>Calibration comparison</h3>
|
||||
<p className="muted">Compare persisted detection runs by confidence threshold before promoting a model setting.</p>
|
||||
<h3>Kalibraties vergelijken</h3>
|
||||
<p className="muted">Vergelijk bewaarde analyseruns per zekerheidsdrempel voordat een modelinstelling wordt goedgekeurd.</p>
|
||||
</div>
|
||||
<span className="count-pill">{calibrationRows.length} rows</span>
|
||||
<span className="count-pill">{calibrationRows.length} resultaten</span>
|
||||
</div>
|
||||
{calibrationRows.length > 0 ? (
|
||||
<>
|
||||
<div className="calibration-summary-grid" aria-label="Calibration comparison winners">
|
||||
<CalibrationSummaryCard title="Best F1 candidate" row={bestF1Candidate} metric="f1" />
|
||||
<CalibrationSummaryCard title="Best precision candidate" row={bestPrecisionCandidate} metric="precision" />
|
||||
<CalibrationSummaryCard title="Lowest false-positive pressure" row={lowestFalsePositivePressureCandidate} metric="falsePositives" />
|
||||
<CalibrationSummaryCard title="Beste F1-score" row={bestF1Candidate} metric="f1" />
|
||||
<CalibrationSummaryCard title="Beste precisie" row={bestPrecisionCandidate} metric="precision" />
|
||||
<CalibrationSummaryCard title="Minste foutieve meldingen" row={lowestFalsePositivePressureCandidate} metric="falsePositives" />
|
||||
</div>
|
||||
<div className="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Threshold</th>
|
||||
<th>Drempel</th>
|
||||
<th>Model</th>
|
||||
<th>Detections</th>
|
||||
<th>Precision</th>
|
||||
<th>Objecten</th>
|
||||
<th>Precisie</th>
|
||||
<th>Recall</th>
|
||||
<th>F1</th>
|
||||
<th>False positives</th>
|
||||
<th>False negatives</th>
|
||||
<th>Quality check</th>
|
||||
<th>Fout positief</th>
|
||||
<th>Fout negatief</th>
|
||||
<th>Kwaliteitscontrole</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -915,7 +928,7 @@ export function DetectionLab({
|
||||
<td>{formatNullableNumber(row.threshold, 2)}</td>
|
||||
<td>
|
||||
<strong>{row.modelName}</strong>
|
||||
<span className="table-subtle">{row.modelAssetId ?? 'runtime configured path'}</span>
|
||||
<span className="table-subtle">{row.modelAssetId ?? 'geconfigureerd lokaal model'}</span>
|
||||
</td>
|
||||
<td>{row.detectionCount ?? 'n/a'}</td>
|
||||
<td>{formatNullableNumber(row.precision, 3)}</td>
|
||||
@@ -930,24 +943,24 @@ export function DetectionLab({
|
||||
</table>
|
||||
</div>
|
||||
<div className="lab-action-guardrail">
|
||||
<span>Promotion guardrail</span>
|
||||
<strong>Promote only after checking evidence across AOIs, false positives and false negatives.</strong>
|
||||
<span>Voorwaarde voor goedkeuring</span>
|
||||
<strong>Keur pas goed nadat meerdere gebieden en de foutieve positieve en negatieve resultaten zijn gecontroleerd.</strong>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>No calibration comparison available yet.</strong>
|
||||
<p>Run configured YOLO at multiple confidence thresholds, then compare each persisted detection run against the same reference dataset.</p>
|
||||
<strong>Nog geen kalibratievergelijking beschikbaar.</strong>
|
||||
<p>Voer het lokale model met meerdere zekerheidsdrempels uit en vergelijk de resultaten met dezelfde referentielaag.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="ai-lab-qa-surface" aria-label="Detection QA controls and results">
|
||||
<h3>Detection QA</h3>
|
||||
<div className="ai-lab-qa-surface" aria-label="Kwaliteitscontrole gebouwdetectie">
|
||||
<h3>Kwaliteitscontrole gebouwdetectie</h3>
|
||||
<label>
|
||||
Reference dataset
|
||||
Referentielaag
|
||||
<select value={detectionReferenceDatasetId} onChange={(event) => onSelectReferenceDataset(event.target.value)}>
|
||||
<option value="">Select reference dataset</option>
|
||||
<option value="">Kies een referentielaag</option>
|
||||
{referenceDatasets.map((dataset) => (
|
||||
<option key={dataset.id} value={dataset.id}>
|
||||
{dataset.name}
|
||||
@@ -956,24 +969,24 @@ export function DetectionLab({
|
||||
</select>
|
||||
</label>
|
||||
<button className="primary-action" type="button" onClick={onRunQa} disabled={runningDetectionQa || !selectedDetectionRunId || !detectionReferenceDatasetId}>
|
||||
Compare detections to reference
|
||||
Vergelijk met referentielaag
|
||||
</button>
|
||||
{detectionQaError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>Detection QA failed.</strong>
|
||||
<strong>De kwaliteitscontrole is mislukt.</strong>
|
||||
<p>{detectionQaError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{detectionQaResult ? (
|
||||
<div className="result-summary-card">
|
||||
<p>Status: {detectionQaResult.status}</p>
|
||||
<p>Quality check: {detectionQaResult.quality_check_id}</p>
|
||||
<p>Precision: {detectionQaResult.precision?.toFixed(3) ?? 'n/a'}</p>
|
||||
<p>Kwaliteitscontrole: {detectionQaResult.quality_check_id}</p>
|
||||
<p>Precisie: {detectionQaResult.precision?.toFixed(3) ?? 'n.v.t.'}</p>
|
||||
<p>Recall: {detectionQaResult.recall?.toFixed(3) ?? 'n/a'}</p>
|
||||
<p>F1: {detectionQaResult.f1_score?.toFixed(3) ?? 'n/a'}</p>
|
||||
<p>Mean IoU: {detectionQaResult.mean_iou?.toFixed(3) ?? 'n/a'}</p>
|
||||
<p>False positives: {detectionQaResult.false_positives}</p>
|
||||
<p>False negatives: {detectionQaResult.false_negatives}</p>
|
||||
<p>Gemiddelde overlap: {detectionQaResult.mean_iou?.toFixed(3) ?? 'n.v.t.'}</p>
|
||||
<p>Fout positief: {detectionQaResult.false_positives}</p>
|
||||
<p>Fout negatief: {detectionQaResult.false_negatives}</p>
|
||||
{detectionQaResult.coverage ? (
|
||||
<div className="detection-qa-diagnostic">
|
||||
<span>Inference coverage</span>
|
||||
@@ -1004,6 +1017,8 @@ export function DetectionLab({
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -326,27 +326,27 @@ export function ExportCenter({
|
||||
<div className="export-state-stack">
|
||||
{loadingExports ? (
|
||||
<div className="result-state result-state-loading">
|
||||
<strong>Loading export registry.</strong>
|
||||
<p>Retrieving persisted artifacts for the selected project.</p>
|
||||
<strong>Downloads laden.</strong>
|
||||
<p>De bewaarde bestanden van deze werkruimte worden opgehaald.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{exportError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>Export action failed.</strong>
|
||||
<strong>Download kon niet worden gemaakt.</strong>
|
||||
<p>{exportError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{latestExport ? (
|
||||
<div className="latest-export-card">
|
||||
<span>Latest export</span>
|
||||
<span>Laatste download</span>
|
||||
<strong>{formatExportType(latestExport.export_type)}</strong>
|
||||
<p>{latestExport.path}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{exports.length === 0 ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>No exports registered yet.</strong>
|
||||
<p>Create metadata, GeoJSON or HTML report artifacts once the active project has data to hand off.</p>
|
||||
<strong>Nog geen downloads gemaakt.</strong>
|
||||
<p>Kies hierboven een leesbaar rapport, projectoverzicht of kaartlaag.</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1049,7 +1049,7 @@ export function MapWorkspace({
|
||||
) : null}
|
||||
|
||||
<label className="geo-scope-select">
|
||||
Gemeente of volledige regio
|
||||
Snel naar een gemeente (optioneel)
|
||||
<select aria-label="Werkgebied" value={selectedMapAreaId} onChange={(event) => handleSelectMapArea(event.target.value)} disabled={areas.length === 0}>
|
||||
{areas.map((area) => (
|
||||
<option key={area.id} value={area.id}>{area.name}</option>
|
||||
|
||||
@@ -1529,6 +1529,7 @@ details.ai-lab-model-surface > summary strong {
|
||||
|
||||
.technical-run-list,
|
||||
.area-catalog-disclosure,
|
||||
.dataset-history-disclosure,
|
||||
.dataset-technical-details,
|
||||
.quality-advanced-disclosure,
|
||||
.provider-technical-details,
|
||||
@@ -1543,6 +1544,7 @@ details.ai-lab-model-surface > summary strong {
|
||||
|
||||
.technical-run-list > summary,
|
||||
.area-catalog-disclosure > summary,
|
||||
.dataset-history-disclosure > summary,
|
||||
.dataset-technical-details > summary,
|
||||
.quality-advanced-disclosure > summary,
|
||||
.provider-technical-details > summary,
|
||||
@@ -1564,6 +1566,7 @@ details.ai-lab-model-surface > summary strong {
|
||||
|
||||
.technical-run-list > summary::-webkit-details-marker,
|
||||
.area-catalog-disclosure > summary::-webkit-details-marker,
|
||||
.dataset-history-disclosure > summary::-webkit-details-marker,
|
||||
.dataset-technical-details > summary::-webkit-details-marker,
|
||||
.quality-advanced-disclosure > summary::-webkit-details-marker,
|
||||
.provider-technical-details > summary::-webkit-details-marker,
|
||||
@@ -1575,6 +1578,7 @@ details.ai-lab-model-surface > summary strong {
|
||||
|
||||
.technical-run-list > summary::after,
|
||||
.area-catalog-disclosure > summary::after,
|
||||
.dataset-history-disclosure > summary::after,
|
||||
.dataset-technical-details > summary::after,
|
||||
.quality-advanced-disclosure > summary::after,
|
||||
.provider-technical-details > summary::after,
|
||||
@@ -1589,6 +1593,7 @@ details.ai-lab-model-surface > summary strong {
|
||||
|
||||
.technical-run-list[open] > summary::after,
|
||||
.area-catalog-disclosure[open] > summary::after,
|
||||
.dataset-history-disclosure[open] > summary::after,
|
||||
.dataset-technical-details[open] > summary::after,
|
||||
.quality-advanced-disclosure[open] > summary::after,
|
||||
.provider-technical-details[open] > summary::after,
|
||||
@@ -1645,6 +1650,56 @@ details.ai-lab-model-surface > summary strong {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.dataset-history-disclosure {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.dataset-history-disclosure > summary strong {
|
||||
margin-right: 0.25rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.dataset-history-list {
|
||||
display: grid;
|
||||
max-height: 24rem;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
padding: 0.5rem;
|
||||
list-style: none;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.dataset-history-list li {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid var(--line);
|
||||
padding: 0.65rem 0.25rem;
|
||||
}
|
||||
|
||||
.dataset-history-list li:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.dataset-history-list li > div:first-child {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.dataset-history-list li span {
|
||||
color: var(--muted);
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
|
||||
.detection-technical-evaluation {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.ai-user-summary {
|
||||
order: 0;
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user