Close full operational audit findings
This commit is contained in:
@@ -12,17 +12,11 @@ import type {
|
||||
} from '../../types'
|
||||
import type { DetectionCalibrationRunRow, DetectionWorkflowStage } from '../../hooks/useDetectionWorkflow'
|
||||
import { DETECTION_OPERATOR_PROFILES, type DetectionOperatorProfile } from './detectionProfiles'
|
||||
import { DetectionModelManagement, detectionModelLabel } from './DetectionModelManagement'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function detectionQualityInterpretation(f1: number | null | undefined): string {
|
||||
if (typeof f1 !== 'number' || !Number.isFinite(f1)) return 'Nog geen gevalideerde kwaliteitsmeting.'
|
||||
if (f1 >= 0.85) return 'Sterk resultaat; steekproefcontrole blijft vereist.'
|
||||
@@ -31,6 +25,18 @@ function detectionQualityInterpretation(f1: number | null | undefined): string {
|
||||
return 'Onvoldoende betrouwbaar voor operationeel gebruik.'
|
||||
}
|
||||
|
||||
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', {
|
||||
dateStyle: 'short',
|
||||
timeStyle: 'short',
|
||||
}).format(new Date(timestamp))
|
||||
: 'datum onbekend'
|
||||
return `${run.model_name || 'Gebouwdetectie'} · ${status} · ${dateLabel}`
|
||||
}
|
||||
|
||||
interface CalibrationRow {
|
||||
analysisRunId: string
|
||||
qualityCheckId: string
|
||||
@@ -284,255 +290,29 @@ export function DetectionLab({
|
||||
{detectionQualityInterpretation(selectedOperatorProfile?.f1)}
|
||||
</p>
|
||||
|
||||
<details className="ai-lab-model-surface" aria-label="Detection model capabilities">
|
||||
<summary>
|
||||
<span>Technische modelinformatie</span>
|
||||
<strong>{detectionModels.length} registraties</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-state-stack">
|
||||
{loadingDetectionModels ? (
|
||||
<div className="result-state result-state-loading">
|
||||
<strong>Loading detection models.</strong>
|
||||
<p>Checking backend model registry availability.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{detectionModelError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>Detection model registry unavailable.</strong>
|
||||
<p>{detectionModelError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{modelAssetError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>Local model assets unavailable.</strong>
|
||||
<p>{modelAssetError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{detectionModels.length === 0 && !loadingDetectionModels ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>No detection models reported by backend.</strong>
|
||||
<p>Refresh models after the backend is reachable.</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<ul className="model-list">
|
||||
{detectionModels.map((model) => (
|
||||
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
|
||||
<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>
|
||||
<span>{model.framework}</span>
|
||||
<span>{model.task_type}</span>
|
||||
</div>
|
||||
<p className="muted">classes: {model.supported_classes.join(', ')}</p>
|
||||
<p className="muted">{model.limitation_message}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{selectedDetectionModelId === 'yolo-configured' ? (
|
||||
<details className="ai-lab-model-surface" aria-label="Local model asset selection">
|
||||
<summary>
|
||||
<span>Modelkeuze voor beheerders</span>
|
||||
<strong>{selectedOperatorProfile?.displayName ?? selectedModelAsset?.display_name ?? 'Geen lokaal model'}</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>Lokaal modelbestand</h3>
|
||||
<p>GeoIntel kiest automatisch het actieve lokale model. Een beheerder kan hier bewust een ander reeds aanwezig, alleen-lezen modelbestand kiezen.</p>
|
||||
</div>
|
||||
<span className={selectedModelAsset ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{selectedModelAsset ? 'model gekozen' : 'geen model gekozen'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="model-asset-guidance">
|
||||
<strong>Gevalideerde profielen</strong>
|
||||
<p>
|
||||
Een profiel koppelt een lokaal model aan een gemeten zekerheidsdrempel. Een andere keuze geldt alleen voor de huidige analyse en wijzigt de serverconfiguratie niet.
|
||||
</p>
|
||||
</div>
|
||||
<div className="operator-profile-grid" aria-label="Configured YOLO operator profiles">
|
||||
{DETECTION_OPERATOR_PROFILES.map((profile) => {
|
||||
const profileAsset = modelAssets.find((asset) => asset.model_asset_id === profile.modelAssetId)
|
||||
const profileSelected =
|
||||
selectedModelAssetId === profile.modelAssetId &&
|
||||
Math.abs(detectionConfidenceThreshold - profile.confidenceThreshold) < 0.0001
|
||||
return (
|
||||
<div
|
||||
className={profileSelected ? 'operator-profile-card operator-profile-card-selected' : 'operator-profile-card'}
|
||||
key={profile.id}
|
||||
>
|
||||
<div className="operator-profile-card-header">
|
||||
<strong>{profile.displayName}</strong>
|
||||
<span className={profile.defaultApproved ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{profile.defaultApproved ? 'standaardprofiel' : 'kandidaat · extra controle vereist'}
|
||||
</span>
|
||||
</div>
|
||||
<p>{profile.description}</p>
|
||||
<div className="operator-profile-metrics">
|
||||
<span>drempel {profile.confidenceThreshold.toFixed(2)}</span>
|
||||
<span>precision {profile.precision.toFixed(3)}</span>
|
||||
<span>recall {profile.recall.toFixed(3)}</span>
|
||||
<span>F1 {profile.f1.toFixed(3)}</span>
|
||||
<span>testgebieden {profile.positiveSampleCount}</span>
|
||||
<span>max. achtergrondfouten {profile.maxBackgroundDetections}</span>
|
||||
</div>
|
||||
<div className="entity-meta">
|
||||
<span>modelbestand: {profile.modelAssetId}</span>
|
||||
<span>beoordeling: {profile.promotionRecommendation}</span>
|
||||
<span>beschikbaar: {profileAsset ? 'ja' : 'niet gekoppeld'}</span>
|
||||
</div>
|
||||
<p className="field-guidance">{profile.limitationMessage}</p>
|
||||
<button
|
||||
className="secondary-action"
|
||||
type="button"
|
||||
onClick={() => onApplyOperatorProfile(profile)}
|
||||
disabled={!profileAsset}
|
||||
>
|
||||
Profiel gebruiken
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{selectedModelAsset ? (
|
||||
<div className="model-asset-guidance">
|
||||
<strong>Status gekozen model</strong>
|
||||
<p>
|
||||
{selectedModelAsset.display_name} wordt voor deze analyse gebruikt. De standaard serverconfiguratie blijft ongewijzigd.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<label>
|
||||
Lokaal modelbestand
|
||||
<select value={selectedModelAssetId} onChange={(event) => onSelectModelAsset(event.target.value)}>
|
||||
<option value="">Kies een lokaal modelbestand</option>
|
||||
{modelAssets.map((asset) => (
|
||||
<option key={asset.model_asset_id} value={asset.model_asset_id}>
|
||||
{asset.display_name} {asset.active ? '(active)' : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
{modelAssets.length === 0 && !loadingDetectionModels ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>Geen lokaal modelbestand gevonden.</strong>
|
||||
<p>Plaats een gecontroleerd model in de modelmap of configureer het bestaande YOLO-modelpad.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedModelAsset ? (
|
||||
<div className="result-summary-card">
|
||||
<p>File: {selectedModelAsset.filename}</p>
|
||||
<p>Status: {selectedModelAsset.status}</p>
|
||||
<p>Active runtime env model: {selectedModelAsset.active ? 'yes' : 'no'}</p>
|
||||
<p>will_download_models: {selectedModelAsset.will_download_models ? 'yes' : 'no'}</p>
|
||||
<p>Size: {formatModelAssetSize(selectedModelAsset.size_bytes)}</p>
|
||||
<p>SHA-256: {selectedModelAsset.sha256.slice(0, 12)}</p>
|
||||
<p>Path: {selectedModelAsset.model_path}</p>
|
||||
<p>{selectedModelAsset.limitation_message}</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</details>
|
||||
) : null}
|
||||
|
||||
<details className="ai-lab-model-surface" aria-label="YOLO runtime preflight">
|
||||
<summary>
|
||||
<span>Technische runtimecontrole</span>
|
||||
<strong>{yoloRuntimeReady ? 'gereed' : yoloPreflight?.status ?? 'niet geladen'}</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>YOLO runtime preflight</h3>
|
||||
<p>Read-only runtime status. This does not load a model, run inference or download weights.</p>
|
||||
</div>
|
||||
<button className="secondary-action" type="button" onClick={onRefreshYoloPreflight} disabled={loadingYoloPreflight}>
|
||||
Refresh preflight
|
||||
</button>
|
||||
</div>
|
||||
<div className="ai-lab-state-stack">
|
||||
{loadingYoloPreflight ? (
|
||||
<div className="result-state result-state-loading">
|
||||
<strong>Loading YOLO preflight.</strong>
|
||||
<p>Checking backend runtime configuration and optional dependency visibility.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{yoloPreflightError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>YOLO preflight unavailable.</strong>
|
||||
<p>{yoloPreflightError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{!yoloPreflight && !loadingYoloPreflight && !yoloPreflightError ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>No YOLO preflight loaded.</strong>
|
||||
<p>Refresh preflight to inspect the live backend AI runtime before running configured YOLO.</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{yoloPreflight ? (
|
||||
<div className={yoloPreflight.status === 'ready' ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}>
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>Status: {yoloPreflight.status}</h3>
|
||||
<p>{yoloPreflight.message}</p>
|
||||
</div>
|
||||
<span className={yoloPreflight.status === 'ready' ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{yoloPreflight.checks.dependencies_available ? 'dependencies visible' : 'not ready'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="lab-readiness-grid">
|
||||
<div className={yoloPreflight.checks.enabled ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>YOLO enabled</span>
|
||||
<strong>{yoloPreflight.checks.enabled ? 'true' : 'false'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.checks.dependencies_available ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Dependencies</span>
|
||||
<strong>{yoloPreflight.checks.dependencies_available === true ? 'available' : yoloPreflight.checks.dependencies_available === false ? 'unavailable' : 'not checked'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.checks.model_file_exists ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Local model file</span>
|
||||
<strong>{yoloPreflight.checks.model_file_exists === true ? 'found' : yoloPreflight.checks.model_path_set ? 'missing' : 'not configured'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.runtime.cuda_available ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>CUDA</span>
|
||||
<strong>{yoloPreflight.runtime.cuda_available === true ? 'available' : yoloPreflight.runtime.cuda_available === false ? 'not available' : 'not checked'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.checks.manifest_valid ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Tile manifest validation</span>
|
||||
<strong>{yoloPreflight.checks.manifest_valid === true ? 'valid' : yoloPreflight.checks.manifest_path_set ? 'not valid' : 'not provided'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.tile_count > 0 ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Tile count</span>
|
||||
<strong>{yoloPreflight.tile_count} / {yoloPreflight.max_tiles}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="entity-meta">
|
||||
<span>torch_version: {yoloPreflight.runtime.torch_version ?? 'n/a'}</span>
|
||||
<span>ultralytics_version: {yoloPreflight.runtime.ultralytics_version ?? 'n/a'}</span>
|
||||
<span>cuda_available: {String(yoloPreflight.runtime.cuda_available ?? 'unknown')}</span>
|
||||
<span>will_run_inference: {String(yoloPreflight.will_run_inference)}</span>
|
||||
<span>YOLO_CONFIG_DIR: {yoloPreflight.runtime.yolo_config_dir ?? 'n/a'}</span>
|
||||
<span>model directory: {yoloPreflight.runtime.model_directory ?? 'n/a'}</span>
|
||||
<span>model_asset_id: {yoloPreflight.model_asset_id ?? 'n/a'}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</details>
|
||||
<DetectionModelManagement
|
||||
detectionModels={detectionModels}
|
||||
modelAssets={modelAssets}
|
||||
loadingDetectionModels={loadingDetectionModels}
|
||||
detectionModelError={detectionModelError}
|
||||
modelAssetError={modelAssetError}
|
||||
selectedDetectionModelId={selectedDetectionModelId}
|
||||
selectedModelAssetId={selectedModelAssetId}
|
||||
detectionConfidenceThreshold={detectionConfidenceThreshold}
|
||||
yoloPreflight={yoloPreflight}
|
||||
loadingYoloPreflight={loadingYoloPreflight}
|
||||
yoloPreflightError={yoloPreflightError}
|
||||
onRefreshYoloPreflight={onRefreshYoloPreflight}
|
||||
onSelectModelAsset={onSelectModelAsset}
|
||||
onApplyOperatorProfile={onApplyOperatorProfile}
|
||||
/>
|
||||
|
||||
<div className="lab-block">
|
||||
<div className="ai-lab-run-surface" aria-label="Detection run controls">
|
||||
<div className="ai-lab-run-surface" aria-label="Gebouwdetectie starten">
|
||||
<h3>Nieuwe beeldanalyse</h3>
|
||||
<div
|
||||
className={guidedDetectionReady ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}
|
||||
aria-label="Detection run readiness"
|
||||
aria-label="Startklaar voor gebouwdetectie"
|
||||
>
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
@@ -720,17 +500,22 @@ export function DetectionLab({
|
||||
) : null}
|
||||
{detectionRunResult ? (
|
||||
<div className="result-summary-card">
|
||||
<p>Status: {detectionRunResult.status}</p>
|
||||
<p>Uitleg: {detectionRunResult.message}</p>
|
||||
<p>Analyse: {detectionRunResult.analysis_run_id}</p>
|
||||
<p>Verwerking: {detectionRunResult.job_id}</p>
|
||||
<p>Status: {detectionRunResult.status === 'completed' ? 'afgerond' : detectionRunResult.status}</p>
|
||||
<p>{detectionRunResult.message}</p>
|
||||
<p>Gevonden objecten: {detectionRunResult.detection_count}</p>
|
||||
{detectionRunResult.error_code ? <p className="error">Code: {detectionRunResult.error_code}</p> : null}
|
||||
<details className="technical-inline-details">
|
||||
<summary>Technische verwerking</summary>
|
||||
<div className="entity-meta">
|
||||
<span>Analyserun-ID: {detectionRunResult.analysis_run_id}</span>
|
||||
<span>Taak-ID: {detectionRunResult.job_id}</span>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<details className="ai-lab-model-surface guided-calibration-surface" aria-label="Guided calibration runner">
|
||||
<details className="ai-lab-model-surface guided-calibration-surface" aria-label="Modelkalibratie voor beheerders">
|
||||
<summary>
|
||||
<span>Modelkalibratie voor beheerders</span>
|
||||
<strong>{detectionCalibrationRows.length > 0 ? `${detectionCalibrationRows.length} drempels getest` : 'gesloten'}</strong>
|
||||
@@ -738,28 +523,28 @@ export function DetectionLab({
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>Guided calibration runner</h3>
|
||||
<p>This runs real configured YOLO jobs and QA comparisons for each threshold. It does not promote or mutate model files.</p>
|
||||
<h3>Zekerheidsdrempels vergelijken</h3>
|
||||
<p>Voert het lokale model en een kwaliteitscontrole uit voor iedere drempel. Modelbestanden worden niet gewijzigd.</p>
|
||||
</div>
|
||||
<span className={calibrationRunReady ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{calibrationRunReady ? 'ready' : 'needs dataset, model, manifest and reference'}
|
||||
{calibrationRunReady ? 'startklaar' : 'luchtbeeld, model, beeldtegels en referentie vereist'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="lab-form-grid">
|
||||
<label>
|
||||
Threshold set
|
||||
Zekerheidsdrempels
|
||||
<input
|
||||
type="text"
|
||||
value={calibrationThresholdText}
|
||||
onChange={(event) => onSetCalibrationThresholdText(event.target.value)}
|
||||
placeholder="0.50 0.25 0.15"
|
||||
/>
|
||||
<span className="field-guidance">Use spaces, commas or semicolons. Values must be between 0 and 1.</span>
|
||||
<span className="field-guidance">Scheid waarden met spaties, komma's of puntkomma's. Iedere waarde ligt tussen 0 en 1.</span>
|
||||
</label>
|
||||
<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}
|
||||
@@ -774,30 +559,30 @@ export function DetectionLab({
|
||||
onClick={onRunCalibration}
|
||||
disabled={runningDetectionCalibration || !calibrationRunReady}
|
||||
>
|
||||
Run calibration sweep
|
||||
Drempels vergelijken
|
||||
</button>
|
||||
{detectionCalibrationError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>Calibration sweep failed.</strong>
|
||||
<strong>De kalibratievergelijking is mislukt.</strong>
|
||||
<p>{detectionCalibrationError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{detectionCalibrationRows.length > 0 ? (
|
||||
<div className="calibration-progress-panel" aria-label="Calibration run progress">
|
||||
<div className="calibration-progress-panel" aria-label="Voortgang modelkalibratie">
|
||||
<div className="panel-title-row">
|
||||
<div>
|
||||
<h3>Calibration run progress</h3>
|
||||
<p className="muted">Each row is backed by a persisted detection run and QA check when successful.</p>
|
||||
<h3>Voortgang modelkalibratie</h3>
|
||||
<p className="muted">Iedere geslaagde rij is gekoppeld aan een bewaarde beeldanalyse en kwaliteitscontrole.</p>
|
||||
</div>
|
||||
<div className="panel-action-row">
|
||||
<span className="count-pill">{detectionCalibrationRows.length} thresholds</span>
|
||||
<span className="count-pill">{detectionCalibrationRows.length} drempels</span>
|
||||
<button
|
||||
className="secondary-action"
|
||||
type="button"
|
||||
onClick={() => downloadCalibrationSummary(selectedProjectId, detectionCalibrationRows)}
|
||||
disabled={detectionCalibrationRows.length === 0 || !selectedProjectId}
|
||||
>
|
||||
Download calibration summary
|
||||
Samenvatting downloaden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -805,15 +590,15 @@ export function DetectionLab({
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Threshold</th>
|
||||
<th>Drempel</th>
|
||||
<th>Status</th>
|
||||
<th>Detections</th>
|
||||
<th>Precision</th>
|
||||
<th>Recall</th>
|
||||
<th>Objecten</th>
|
||||
<th>Precisie</th>
|
||||
<th>Herkenningsgraad</th>
|
||||
<th>F1</th>
|
||||
<th>False positives</th>
|
||||
<th>False negatives</th>
|
||||
<th>Evidence</th>
|
||||
<th>Onterecht gevonden</th>
|
||||
<th>Gemist</th>
|
||||
<th>Kaartbewijs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -821,21 +606,21 @@ export function DetectionLab({
|
||||
<tr key={row.threshold}>
|
||||
<td>{row.threshold.toFixed(2)}</td>
|
||||
<td>{row.status}</td>
|
||||
<td>{row.detection_count ?? 'n/a'}</td>
|
||||
<td>{row.detection_count ?? 'n.v.t.'}</td>
|
||||
<td>{formatNullableNumber(row.precision ?? null, 3)}</td>
|
||||
<td>{formatNullableNumber(row.recall ?? null, 3)}</td>
|
||||
<td>{formatNullableNumber(row.f1_score ?? null, 3)}</td>
|
||||
<td>{row.false_positives ?? 'n/a'}</td>
|
||||
<td>{row.false_negatives ?? 'n/a'}</td>
|
||||
<td>{row.false_positives ?? 'n.v.t.'}</td>
|
||||
<td>{row.false_negatives ?? 'n.v.t.'}</td>
|
||||
<td>
|
||||
<button
|
||||
className="secondary-action table-action"
|
||||
type="button"
|
||||
onClick={() => row.quality_check_id ? onOpenCalibrationEvidence(row.quality_check_id) : undefined}
|
||||
disabled={!row.quality_check_id || row.status !== 'success'}
|
||||
aria-label={`Open evidence map for threshold ${row.threshold.toFixed(2)}`}
|
||||
aria-label={`Open kaartbewijs voor drempel ${row.threshold.toFixed(2)}`}
|
||||
>
|
||||
Open evidence map
|
||||
Toon op kaart
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -846,14 +631,14 @@ export function DetectionLab({
|
||||
</div>
|
||||
) : (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>No calibration sweep has been run in this session.</strong>
|
||||
<p>Choose a reference dataset and threshold set, then start the explicit sweep.</p>
|
||||
<strong>In deze sessie zijn nog geen drempels vergeleken.</strong>
|
||||
<p>Kies een referentielaag en drempelreeks en start daarna de vergelijking.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div className="ai-lab-results-surface" aria-label="Detection results">
|
||||
<div className="ai-lab-results-surface" aria-label="Resultaten van de beeldanalyse">
|
||||
<div className="panel-title-row">
|
||||
<div>
|
||||
<h3>Gevonden objecten</h3>
|
||||
@@ -875,7 +660,7 @@ export function DetectionLab({
|
||||
<option value="">Kies een bewaarde analyse</option>
|
||||
{detectionRuns.map((run) => (
|
||||
<option key={run.id} value={run.id}>
|
||||
{run.model_name || 'Gebouwdetectie'} · {run.status} · {run.id}
|
||||
{formatDetectionRunLabel(run)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -918,7 +703,7 @@ export function DetectionLab({
|
||||
</div>
|
||||
{detectionItems.length > 0 ? (
|
||||
<>
|
||||
<div className="pagination-toolbar" aria-label="Detection result pagination">
|
||||
<div className="pagination-toolbar" aria-label="Paginering van gevonden objecten">
|
||||
<p className="pagination-summary" aria-live="polite">
|
||||
<strong>{detectionPageStart + 1}-{detectionPageEnd}</strong>
|
||||
<span>van {detectionItems.length}</span>
|
||||
@@ -1005,7 +790,7 @@ export function DetectionLab({
|
||||
</div>
|
||||
{calibrationRows.length > 0 ? (
|
||||
<>
|
||||
<div className="calibration-summary-grid" aria-label="Calibration comparison winners">
|
||||
<div className="calibration-summary-grid" aria-label="Beste kalibratieresultaten">
|
||||
<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" />
|
||||
@@ -1018,7 +803,7 @@ export function DetectionLab({
|
||||
<th>Model</th>
|
||||
<th>Objecten</th>
|
||||
<th>Precisie</th>
|
||||
<th>Recall</th>
|
||||
<th>Herkenningsgraad</th>
|
||||
<th>F1</th>
|
||||
<th>Fout positief</th>
|
||||
<th>Fout negatief</th>
|
||||
@@ -1033,13 +818,17 @@ export function DetectionLab({
|
||||
<strong>{row.modelName}</strong>
|
||||
<span className="table-subtle">{row.modelAssetId ?? 'geconfigureerd lokaal model'}</span>
|
||||
</td>
|
||||
<td>{row.detectionCount ?? 'n/a'}</td>
|
||||
<td>{row.detectionCount ?? 'n.v.t.'}</td>
|
||||
<td>{formatNullableNumber(row.precision, 3)}</td>
|
||||
<td>{formatNullableNumber(row.recall, 3)}</td>
|
||||
<td>{formatNullableNumber(row.f1, 3)}</td>
|
||||
<td>{row.falsePositives ?? 'n/a'}</td>
|
||||
<td>{row.falseNegatives ?? 'n/a'}</td>
|
||||
<td>{row.qualityCheckId}</td>
|
||||
<td>{row.falsePositives ?? 'n.v.t.'}</td>
|
||||
<td>{row.falseNegatives ?? 'n.v.t.'}</td>
|
||||
<td>
|
||||
<button className="secondary-action table-action" type="button" onClick={() => onOpenCalibrationEvidence(row.qualityCheckId)}>
|
||||
Toon kaartbewijs
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -1085,15 +874,18 @@ export function DetectionLab({
|
||||
) : null}
|
||||
{detectionQaResult ? (
|
||||
<div className="result-summary-card">
|
||||
<p>Status: {detectionQaResult.status}</p>
|
||||
<p>Kwaliteitscontrole: {detectionQaResult.quality_check_id}</p>
|
||||
<p>Status: {detectionQaResult.status === 'completed' ? 'afgerond' : detectionQaResult.status}</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>Herkenningsgraad: {detectionQaResult.recall?.toFixed(3) ?? 'n.v.t.'}</p>
|
||||
<p>F1: {detectionQaResult.f1_score?.toFixed(3) ?? 'n.v.t.'}</p>
|
||||
<p>Gemiddelde overlap: {detectionQaResult.mean_iou?.toFixed(3) ?? 'n.v.t.'}</p>
|
||||
<p>Minimale IoU voor een match: {detectionQaResult.iou_threshold.toFixed(2)}</p>
|
||||
<p>Fout positief: {detectionQaResult.false_positives}</p>
|
||||
<p>Fout negatief: {detectionQaResult.false_negatives}</p>
|
||||
<details className="technical-inline-details">
|
||||
<summary>Technische referentie</summary>
|
||||
<span>Kwaliteitscontrole-ID: {detectionQaResult.quality_check_id}</span>
|
||||
</details>
|
||||
{detectionQaResult.coverage ? (
|
||||
<div className="detection-qa-diagnostic">
|
||||
<span>Gecontroleerd beeldbereik</span>
|
||||
@@ -1117,7 +909,7 @@ export function DetectionLab({
|
||||
{detectionQaResult.box_to_footprint_diagnostics.strict_matches} strikte vormmatches
|
||||
</strong>
|
||||
<p>
|
||||
{detectionQaResult.box_to_footprint_diagnostics.possible_box_to_footprint_mismatch_count} mogelijke vormafwijkingen. Precisie, recall en F1 hierboven blijven gebaseerd op de strikte geometrische overlap.
|
||||
{detectionQaResult.box_to_footprint_diagnostics.possible_box_to_footprint_mismatch_count} mogelijke vormafwijkingen. Precisie, herkenningsgraad en F1 hierboven blijven gebaseerd op de strikte geometrische overlap.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -1142,12 +934,12 @@ function CalibrationSummaryCard({
|
||||
<span>{title}</span>
|
||||
{row ? (
|
||||
<>
|
||||
<strong>{metric === 'falsePositives' ? row.falsePositives ?? 'n/a' : formatNullableNumber(row[metric], 3)}</strong>
|
||||
<strong>{metric === 'falsePositives' ? row.falsePositives ?? 'n.v.t.' : formatNullableNumber(row[metric], 3)}</strong>
|
||||
<p>Threshold {formatNullableNumber(row.threshold, 2)} · {row.modelName}</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>n/a</strong>
|
||||
<strong>n.v.t.</strong>
|
||||
<p>Persisted QA metrics are required.</p>
|
||||
</>
|
||||
)}
|
||||
@@ -1155,16 +947,6 @@ function CalibrationSummaryCard({
|
||||
)
|
||||
}
|
||||
|
||||
function formatModelAssetSize(sizeBytes: number): string {
|
||||
if (sizeBytes >= 1024 * 1024) {
|
||||
return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
if (sizeBytes >= 1024) {
|
||||
return `${(sizeBytes / 1024).toFixed(1)} KB`
|
||||
}
|
||||
return `${sizeBytes} B`
|
||||
}
|
||||
|
||||
function DetectionWorkflowStep({
|
||||
label,
|
||||
complete,
|
||||
@@ -1209,7 +991,7 @@ function buildCalibrationRows(detectionRuns: DetectionRunRead[], qualityChecks:
|
||||
analysisRunId: run.id,
|
||||
qualityCheckId: check.id,
|
||||
threshold,
|
||||
modelName: run.model_name ?? 'configured detection',
|
||||
modelName: run.model_name ?? 'geconfigureerde detectie',
|
||||
modelAssetId: stringFromRecord(run.parameters_json, 'model_asset_id'),
|
||||
detectionCount: numberFromRecord(run.result_json, 'detection_count'),
|
||||
precision: metricValue(check, 'precision'),
|
||||
@@ -1335,12 +1117,12 @@ 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/a'
|
||||
return typeof value === 'number' && Number.isFinite(value) ? value.toFixed(digits) : 'n.v.t.'
|
||||
}
|
||||
|
||||
function formatSourceTilePath(path: string | null | undefined): string {
|
||||
if (!path) {
|
||||
return 'n/a'
|
||||
return 'n.v.t.'
|
||||
}
|
||||
const parts = path.replace(/\\/g, '/').split('/').filter(Boolean)
|
||||
return parts[parts.length - 1] ?? path
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
import type {
|
||||
DetectionModelCapability,
|
||||
ModelAssetRead,
|
||||
YoloPreflightResponse,
|
||||
} from '../../types'
|
||||
import { DETECTION_OPERATOR_PROFILES, type DetectionOperatorProfile } from './detectionProfiles'
|
||||
|
||||
interface DetectionModelManagementProps {
|
||||
detectionModels: DetectionModelCapability[]
|
||||
modelAssets: ModelAssetRead[]
|
||||
loadingDetectionModels: boolean
|
||||
detectionModelError: string | null
|
||||
modelAssetError: string | null
|
||||
selectedDetectionModelId: string
|
||||
selectedModelAssetId: string
|
||||
detectionConfidenceThreshold: number
|
||||
yoloPreflight: YoloPreflightResponse | null
|
||||
loadingYoloPreflight: boolean
|
||||
yoloPreflightError: string | null
|
||||
onRefreshYoloPreflight: () => void
|
||||
onSelectModelAsset: (modelAssetId: string) => void
|
||||
onApplyOperatorProfile: (profile: DetectionOperatorProfile) => void
|
||||
}
|
||||
|
||||
export 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
|
||||
}
|
||||
|
||||
function formatModelAssetSize(sizeBytes: number): string {
|
||||
if (!Number.isFinite(sizeBytes) || sizeBytes <= 0) return 'n.v.t.'
|
||||
const megabytes = sizeBytes / (1024 * 1024)
|
||||
return `${megabytes.toLocaleString('nl-BE', { maximumFractionDigits: 1 })} MB`
|
||||
}
|
||||
|
||||
function statusLabel(value: string): string {
|
||||
if (value === 'configured' || value === 'ready') return 'gereed'
|
||||
if (value === 'not_configured') return 'niet geconfigureerd'
|
||||
if (value === 'dependency_unavailable') return 'software ontbreekt'
|
||||
return value.replace(/_/g, ' ')
|
||||
}
|
||||
|
||||
export function DetectionModelManagement({
|
||||
detectionModels,
|
||||
modelAssets,
|
||||
loadingDetectionModels,
|
||||
detectionModelError,
|
||||
modelAssetError,
|
||||
selectedDetectionModelId,
|
||||
selectedModelAssetId,
|
||||
detectionConfidenceThreshold,
|
||||
yoloPreflight,
|
||||
loadingYoloPreflight,
|
||||
yoloPreflightError,
|
||||
onRefreshYoloPreflight,
|
||||
onSelectModelAsset,
|
||||
onApplyOperatorProfile,
|
||||
}: DetectionModelManagementProps): JSX.Element {
|
||||
const selectedModelAsset = modelAssets.find((asset) => asset.model_asset_id === selectedModelAssetId) ?? null
|
||||
const selectedOperatorProfile = DETECTION_OPERATOR_PROFILES.find(
|
||||
(profile) => profile.modelAssetId === selectedModelAssetId,
|
||||
) ?? null
|
||||
const yoloRuntimeReady = Boolean(
|
||||
yoloPreflight?.checks.enabled
|
||||
&& yoloPreflight.checks.dependencies_available
|
||||
&& yoloPreflight.checks.model_file_exists,
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="detection-model-management">
|
||||
<details className="ai-lab-model-surface" aria-label="Technische modelmogelijkheden">
|
||||
<summary>
|
||||
<span>Technische modelinformatie</span>
|
||||
<strong>{detectionModels.length} registraties</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-state-stack">
|
||||
{loadingDetectionModels ? (
|
||||
<div className="result-state result-state-loading">
|
||||
<strong>Analysemodellen worden gecontroleerd.</strong>
|
||||
<p>GeoIntel leest de modelregistratie en lokale bestanden.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{detectionModelError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>De modelregistratie is niet bereikbaar.</strong>
|
||||
<p>{detectionModelError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{modelAssetError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>De lokale modelbestanden konden niet worden gelezen.</strong>
|
||||
<p>{modelAssetError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{detectionModels.length === 0 && !loadingDetectionModels ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>De backend meldt geen analysemodellen.</strong>
|
||||
<p>Vernieuw de status zodra de backend opnieuw bereikbaar is.</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<ul className="model-list">
|
||||
{detectionModels.map((model) => (
|
||||
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
|
||||
<strong>{detectionModelLabel(model)}</strong>
|
||||
<span className={model.configured ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{statusLabel(model.status)}
|
||||
</span>
|
||||
<p className="muted">Ondersteunde klassen: {model.supported_classes.join(', ') || 'niet opgegeven'}</p>
|
||||
<p className="muted">{model.limitation_message}</p>
|
||||
<details className="technical-inline-details">
|
||||
<summary>Technische identificatie</summary>
|
||||
<div className="entity-meta">
|
||||
<span>Model-ID: {model.model_id}</span>
|
||||
<span>Framework: {model.framework}</span>
|
||||
<span>Taaktype: {model.task_type}</span>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{selectedDetectionModelId === 'yolo-configured' ? (
|
||||
<details className="ai-lab-model-surface" aria-label="Lokale modelkeuze">
|
||||
<summary>
|
||||
<span>Modelkeuze voor beheerders</span>
|
||||
<strong>{selectedOperatorProfile?.displayName ?? selectedModelAsset?.display_name ?? 'Geen lokaal model'}</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>Lokaal modelbestand</h3>
|
||||
<p>GeoIntel kiest automatisch het actieve lokale model. Een andere keuze geldt alleen voor deze analyse.</p>
|
||||
</div>
|
||||
<span className={selectedModelAsset ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{selectedModelAsset ? 'model gekozen' : 'geen model gekozen'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="operator-profile-grid" aria-label="Gevalideerde YOLO-profielen">
|
||||
{DETECTION_OPERATOR_PROFILES.map((profile) => {
|
||||
const profileAsset = modelAssets.find((asset) => asset.model_asset_id === profile.modelAssetId)
|
||||
const profileSelected =
|
||||
selectedModelAssetId === profile.modelAssetId
|
||||
&& Math.abs(detectionConfidenceThreshold - profile.confidenceThreshold) < 0.0001
|
||||
return (
|
||||
<div
|
||||
className={profileSelected ? 'operator-profile-card operator-profile-card-selected' : 'operator-profile-card'}
|
||||
key={profile.id}
|
||||
>
|
||||
<div className="operator-profile-card-header">
|
||||
<strong>{profile.displayName}</strong>
|
||||
<span className={profile.defaultApproved ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{profile.defaultApproved ? 'standaardprofiel' : 'kandidaat, extra controle vereist'}
|
||||
</span>
|
||||
</div>
|
||||
<p>{profile.description}</p>
|
||||
<div className="operator-profile-metrics">
|
||||
<span>drempel {profile.confidenceThreshold.toFixed(2)}</span>
|
||||
<span>precisie {profile.precision.toFixed(3)}</span>
|
||||
<span>herkenningsgraad {profile.recall.toFixed(3)}</span>
|
||||
<span>F1 {profile.f1.toFixed(3)}</span>
|
||||
<span>testgebieden {profile.positiveSampleCount}</span>
|
||||
<span>max. achtergrondfouten {profile.maxBackgroundDetections}</span>
|
||||
</div>
|
||||
<p className="field-guidance">{profile.limitationMessage}</p>
|
||||
<button
|
||||
className="secondary-action"
|
||||
type="button"
|
||||
onClick={() => onApplyOperatorProfile(profile)}
|
||||
disabled={!profileAsset}
|
||||
>
|
||||
Profiel gebruiken
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<label>
|
||||
Lokaal modelbestand
|
||||
<select value={selectedModelAssetId} onChange={(event) => onSelectModelAsset(event.target.value)}>
|
||||
<option value="">Kies een lokaal modelbestand</option>
|
||||
{modelAssets.map((asset) => (
|
||||
<option key={asset.model_asset_id} value={asset.model_asset_id}>
|
||||
{asset.display_name} {asset.active ? '(actief)' : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
{modelAssets.length === 0 && !loadingDetectionModels ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>Geen lokaal modelbestand gevonden.</strong>
|
||||
<p>Plaats een gecontroleerd model in de modelmap of configureer het bestaande YOLO-modelpad.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedModelAsset ? (
|
||||
<details className="technical-inline-details">
|
||||
<summary>Bestands- en integriteitsgegevens</summary>
|
||||
<div className="result-summary-card">
|
||||
<p>Bestand: {selectedModelAsset.filename}</p>
|
||||
<p>Status: {statusLabel(selectedModelAsset.status)}</p>
|
||||
<p>Actief servermodel: {selectedModelAsset.active ? 'ja' : 'nee'}</p>
|
||||
<p>Automatisch downloaden: {selectedModelAsset.will_download_models ? 'ja' : 'nee'}</p>
|
||||
<p>Grootte: {formatModelAssetSize(selectedModelAsset.size_bytes)}</p>
|
||||
<p>SHA-256: {selectedModelAsset.sha256.slice(0, 12)}</p>
|
||||
<p>Pad: {selectedModelAsset.model_path}</p>
|
||||
</div>
|
||||
</details>
|
||||
) : null}
|
||||
</div>
|
||||
</details>
|
||||
) : null}
|
||||
|
||||
<details className="ai-lab-model-surface" aria-label="Technische YOLO-runtimecontrole">
|
||||
<summary>
|
||||
<span>Technische runtimecontrole</span>
|
||||
<strong>{yoloRuntimeReady ? 'gereed' : statusLabel(yoloPreflight?.status ?? 'niet geladen')}</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>YOLO-runtimecontrole</h3>
|
||||
<p>Deze alleen-lezen controle start geen analyse en downloadt geen modelbestanden.</p>
|
||||
</div>
|
||||
<button className="secondary-action" type="button" onClick={onRefreshYoloPreflight} disabled={loadingYoloPreflight}>
|
||||
Controle vernieuwen
|
||||
</button>
|
||||
</div>
|
||||
<div className="ai-lab-state-stack">
|
||||
{loadingYoloPreflight ? (
|
||||
<div className="result-state result-state-loading">
|
||||
<strong>De YOLO-runtime wordt gecontroleerd.</strong>
|
||||
<p>GeoIntel controleert configuratie, optionele software en lokale bestanden.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{yoloPreflightError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>De YOLO-runtimecontrole is niet beschikbaar.</strong>
|
||||
<p>{yoloPreflightError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{!yoloPreflight && !loadingYoloPreflight && !yoloPreflightError ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>Nog geen runtimecontrole geladen.</strong>
|
||||
<p>Vernieuw de controle voordat je een lokaal model gebruikt.</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{yoloPreflight ? (
|
||||
<div className={yoloPreflight.status === 'ready' ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}>
|
||||
<div className="lab-readiness-grid">
|
||||
<div className={yoloPreflight.checks.enabled ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>YOLO ingeschakeld</span>
|
||||
<strong>{yoloPreflight.checks.enabled ? 'ja' : 'nee'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.checks.dependencies_available ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Benodigde software</span>
|
||||
<strong>{yoloPreflight.checks.dependencies_available === true ? 'beschikbaar' : yoloPreflight.checks.dependencies_available === false ? 'ontbreekt' : 'niet gecontroleerd'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.checks.model_file_exists ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Lokaal modelbestand</span>
|
||||
<strong>{yoloPreflight.checks.model_file_exists === true ? 'gevonden' : yoloPreflight.checks.model_path_set ? 'ontbreekt' : 'niet geconfigureerd'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.runtime.cuda_available ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>GPU-versnelling</span>
|
||||
<strong>{yoloPreflight.runtime.cuda_available === true ? 'beschikbaar' : yoloPreflight.runtime.cuda_available === false ? 'niet beschikbaar' : 'niet gecontroleerd'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.checks.manifest_valid ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Beeldtegels</span>
|
||||
<strong>{yoloPreflight.checks.manifest_valid === true ? 'geldig' : yoloPreflight.checks.manifest_path_set ? 'ongeldig' : 'niet opgegeven'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.tile_count > 0 ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Aantal beeldtegels</span>
|
||||
<strong>{yoloPreflight.tile_count} / {yoloPreflight.max_tiles}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<details className="technical-inline-details">
|
||||
<summary>Versies en serverpaden</summary>
|
||||
<div className="entity-meta">
|
||||
<span>PyTorch: {yoloPreflight.runtime.torch_version ?? 'n.v.t.'}</span>
|
||||
<span>Ultralytics: {yoloPreflight.runtime.ultralytics_version ?? 'n.v.t.'}</span>
|
||||
<span>YOLO-configuratiemap: {yoloPreflight.runtime.yolo_config_dir ?? 'n.v.t.'}</span>
|
||||
<span>Modelmap: {yoloPreflight.runtime.model_directory ?? 'n.v.t.'}</span>
|
||||
<span>Model-ID: {yoloPreflight.model_asset_id ?? 'n.v.t.'}</span>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user