Skip to content

Replace a phase still with a coach-picked frame

POST
/api/solutions/runs/{run_id}/phase-frame/
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.

run_id
required
string format: uuid

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
block_id
required

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-1
  • phase-breakdown-1 - phase-breakdown-1
  • phase-slider-side-1 - phase-slider-side-1
  • phase-slider-front-1 - phase-slider-front-1
  • phase-slider-back-1 - phase-slider-back-1
string
Allowed values: phase-slider-1 phase-breakdown-1 phase-slider-side-1 phase-slider-front-1 phase-slider-back-1
phase_id
required

Analyzer phase name, e.g. take_off — the id of the phase inside the block payload.

string
>= 1 characters <= 100 characters
attempt_id

Per-trial payload id for multi-attempt reports (the block’s repick.attemptId); omit for single-attempt reports.

string
"" <= 64 characters
session_id
required

Bound session whose clip the frame is extracted from (the block’s repick.sessionId). Must be in the run’s bindings.

string format: uuid
timestamp_ms
required

Clip position to extract, in ms from clip start.

integer

Examples

ExampleRe-pickTheTake-offStill

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
}
Media typeapplication/json

200 from the phase-frame pick — the stored override frame.

object
frame_image_url
required

Presigned URL (valid at least six days) of the extracted frame now shown for the phase.

string
recompute
required

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 - queued
  • coalesced - coalesced
  • rate_limited - rate_limited
  • team_busy - team_busy
  • none - none
string
Allowed values: queued coalesced rate_limited team_busy none
retry_after

Seconds until the hourly correction budget frees a slot. Present only with recompute: rate_limited.

integer

Examples

ExampleOverrideStored

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.

Media typeapplication/json
object
detail
required

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).

string

Example generated

{
"detail": "example"
}
Media typeapplication/json
object
detail
required

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).

string

Example generated

{
"detail": "example"
}
Media typeapplication/json
object
detail
required

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).

string

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.

Media typeapplication/json
object
detail
required

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).

string

Example generated

{
"detail": "example"
}