page the analysis run listings
Detection and segmentation run listings returned every run a project had ever produced. Runs accumulate with every analysis while the panel only ever draws the recent ones, so the response grew without bound for no benefit. Both take limit and offset now and report total, limit, offset and truncated, matching the result listings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -102,9 +102,15 @@ def queue_detection(payload: DetectionRunRequest, db: Session = Depends(get_db))
|
|||||||
def list_detection_runs(
|
def list_detection_runs(
|
||||||
project_id: UUID | None = None,
|
project_id: UUID | None = None,
|
||||||
dataset_id: UUID | None = None,
|
dataset_id: UUID | None = None,
|
||||||
|
limit: int = Query(default=DetectionService.DEFAULT_RUN_LIST_LIMIT, ge=0, le=5_000),
|
||||||
|
offset: int = Query(default=0, ge=0),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return envelope(DetectionService.list_runs(db, project_id=project_id, dataset_id=dataset_id).model_dump())
|
return envelope(
|
||||||
|
DetectionService.list_runs(
|
||||||
|
db, project_id=project_id, dataset_id=dataset_id, limit=limit, offset=offset
|
||||||
|
).model_dump()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/runs/{analysis_run_id}", response_model=Envelope[DetectionRunRead])
|
@router.get("/runs/{analysis_run_id}", response_model=Envelope[DetectionRunRead])
|
||||||
|
|||||||
@@ -73,9 +73,15 @@ def queue_segmentation(payload: SegmentationRunRequest, db: Session = Depends(ge
|
|||||||
def list_segmentation_runs(
|
def list_segmentation_runs(
|
||||||
project_id: UUID | None = None,
|
project_id: UUID | None = None,
|
||||||
dataset_id: UUID | None = None,
|
dataset_id: UUID | None = None,
|
||||||
|
limit: int = Query(default=DetectionService.DEFAULT_RUN_LIST_LIMIT, ge=0, le=5_000),
|
||||||
|
offset: int = Query(default=0, ge=0),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return envelope(SegmentationService.list_runs(db, project_id=project_id, dataset_id=dataset_id).model_dump())
|
return envelope(
|
||||||
|
SegmentationService.list_runs(
|
||||||
|
db, project_id=project_id, dataset_id=dataset_id, limit=limit, offset=offset
|
||||||
|
).model_dump()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/runs/{analysis_run_id}", response_model=Envelope[SegmentationRunRead])
|
@router.get("/runs/{analysis_run_id}", response_model=Envelope[SegmentationRunRead])
|
||||||
|
|||||||
@@ -132,7 +132,11 @@ class DetectionRunRead(BaseModel):
|
|||||||
|
|
||||||
class DetectionRunListResponse(BaseModel):
|
class DetectionRunListResponse(BaseModel):
|
||||||
items: list[DetectionRunRead]
|
items: list[DetectionRunRead]
|
||||||
|
# ``total`` counts every run; ``items`` is the most recent page of them.
|
||||||
total: int
|
total: int
|
||||||
|
limit: int | None = None
|
||||||
|
offset: int = 0
|
||||||
|
truncated: bool = False
|
||||||
|
|
||||||
|
|
||||||
class DetectionRead(BaseModel):
|
class DetectionRead(BaseModel):
|
||||||
|
|||||||
@@ -71,7 +71,11 @@ class SegmentationRunRead(BaseModel):
|
|||||||
|
|
||||||
class SegmentationRunListResponse(BaseModel):
|
class SegmentationRunListResponse(BaseModel):
|
||||||
items: list[SegmentationRunRead]
|
items: list[SegmentationRunRead]
|
||||||
|
# ``total`` counts every run; ``items`` is the most recent page of them.
|
||||||
total: int
|
total: int
|
||||||
|
limit: int | None = None
|
||||||
|
offset: int = 0
|
||||||
|
truncated: bool = False
|
||||||
|
|
||||||
|
|
||||||
class SegmentationRead(BaseModel):
|
class SegmentationRead(BaseModel):
|
||||||
|
|||||||
@@ -281,6 +281,8 @@ class DetectionService:
|
|||||||
*,
|
*,
|
||||||
project_id: uuid.UUID | None = None,
|
project_id: uuid.UUID | None = None,
|
||||||
dataset_id: uuid.UUID | None = None,
|
dataset_id: uuid.UUID | None = None,
|
||||||
|
limit: int | None = None,
|
||||||
|
offset: int = 0,
|
||||||
) -> DetectionRunListResponse:
|
) -> DetectionRunListResponse:
|
||||||
query = db.query(AnalysisRun).filter(AnalysisRun.analysis_type == "detection")
|
query = db.query(AnalysisRun).filter(AnalysisRun.analysis_type == "detection")
|
||||||
if project_id is not None:
|
if project_id is not None:
|
||||||
@@ -288,7 +290,16 @@ class DetectionService:
|
|||||||
if dataset_id is not None:
|
if dataset_id is not None:
|
||||||
query = query.filter(AnalysisRun.dataset_id == dataset_id)
|
query = query.filter(AnalysisRun.dataset_id == dataset_id)
|
||||||
rows = query.order_by(AnalysisRun.created_at.desc()).all()
|
rows = query.order_by(AnalysisRun.created_at.desc()).all()
|
||||||
return DetectionRunListResponse(items=[DetectionRunRead.model_validate(row) for row in rows], total=len(rows))
|
# Runs accumulate with every analysis; the panel draws the recent ones.
|
||||||
|
resolved_limit = DetectionService.DEFAULT_RUN_LIST_LIMIT if limit is None else int(limit)
|
||||||
|
page, total, truncated = DetectionService.paginate(rows, limit=resolved_limit, offset=offset)
|
||||||
|
return DetectionRunListResponse(
|
||||||
|
items=[DetectionRunRead.model_validate(row) for row in page],
|
||||||
|
total=total,
|
||||||
|
limit=resolved_limit,
|
||||||
|
offset=max(0, int(offset)),
|
||||||
|
truncated=truncated,
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def list_detections(
|
def list_detections(
|
||||||
@@ -774,6 +785,7 @@ class DetectionService:
|
|||||||
# A regional run holds tens of thousands of detections; the results table
|
# A regional run holds tens of thousands of detections; the results table
|
||||||
# and the map overlay both read them after every run.
|
# and the map overlay both read them after every run.
|
||||||
DEFAULT_RESULT_LIMIT = 2_000
|
DEFAULT_RESULT_LIMIT = 2_000
|
||||||
|
DEFAULT_RUN_LIST_LIMIT = 200
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def paginate(rows: list[Any], *, limit: int, offset: int) -> tuple[list[Any], int, bool]:
|
def paginate(rows: list[Any], *, limit: int, offset: int) -> tuple[list[Any], int, bool]:
|
||||||
|
|||||||
@@ -244,6 +244,8 @@ class SegmentationService:
|
|||||||
*,
|
*,
|
||||||
project_id: uuid.UUID | None = None,
|
project_id: uuid.UUID | None = None,
|
||||||
dataset_id: uuid.UUID | None = None,
|
dataset_id: uuid.UUID | None = None,
|
||||||
|
limit: int | None = None,
|
||||||
|
offset: int = 0,
|
||||||
) -> SegmentationRunListResponse:
|
) -> SegmentationRunListResponse:
|
||||||
query = db.query(AnalysisRun).filter(AnalysisRun.analysis_type == "segmentation")
|
query = db.query(AnalysisRun).filter(AnalysisRun.analysis_type == "segmentation")
|
||||||
if project_id is not None:
|
if project_id is not None:
|
||||||
@@ -251,7 +253,16 @@ class SegmentationService:
|
|||||||
if dataset_id is not None:
|
if dataset_id is not None:
|
||||||
query = query.filter(AnalysisRun.dataset_id == dataset_id)
|
query = query.filter(AnalysisRun.dataset_id == dataset_id)
|
||||||
rows = query.order_by(AnalysisRun.created_at.desc()).all()
|
rows = query.order_by(AnalysisRun.created_at.desc()).all()
|
||||||
return SegmentationRunListResponse(items=[SegmentationRunRead.model_validate(row) for row in rows], total=len(rows))
|
# Runs accumulate with every analysis; the panel draws the recent ones.
|
||||||
|
resolved_limit = DetectionService.DEFAULT_RUN_LIST_LIMIT if limit is None else int(limit)
|
||||||
|
page, total, truncated = DetectionService.paginate(rows, limit=resolved_limit, offset=offset)
|
||||||
|
return SegmentationRunListResponse(
|
||||||
|
items=[SegmentationRunRead.model_validate(row) for row in page],
|
||||||
|
total=total,
|
||||||
|
limit=resolved_limit,
|
||||||
|
offset=max(0, int(offset)),
|
||||||
|
truncated=truncated,
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def list_segmentations(
|
def list_segmentations(
|
||||||
|
|||||||
@@ -1397,8 +1397,12 @@ export interface DetectionRunRead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DetectionRunListResponse {
|
export interface DetectionRunListResponse {
|
||||||
|
/** The most recent page; `total` counts every run. */
|
||||||
items: DetectionRunRead[]
|
items: DetectionRunRead[]
|
||||||
total: number
|
total: number
|
||||||
|
limit?: number | null
|
||||||
|
offset?: number
|
||||||
|
truncated?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DetectionRead {
|
export interface DetectionRead {
|
||||||
@@ -1582,8 +1586,12 @@ export interface SegmentationRunRead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SegmentationRunListResponse {
|
export interface SegmentationRunListResponse {
|
||||||
|
/** The most recent page; `total` counts every run. */
|
||||||
items: SegmentationRunRead[]
|
items: SegmentationRunRead[]
|
||||||
total: number
|
total: number
|
||||||
|
limit?: number | null
|
||||||
|
offset?: number
|
||||||
|
truncated?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SegmentationRead {
|
export interface SegmentationRead {
|
||||||
|
|||||||
Reference in New Issue
Block a user