fix(ai): bind model scope to immutable geometry
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Jens
2026-08-09 10:54:52 +02:00
parent f41392a415
commit b76cd1837b
19 changed files with 431 additions and 58 deletions
@@ -21,11 +21,8 @@ const DETECTION_PAGE_SIZE_OPTIONS = [25, 50, 100] as const
const DEFAULT_DETECTION_PAGE_SIZE = 50
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.'
if (f1 >= 0.70) return 'Bruikbaar met gerichte handmatige controle.'
if (f1 >= 0.50) return 'Verkennend resultaat; beoordeel fouten voor operationeel gebruik.'
return 'Onvoldoende betrouwbaar voor operationeel gebruik.'
if (typeof f1 !== 'number' || !Number.isFinite(f1)) return 'Nog geen onafhankelijke kwaliteitsmeting beschikbaar.'
return `Historische F1 ${f1.toFixed(3)} is alleen kalibratiecontext. Een actuele, ruimtelijk onafhankelijke QA-run bepaalt of dit resultaat lokaal bruikbaar is.`
}
function detectionStatusLabel(status: string): string {
@@ -311,8 +308,8 @@ export function DetectionLab({
<p>{yoloRuntimeReady ? `${yoloPreflight?.runtime.cuda_available ? 'GPU' : 'CPU'} · lokaal model gevonden` : 'Controleer de modelconfiguratie onder beheer.'}</p>
</div>
<div className="ai-user-summary-card">
<span>Validatiescope</span>
<strong>{selectedOperatorProfile ? `F1 ${selectedOperatorProfile.f1.toFixed(3)}` : 'Nog niet gekoppeld'}</strong>
<span>Bewijsstatus</span>
<strong>{selectedOperatorProfile ? `Historische F1 ${selectedOperatorProfile.f1.toFixed(3)}` : 'Nog niet gekoppeld'}</strong>
<p>{selectedOperatorProfile ? selectedOperatorProfile.validationScope : 'Kies een modelprofiel met gedocumenteerd evaluatiebewijs.'}</p>
</div>
<div className={rasterDatasets.length > 0 ? 'ai-user-summary-card ai-user-summary-card-ready' : 'ai-user-summary-card'}>
@@ -141,7 +141,7 @@ export function DetectionModelManagement({
{selectedModelAsset ? 'model gekozen' : 'geen model gekozen'}
</span>
</div>
<div className="operator-profile-grid" aria-label="Gevalideerde YOLO-profielen">
<div className="operator-profile-grid" aria-label="Historische YOLO-controleprofielen">
{DETECTION_OPERATOR_PROFILES.map((profile) => {
const profileAsset = modelAssets.find((asset) => asset.model_asset_id === profile.modelAssetId)
const profileSelected =
@@ -154,18 +154,18 @@ export function DetectionModelManagement({
>
<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 className="status-badge">
{profile.independentTestProven ? 'onafhankelijk getoetst' : 'historisch, geen releasebewijs'}
</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>
<span>historische precisie {profile.precision.toFixed(3)}</span>
<span>historische herkenningsgraad {profile.recall.toFixed(3)}</span>
<span>historische F1 {profile.f1.toFixed(3)}</span>
<span>positieve controles {profile.positiveSampleCount}</span>
<span>gemeten achtergrondfouten {profile.maxBackgroundDetections}</span>
</div>
<p className="field-guidance">{profile.limitationMessage}</p>
<button
@@ -3,8 +3,7 @@ export interface DetectionOperatorProfile {
displayName: string
modelAssetId: string
confidenceThreshold: number
defaultApproved: boolean
promotionRecommendation: 'none' | 'promote_candidate'
independentTestProven: boolean
precision: number
recall: number
f1: number
@@ -21,51 +20,48 @@ export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
displayName: 'Aanbevolen controleprofiel kleine gebouwen',
modelAssetId: 'geointel-building-yolov8s-smallbld-minpx3-img640-ft30-pt',
confidenceThreshold: 0.15,
defaultApproved: true,
promotionRecommendation: 'promote_candidate',
independentTestProven: false,
precision: 0.6140895327792112,
recall: 0.6062221049337548,
f1: 0.6068607646002744,
positiveSampleCount: 7,
maxBackgroundDetections: 0,
validationScope: '7 onafhankelijke testgebieden in Mol en de Kempen',
validationScope: 'historische operatorcontrole in Mol en de Kempen; ruimtelijke onafhankelijkheid niet bewezen',
description:
'Aanbevolen profiel met een evenwicht tussen gevonden en gemiste kleine gebouwen, opnieuw gemeten over zeven onafhankelijke testgebieden in Mol en de Kempen.',
'Historisch controleprofiel met een evenwicht tussen gevonden en gemiste kleine gebouwen. Gebruik dit als startpunt voor lokale QA, niet als vrijgavebewijs.',
limitationMessage:
'De drie lege-achtergrondtests zijn geslaagd. Postel blijft met 47,5% F1 het moeilijkste testgebied; behandel elke detectie als een controlekandidaat en niet als grondwaarheid.',
'Slechts drie pure-achtergrondbeelden en geen onafhankelijke hold-out ondersteunen deze historische meting. Postel bleef het moeilijkste gebied; behandel elke detectie als controlekandidaat en niet als grondwaarheid.',
},
{
id: 'expanded-balanced-review',
displayName: 'Voorgaand gebalanceerd controleprofiel',
modelAssetId: 'geointel-building-yolov8s-aoi1024expandedminpx4vis035e50-pt',
confidenceThreshold: 0.15,
defaultApproved: true,
promotionRecommendation: 'promote_candidate',
independentTestProven: false,
precision: 0.6470590036169351,
recall: 0.4699913836847832,
f1: 0.5432865390636915,
positiveSampleCount: 7,
maxBackgroundDetections: 0,
validationScope: '7 onafhankelijke testgebieden in Mol en de Kempen',
description: 'Voorgaand profiel voor controles waarbij minder foutieve vondsten belangrijker zijn dan maximale dekking.',
validationScope: 'historische operatorcontrole in Mol en de Kempen; ruimtelijke onafhankelijkheid niet bewezen',
description: 'Historisch profiel voor lokale controles waarbij minder foutieve vondsten belangrijker zijn dan maximale dekking.',
limitationMessage:
'De lege-achtergrondtest is geslaagd. Dit profiel vindt minder onterechte objecten, maar mist meer kleine gebouwen dan het aanbevolen profiel.',
'Dit profiel vond historisch minder onterechte objecten, maar miste meer kleine gebouwen. De meting is geen onafhankelijke productbenchmark.',
},
{
id: 'conservative-review',
displayName: 'Conservatief controleprofiel',
modelAssetId: 'geointel-building-yolov8s-aoi1024bg512r3e50-pt',
confidenceThreshold: 0.35,
defaultApproved: true,
promotionRecommendation: 'promote_candidate',
independentTestProven: false,
precision: 0.840006,
recall: 0.202135,
f1: 0.32086574003576274,
positiveSampleCount: 7,
maxBackgroundDetections: 0,
validationScope: '7 onafhankelijke testgebieden in Mol en de Kempen',
description: 'Profiel met hoge precisie voor controles waarbij zo weinig mogelijk foutieve vondsten zwaarder wegen dan volledige dekking.',
validationScope: 'historische operatorcontrole in Mol en de Kempen; ruimtelijke onafhankelijkheid niet bewezen',
description: 'Historisch profiel voor lokale controles waarbij zo weinig mogelijk foutieve vondsten zwaarder wegen dan volledige dekking.',
limitationMessage:
'Goedgekeurd na de lege-achtergrondtest. Resultaten in dun bebouwde context blijven altijd controlebewijs en geen automatische waarheid.',
'De historische precisie gaat samen met zeer lage herkenningsgraad. Resultaten blijven controlebewijs en geen automatische waarheid of releasebewijs.',
},
]