page detection and segmentation results instead of returning all of them

/detection/runs/{id}/detections and its GeoJSON sibling returned every
persisted detection, as did the segmentation equivalents. A regional run holds
tens of thousands, and these are the endpoints the results table and the map
overlay call after every run.

They now take limit and offset, default to 2.000, and report total, limit,
offset and truncated so the complete population stays visible while what is
transferred does not. The GeoJSON responses carry the same window in a
geointel_result_window foreign member.

Rows are ordered by confidence, so a capped overlay draws the strongest
detections rather than an arbitrary slice, and the lab says how many of how
many are being shown rather than silently presenting a page as the whole run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jens
2026-08-22 15:24:46 +02:00
co-authored by Claude Opus 5
parent 5278fcd361
commit 5b3839dc89
12 changed files with 271 additions and 8 deletions
+4
View File
@@ -350,6 +350,8 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
detectionRuns,
selectedDetectionRunId,
detectionItems,
detectionTotal,
detectionTruncated,
detectionGeoJson,
detectionClassFilter,
detectionMinConfidenceFilter,
@@ -1233,6 +1235,8 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
detectionWorkflowStage={detectionWorkflowStage}
selectedDetectionRunId={selectedDetectionRunId}
detectionItems={detectionItems}
detectionTotal={detectionTotal}
detectionTruncated={detectionTruncated}
detectionClassFilter={detectionClassFilter}
detectionMinConfidenceFilter={detectionMinConfidenceFilter}
loadingDetectionResults={loadingDetectionResults}
@@ -98,6 +98,9 @@ interface DetectionLabProps {
detectionWorkflowStage: DetectionWorkflowStage
selectedDetectionRunId: string
detectionItems: DetectionRead[]
/** Complete population behind the returned page. */
detectionTotal?: number
detectionTruncated?: boolean
detectionClassFilter: string
detectionMinConfidenceFilter: number
loadingDetectionResults: boolean
@@ -159,6 +162,8 @@ export function DetectionLab({
detectionWorkflowStage,
selectedDetectionRunId,
detectionItems,
detectionTotal,
detectionTruncated = false,
detectionClassFilter,
detectionMinConfidenceFilter,
loadingDetectionResults,
@@ -679,6 +684,13 @@ export function DetectionLab({
</details> : null}
<div className="ai-lab-results-surface" aria-label="Resultaten van de beeldanalyse">
{detectionTruncated ? (
<p className="geo-data-notice">
Deze run leverde {(detectionTotal ?? detectionItems.length).toLocaleString('nl-BE')} detecties op;
hieronder en op de kaart staan de {detectionItems.length.toLocaleString('nl-BE')} met de hoogste
zekerheid. De tellingen in de kwaliteitscontrole gebruiken de volledige run.
</p>
) : null}
<div className="panel-title-row">
<div>
<h3>Gevonden objecten</h3>
@@ -109,6 +109,9 @@ export function useDetectionWorkflow({
const [detectionRuns, setDetectionRuns] = useState<DetectionRunRead[]>([])
const [selectedDetectionRunId, setSelectedDetectionRunId] = useState('')
const [detectionItems, setDetectionItems] = useState<DetectionRead[]>([])
// The server returns one page; a regional run holds far more than it draws.
const [detectionTotal, setDetectionTotal] = useState(0)
const [detectionTruncated, setDetectionTruncated] = useState(false)
const [detectionGeoJson, setDetectionGeoJson] = useState<GeoJSON.FeatureCollection | null>(null)
const [detectionClassFilter, setDetectionClassFilter] = useState('')
const [detectionMinConfidenceFilter, setDetectionMinConfidenceFilter] = useState(0)
@@ -200,6 +203,10 @@ export function useDetectionWorkflow({
const loadDetectionResults = async (analysisRunId = selectedDetectionRunId) => {
if (!analysisRunId) {
setDetectionItems([])
setDetectionTotal(0)
setDetectionTruncated(false)
setDetectionTotal(0)
setDetectionTruncated(false)
setDetectionGeoJson(null)
return
}
@@ -216,6 +223,8 @@ export function useDetectionWorkflow({
detectionApi.getRunGeoJson(analysisRunId, params),
])
setDetectionItems(detectionsResponse.items)
setDetectionTotal(detectionsResponse.total)
setDetectionTruncated(Boolean(detectionsResponse.truncated))
setDetectionGeoJson(geoJsonResponse)
} catch (error) {
setDetectionRunError(formatError(error, 'Failed to load detection results'))
@@ -563,6 +572,8 @@ export function useDetectionWorkflow({
detectionRuns,
selectedDetectionRunId,
detectionItems,
detectionTotal,
detectionTruncated,
detectionGeoJson,
detectionClassFilter,
detectionMinConfidenceFilter,
+10 -2
View File
@@ -36,12 +36,20 @@ export const detectionApi = {
apiGet<DetectionRunRead>(`/api/v1/detection/runs/${analysisRunId}`),
listDetections: (
analysisRunId: string,
params: { project_id: string; dataset_id?: string | null; class_name?: string | null; min_confidence?: number | null },
params: {
project_id: string
dataset_id?: string | null
class_name?: string | null
min_confidence?: number | null
// A run holds tens of thousands of detections; the response is one page.
limit?: number | null
offset?: number | null
},
): Promise<DetectionListResponse> =>
apiGet<DetectionListResponse>(`/api/v1/detection/runs/${analysisRunId}/detections${queryString(params)}`),
getRunGeoJson: (
analysisRunId: string,
params: { project_id: string; class_name?: string | null; min_confidence?: number | null },
params: { project_id: string; class_name?: string | null; min_confidence?: number | null; limit?: number | null },
): Promise<GeoJSON.FeatureCollection> =>
apiGet<GeoJSON.FeatureCollection>(`/api/v1/detection/runs/${analysisRunId}/geojson${queryString(params)}`),
compareWithReference: (analysisRunId: string, projectId: string, payload: DetectionQaRequest): Promise<DetectionQaResult> =>
+8
View File
@@ -1418,8 +1418,12 @@ export interface DetectionRead {
}
export interface DetectionListResponse {
/** One page of results; `total` is the complete population. */
items: DetectionRead[]
total: number
limit?: number | null
offset?: number
truncated?: boolean
}
export interface DetectionQaRequest {
@@ -1583,8 +1587,12 @@ export interface SegmentationRead {
}
export interface SegmentationListResponse {
/** One page of results; `total` is the complete population. */
items: SegmentationRead[]
total: number
limit?: number | null
offset?: number
truncated?: boolean
}
export interface SegmentationQaRequest {