feat: add measured detection review loop
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-15 03:00:08 +02:00
parent 94ecd377b7
commit d22abe8e7b
27 changed files with 1578 additions and 29 deletions
+2 -1
View File
@@ -405,6 +405,7 @@ export function useDetectionWorkflow({
analysisRunId: string,
referenceDatasetId: string,
useCurrentFilters = true,
iouThresholdOverride?: number,
): Promise<DetectionQaResult | null> => {
if (!analysisRunId) {
setDetectionQaError('Select a detection run')
@@ -422,7 +423,7 @@ export function useDetectionWorkflow({
try {
const result = await detectionApi.compareWithReference(analysisRunId, {
reference_dataset_id: referenceDatasetId,
iou_threshold: qaIouThreshold,
iou_threshold: iouThresholdOverride ?? qaIouThreshold,
class_name: useCurrentFilters ? detectionClassFilter || null : null,
min_confidence: useCurrentFilters && detectionMinConfidenceFilter > 0 ? detectionMinConfidenceFilter : null,
})
+32 -4
View File
@@ -17,6 +17,8 @@ export type MapOrthophotoAnalysisStage =
| 'complete'
| 'failed'
export const MAP_BUILDING_QA_IOU_THRESHOLD = 0.25
interface MapOrthophotoAnalysisOptions {
selectedProjectId: string | null
selectedAreaId: string
@@ -27,6 +29,7 @@ interface MapOrthophotoAnalysisOptions {
compareDetectionRunWithReference: (
analysisRunId: string,
referenceDatasetId: string,
iouThreshold: number,
) => Promise<DetectionQaResult | null>
onAnalysisReady: () => void
}
@@ -61,6 +64,12 @@ function formatOrthophotoError(caught: unknown): string {
return formatError(caught, 'De kaartgestuurde beeldanalyse is mislukt')
}
function formatQualityPercentage(value: number | null | undefined): string {
return typeof value === 'number' && Number.isFinite(value)
? `${(value * 100).toLocaleString('nl-BE', { maximumFractionDigits: 1 })}%`
: 'n.v.t.'
}
export function useMapOrthophotoAnalysis({
selectedProjectId,
selectedAreaId,
@@ -75,12 +84,18 @@ export function useMapOrthophotoAnalysis({
const [status, setStatus] = useState('')
const [error, setError] = useState<string | null>(null)
const [lastResult, setLastResult] = useState<OrthophotoAcquisitionResult | null>(null)
const [lastQuality, setLastQuality] = useState<DetectionQaResult | null>(null)
const [lastDetectionCount, setLastDetectionCount] = useState<number | null>(null)
const [lastAnalysisRunId, setLastAnalysisRunId] = useState<string | null>(null)
useEffect(() => {
setStage('idle')
setStatus('')
setError(null)
setLastResult(null)
setLastQuality(null)
setLastDetectionCount(null)
setLastAnalysisRunId(null)
}, [selectionBbox?.min_x, selectionBbox?.min_y, selectionBbox?.max_x, selectionBbox?.max_y])
const run = async (bbox: VectorSelectionBBox): Promise<boolean> => {
@@ -91,6 +106,9 @@ export function useMapOrthophotoAnalysis({
}
setError(null)
setLastResult(null)
setLastQuality(null)
setLastDetectionCount(null)
setLastAnalysisRunId(null)
setStage('acquiring')
setStatus('1/3 Officieel luchtbeeld voor de rechthoek ophalen...')
try {
@@ -112,20 +130,27 @@ export function useMapOrthophotoAnalysis({
if (!detection) {
throw new Error('De beeldanalyse stopte. Open Beeldanalyse voor de technische oorzaak.')
}
setLastDetectionCount(detection.detection_count)
setLastAnalysisRunId(detection.analysis_run_id)
const reference = findBuildingReference(datasets)
if (reference) {
setStage('validating')
setStatus('3/3 Resultaat vergelijken met officiële GRB-gebouwen...')
const quality = await compareDetectionRunWithReference(detection.analysis_run_id, reference.id)
const quality = await compareDetectionRunWithReference(
detection.analysis_run_id,
reference.id,
MAP_BUILDING_QA_IOU_THRESHOLD,
)
setLastQuality(quality)
setStatus(
quality
? `Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} gebouwen herkend en gecontroleerd.`
: `Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} gebouwen herkend; kwaliteitscontrole kon niet afronden.`,
? `Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} kandidaten, ${quality.matches.toLocaleString('nl-BE')} gekoppeld aan GRB. Precision ${formatQualityPercentage(quality.precision)}, recall ${formatQualityPercentage(quality.recall)}.`
: `Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} kandidaten; kwaliteitscontrole kon niet afronden.`,
)
} else {
setStatus(
`Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} gebouwen herkend. De GRB-referentielaag ontbreekt voor automatische controle.`,
`Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} kandidaatvormen. De GRB-referentielaag ontbreekt voor automatische controle.`,
)
}
setStage('complete')
@@ -144,6 +169,9 @@ export function useMapOrthophotoAnalysis({
status,
error,
lastResult,
lastQuality,
lastDetectionCount,
lastAnalysisRunId,
running: stage === 'acquiring' || stage === 'detecting' || stage === 'validating',
run,
}