diff --git a/views_api.py b/views_api.py index b1c9326..5e008e0 100644 --- a/views_api.py +++ b/views_api.py @@ -422,6 +422,43 @@ async def api_list_settlements_for_machine( return await get_settlements_for_machine(machine_id) +# NOTE on route ordering: FastAPI matches in declaration order. The literal +# /settlements/stuck must be registered BEFORE /settlements/{settlement_id} +# so the literal wins. Same applies to any future literal sub-route under +# /settlements/* (don't reshuffle this section without re-confirming the +# order). + + +@satmachineadmin_api_router.get( + "/api/v1/dca/settlements/stuck", response_model=StuckSettlementsResponse +) +async def api_list_stuck_settlements( + threshold_minutes: int = 30, + user: User = Depends(check_user_exists), +) -> StuckSettlementsResponse: + """Operator worklist of settlements that didn't process cleanly. + + Returns three lists: + - errored: distribution failed; retry endpoint handles these + - stuck_pending: landed but never picked up by the processor + - stuck_processing: claim taken but no completion in N minutes + + `threshold_minutes` controls the age threshold for 'stuck' (default 30). + Operators can force-recover stuck-processing settlements via + POST /api/v1/dca/settlements/{id}/force-reset.""" + if threshold_minutes < 1: + raise HTTPException( + HTTPStatus.BAD_REQUEST, "threshold_minutes must be >= 1" + ) + buckets = await get_stuck_settlements_for_operator(user.id, threshold_minutes) + return StuckSettlementsResponse( + threshold_minutes=threshold_minutes, + errored=buckets["errored"], + stuck_pending=buckets["stuck_pending"], + stuck_processing=buckets["stuck_processing"], + ) + + @satmachineadmin_api_router.get( "/api/v1/dca/settlements/{settlement_id}", response_model=DcaSettlement ) @@ -472,36 +509,6 @@ async def api_partial_dispense( raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc -@satmachineadmin_api_router.get( - "/api/v1/dca/settlements/stuck", response_model=StuckSettlementsResponse -) -async def api_list_stuck_settlements( - threshold_minutes: int = 30, - user: User = Depends(check_user_exists), -) -> StuckSettlementsResponse: - """Operator worklist of settlements that didn't process cleanly. - - Returns three lists: - - errored: distribution failed; retry endpoint handles these - - stuck_pending: landed but never picked up by the processor - - stuck_processing: claim taken but no completion in N minutes - - `threshold_minutes` controls the age threshold for 'stuck' (default 30). - Operators can force-recover stuck-processing settlements via - POST /api/v1/dca/settlements/{id}/force-reset.""" - if threshold_minutes < 1: - raise HTTPException( - HTTPStatus.BAD_REQUEST, "threshold_minutes must be >= 1" - ) - buckets = await get_stuck_settlements_for_operator(user.id, threshold_minutes) - return StuckSettlementsResponse( - threshold_minutes=threshold_minutes, - errored=buckets["errored"], - stuck_pending=buckets["stuck_pending"], - stuck_processing=buckets["stuck_processing"], - ) - - @satmachineadmin_api_router.post( "/api/v1/dca/settlements/{settlement_id}/force-reset", response_model=DcaSettlement,