Replace a phase still with a coach-picked frame
const url = 'http://localhost:8000/api/solutions/runs/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/phase-frame/';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"block_id":"phase-slider-1","phase_id":"take_off","attempt_id":"","session_id":"6f1d3a2e-8c4b-4f4e-9d2a-1b2c3d4e5f60","timestamp_ms":2440}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:8000/api/solutions/runs/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/phase-frame/ \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "block_id": "phase-slider-1", "phase_id": "take_off", "attempt_id": "", "session_id": "6f1d3a2e-8c4b-4f4e-9d2a-1b2c3d4e5f60", "timestamp_ms": 2440 }'Phase-frame re-pick (#1382): when phase detection lands on the wrong still, the coach scrubs the clip in the report lightbox and submits a timestamp; the API extracts that frame from the bound session’s annotated clip and stores it as a RUN-scoped override. The override is what the report, the public share link and the PDF export all render for that phase; a new run of the same Solution starts from the analyzer’s own frames again. block_id / phase_id / attempt_id / session_id are echoed from the phase block’s repick context in the report payload. Team-scoped; cross-team run ids are a 404.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Request Bodyrequired
Section titled “Request Bodyrequired”Request body for POST /api/solutions/runs/<id>/phase-frame/ — replace
one phase’s still with a frame the coach picked from the clip (#1382).
block_id + phase_id + attempt_id identify the phase (the FE echoes
them from the block’s repick context); session_id names the bound
session whose clip the frame is cut from; timestamp_ms is the scrub
position (clamped into the clip server-side).
object
The phase block being corrected (phase-slider-1 / phase-breakdown-1, or phase-slider-<view>-1 for a multi-camera report’s other cameras).
phase-slider-1- phase-slider-1phase-breakdown-1- phase-breakdown-1phase-slider-side-1- phase-slider-side-1phase-slider-front-1- phase-slider-front-1phase-slider-back-1- phase-slider-back-1
Analyzer phase name, e.g. take_off — the id of the phase inside the block payload.
Per-trial payload id for multi-attempt reports (the block’s repick.attemptId); omit for single-attempt reports.
Bound session whose clip the frame is extracted from (the block’s repick.sessionId). Must be in the run’s bindings.
Clip position to extract, in ms from clip start.
Examples
Replace the running-gait Take-off frame at 2.44 s
{ "block_id": "phase-slider-1", "phase_id": "take_off", "attempt_id": "", "session_id": "6f1d3a2e-8c4b-4f4e-9d2a-1b2c3d4e5f60", "timestamp_ms": 2440}Request body for POST /api/solutions/runs/<id>/phase-frame/ — replace
one phase’s still with a frame the coach picked from the clip (#1382).
block_id + phase_id + attempt_id identify the phase (the FE echoes
them from the block’s repick context); session_id names the bound
session whose clip the frame is cut from; timestamp_ms is the scrub
position (clamped into the clip server-side).
object
The phase block being corrected (phase-slider-1 / phase-breakdown-1, or phase-slider-<view>-1 for a multi-camera report’s other cameras).
phase-slider-1- phase-slider-1phase-breakdown-1- phase-breakdown-1phase-slider-side-1- phase-slider-side-1phase-slider-front-1- phase-slider-front-1phase-slider-back-1- phase-slider-back-1
Analyzer phase name, e.g. take_off — the id of the phase inside the block payload.
Per-trial payload id for multi-attempt reports (the block’s repick.attemptId); omit for single-attempt reports.
Bound session whose clip the frame is extracted from (the block’s repick.sessionId). Must be in the run’s bindings.
Clip position to extract, in ms from clip start.
Request body for POST /api/solutions/runs/<id>/phase-frame/ — replace
one phase’s still with a frame the coach picked from the clip (#1382).
block_id + phase_id + attempt_id identify the phase (the FE echoes
them from the block’s repick context); session_id names the bound
session whose clip the frame is cut from; timestamp_ms is the scrub
position (clamped into the clip server-side).
object
The phase block being corrected (phase-slider-1 / phase-breakdown-1, or phase-slider-<view>-1 for a multi-camera report’s other cameras).
phase-slider-1- phase-slider-1phase-breakdown-1- phase-breakdown-1phase-slider-side-1- phase-slider-side-1phase-slider-front-1- phase-slider-front-1phase-slider-back-1- phase-slider-back-1
Analyzer phase name, e.g. take_off — the id of the phase inside the block payload.
Per-trial payload id for multi-attempt reports (the block’s repick.attemptId); omit for single-attempt reports.
Bound session whose clip the frame is extracted from (the block’s repick.sessionId). Must be in the run’s bindings.
Clip position to extract, in ms from clip start.
Responses
Section titled “Responses”200 from the phase-frame pick — the stored override frame.
object
Presigned URL (valid at least six days) of the extracted frame now shown for the phase.
What the corrected frame started for the numbers read at it (#2612). queued — the analyzer is re-reading, the values arrive on a later report GET; coalesced — a re-run was already in flight and will pick this correction up too; rate_limited — this run has spent its hourly budget, the frame is still swapped and the numbers stay as the detector read them; team_busy — the team’s correction slots are full, same outcome; none — nothing to recompute. The pick itself ALWAYS succeeded: this field never reports a failure of the frame swap.
queued- queuedcoalesced- coalescedrate_limited- rate_limitedteam_busy- team_busynone- none
Seconds until the hourly correction budget frees a slot. Present only with recompute: rate_limited.
Examples
Override stored
{ "frame_image_url": "https://media.example.com/aik-media/teams/1/solutions/9a8b7c6d/phase-frames/0f9e8d7c6b5a.jpg", "recompute": "queued"}Invalid body — unknown block_id, a session_id not in the run’s bindings, or a session with no clip to extract from.
object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}frame_extract_failed — ffmpeg could not produce a frame (unreachable clip, decode failure, timeout). Nothing is stored; the phase keeps its previous frame.
object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}