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:
@@ -297,6 +297,8 @@ class DetectionService:
|
||||
dataset_id: uuid.UUID | None = None,
|
||||
class_name: str | None = None,
|
||||
min_confidence: float | None = None,
|
||||
limit: int | None = None,
|
||||
offset: int = 0,
|
||||
) -> DetectionListResponse:
|
||||
if analysis_run_id is not None:
|
||||
run = db.get(AnalysisRun, analysis_run_id)
|
||||
@@ -309,8 +311,15 @@ class DetectionService:
|
||||
class_name=class_name,
|
||||
min_confidence=min_confidence,
|
||||
)
|
||||
items = [DetectionRead.model_validate(row) for row in rows]
|
||||
return DetectionListResponse(items=items, total=len(items))
|
||||
resolved_limit = DetectionService.DEFAULT_RESULT_LIMIT if limit is None else int(limit)
|
||||
page, total, truncated = DetectionService.paginate(rows, limit=resolved_limit, offset=offset)
|
||||
return DetectionListResponse(
|
||||
items=[DetectionRead.model_validate(row) for row in page],
|
||||
total=total,
|
||||
limit=resolved_limit,
|
||||
offset=max(0, int(offset)),
|
||||
truncated=truncated,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_detection(db, detection_id: uuid.UUID) -> DetectionRead:
|
||||
@@ -327,16 +336,27 @@ class DetectionService:
|
||||
dataset_id: uuid.UUID | None = None,
|
||||
class_name: str | None = None,
|
||||
min_confidence: float | None = None,
|
||||
limit: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
detections = DetectionService._query_detection_rows(
|
||||
rows = DetectionService._query_detection_rows(
|
||||
db,
|
||||
analysis_run_id=analysis_run_id,
|
||||
dataset_id=dataset_id,
|
||||
class_name=class_name,
|
||||
min_confidence=min_confidence,
|
||||
)
|
||||
resolved_limit = DetectionService.DEFAULT_RESULT_LIMIT if limit is None else int(limit)
|
||||
# Rows arrive ranked by confidence, so a capped overlay draws the
|
||||
# strongest detections rather than an arbitrary slice.
|
||||
detections, total, truncated = DetectionService.paginate(rows, limit=resolved_limit, offset=0)
|
||||
return {
|
||||
"type": "FeatureCollection",
|
||||
"geointel_result_window": {
|
||||
"feature_count": len(detections),
|
||||
"total_feature_count": total,
|
||||
"limit": resolved_limit,
|
||||
"truncated": truncated,
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
@@ -738,6 +758,26 @@ class DetectionService:
|
||||
)
|
||||
return dataset
|
||||
|
||||
# A regional run holds tens of thousands of detections; the results table
|
||||
# and the map overlay both read them after every run.
|
||||
DEFAULT_RESULT_LIMIT = 2_000
|
||||
|
||||
@staticmethod
|
||||
def paginate(rows: list[Any], *, limit: int, offset: int) -> tuple[list[Any], int, bool]:
|
||||
"""Slice a result population, keeping the total intact.
|
||||
|
||||
``limit <= 0`` means "everything", for callers that genuinely need the
|
||||
whole population and know what they are asking for.
|
||||
"""
|
||||
|
||||
total = len(rows)
|
||||
start = max(0, int(offset))
|
||||
if limit <= 0:
|
||||
return rows[start:], total, False
|
||||
page = rows[start : start + int(limit)]
|
||||
# Truncated means: this page is not the whole population.
|
||||
return page, total, len(page) < total
|
||||
|
||||
@staticmethod
|
||||
def _query_detection_rows(
|
||||
db,
|
||||
|
||||
@@ -290,16 +290,25 @@ class SegmentationService:
|
||||
dataset_id: uuid.UUID | None = None,
|
||||
class_name: str | None = None,
|
||||
min_confidence: float | None = None,
|
||||
limit: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
segmentations = SegmentationService._query_segmentation_rows(
|
||||
rows = SegmentationService._query_segmentation_rows(
|
||||
db,
|
||||
analysis_run_id=analysis_run_id,
|
||||
dataset_id=dataset_id,
|
||||
class_name=class_name,
|
||||
min_confidence=min_confidence,
|
||||
)
|
||||
resolved_limit = DetectionService.DEFAULT_RESULT_LIMIT if limit is None else int(limit)
|
||||
segmentations, total, truncated = DetectionService.paginate(rows, limit=resolved_limit, offset=0)
|
||||
return {
|
||||
"type": "FeatureCollection",
|
||||
"geointel_result_window": {
|
||||
"feature_count": len(segmentations),
|
||||
"total_feature_count": total,
|
||||
"limit": resolved_limit,
|
||||
"truncated": truncated,
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
|
||||
Reference in New Issue
Block a user