# Tracking Endpoint Message to handle

The **same message structure** is used across:

* real-time tracking
* upload tracking (video/image)

***

### Form score and grades (V3)

Each completed repetition is scored **0–100** and mapped to a grade:

* **A**: score ≥ 90
* **B**: score ≥ 80
* **C**: score ≥ 70
* **D**: score ≥ 60
* **F**: score < 60

The grade reflects execution quality (depth, angles, tempo, stability, and form).

#### Where the grade is sent

In V3, the authoritative score/grade for a **counted rep** is sent inside the **`counter` event** (`counter.form_score`).

> Do not rely on a separate `form_score` event to get the grade of the rep that was just counted.

***

### Message types (V3)

#### Core lifecycle

* `initialization`
* `posture`
* `counter`
* `error`

#### Optional outputs (plan-gated)

* `keypoints`
* `angles`
* `progression`
* `recommendations`

#### Jump-specific (custom exercises)

* `jump_calibration`, `jump_started`, `jump_discarded`, `jump_height`, `jump_summary`

***

### `counter` (updated V3 payload)

The `counter` message is sent **each time the rep count changes**. For upload video, a final counter is emitted once processing ends (`final: true`).

#### Fields

* `type`: `"counter"`
* `current_count`: `number` — total counted reps so far
* `final`: `boolean` (optional) — true when processing has finished (upload video)
* `form_score`: `object` (optional) — score for the rep that was just counted (or current state when `final:true`)
  * `score`: `number` (0–100)
  * `avg_score`: `number` (running average)
  * `grade`: `"A" | "B" | "C" | "D" | "F"`

#### `reference_score` (when using `reference`)

When using `reference=REFERENCE_UUID`, the `counter` event may include a `reference_score` object.

This object contains similarity metrics between the user’s movement and the selected reference movement.

* `overallScore` (`number`, `0..1`) — overall similarity score
* `poseScore` (`number`, `0..1`) — pose similarity
* `timingScore` (`number`, `0..1`) — timing similarity
* `movementScore` (`number`, `0..1`) — movement amplitude similarity
* `grade` (`string`) — letter grade for the repetition

Example:

```json
{
  "type": "counter",
  "current_count": 3,
  "reference_score": {
    "overallScore": 0.82,
    "poseScore": 0.78,
    "timingScore": 0.9,
    "movementScore": 0.84,
    "grade": "B"
  }
}
```

Use `overallScore` if you want a single global score. Use the sub-scores if you want custom logic or custom UI feedback.

#### Concrete examples

```json
{"type":"counter","current_count":1,"form_score":{"score":88,"avg_score":88,"grade":"B"}}
```

```json
{"type":"counter","current_count":5,"form_score":{"score":72,"avg_score":81,"grade":"C"}}
```

```json
{"type":"counter","current_count":10,"final":true,"form_score":{"score":90,"avg_score":84,"grade":"A"}}
```

***

### Standalone `form_score` event (if you see it)

If a `form_score`-only message exists in other contexts (e.g. live feedback while a rep is in progress), it **must not** be treated as the authoritative grade for a **counted** rep.

**Single source of truth for counted reps:** `counter.form_score`.

***

### Tips for developers: minGrade vs client-side filtering

#### Server-side filtering (recommended): `minGrade`

If you set `minGrade=B`, PoseTracker only increments `current_count` for reps graded **A or B**. This means:

* `current_count` becomes your “valid reps” count
* each counter event already corresponds to a valid rep

#### Client-side filtering (if you don’t want server filtering)

If you do **not** set `minGrade`, you receive every counted rep with its grade inside `counter.form_score`. You can maintain your own strict count:

Pseudo-logic:

* if `payload.form_score.grade` is in `["A","B"]`, increment your local strict counter

***

### Errors

(keep your existing error section; ensure it includes plan restriction errors and invalid\_exercise)
