/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>
87 lines
2.5 KiB
Python
87 lines
2.5 KiB
Python
"""Loading a run's results must not depend on the run being small.
|
|
|
|
``/detection/runs/{id}/detections`` and its GeoJSON sibling returned every
|
|
persisted detection. A regional run holds tens of thousands, so the endpoints
|
|
the map and the results table call after every run grew without bound. The
|
|
counts stay complete; what is transferred does not.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from app.services.detection_service import DetectionService
|
|
|
|
|
|
class _Detection:
|
|
def __init__(self, index: int) -> None:
|
|
self.id = uuid.uuid4()
|
|
self.index = index
|
|
|
|
|
|
def _rows(count: int) -> list[_Detection]:
|
|
return [_Detection(index) for index in range(count)]
|
|
|
|
|
|
def test_a_page_is_returned_with_the_complete_total() -> None:
|
|
page, total, truncated = DetectionService.paginate(_rows(1_000), limit=100, offset=0)
|
|
|
|
assert len(page) == 100
|
|
assert total == 1_000
|
|
assert truncated is True
|
|
|
|
|
|
def test_the_offset_walks_the_population() -> None:
|
|
page, total, _ = DetectionService.paginate(_rows(10), limit=3, offset=6)
|
|
|
|
assert [row.index for row in page] == [6, 7, 8]
|
|
assert total == 10
|
|
|
|
|
|
def test_an_offset_past_the_end_yields_an_empty_page_not_an_error() -> None:
|
|
page, total, truncated = DetectionService.paginate(_rows(5), limit=10, offset=50)
|
|
|
|
assert page == []
|
|
assert total == 5
|
|
assert truncated is True
|
|
|
|
|
|
def test_a_population_inside_one_page_is_not_reported_as_truncated() -> None:
|
|
page, total, truncated = DetectionService.paginate(_rows(7), limit=100, offset=0)
|
|
|
|
assert len(page) == 7
|
|
assert total == 7
|
|
assert truncated is False
|
|
|
|
|
|
def test_a_zero_limit_returns_everything_for_callers_that_need_it() -> None:
|
|
page, total, truncated = DetectionService.paginate(_rows(2_500), limit=0, offset=0)
|
|
|
|
assert len(page) == 2_500
|
|
assert total == 2_500
|
|
assert truncated is False
|
|
|
|
|
|
def test_a_negative_offset_is_treated_as_the_start() -> None:
|
|
page, _, _ = DetectionService.paginate(_rows(4), limit=2, offset=-5)
|
|
|
|
assert [row.index for row in page] == [0, 1]
|
|
|
|
|
|
@pytest.mark.parametrize("limit", [1, 2, 3])
|
|
def test_paging_covers_the_population_exactly_once(limit: int) -> None:
|
|
rows = _rows(7)
|
|
seen: list[int] = []
|
|
offset = 0
|
|
while True:
|
|
page, total, _ = DetectionService.paginate(rows, limit=limit, offset=offset)
|
|
if not page:
|
|
break
|
|
seen.extend(row.index for row in page)
|
|
offset += limit
|
|
|
|
assert seen == list(range(7))
|
|
assert total == 7
|