# Operators and certifications

> How an operator joins the roster, what presence and certification levels mean, and how dispatch matches a robot's required certification to a certified operator.

The fleet console's robot, telemetry, and alert screens tell you what the
fleet is doing. The operator roster tells you *who is allowed to drive it*.
It is the teleoperation analogue of a contact-centre agent directory plus a
skills admin: who can drive what, at what level, and until when.

In ClutchCall, a job offer for a robot only reaches an operator who holds
the certification that robot requires. Everything on this page exists to
control that one matching decision.

## The operator record

The roster screen reads the whole roster with `api.operators()`. Each row is
one operator record:

| Field       | Meaning                                                                 |
| ----------- | ----------------------------------------------------------------------- |
| `id`        | The operator id. Shown under the name in monospace; it is the handle every write takes. |
| `name`      | Display name, used in the roster and in confirmation prompts.            |
| `region`    | Free-text grouping label. May be empty, in which case the column renders `—`. |
| `presence`  | One of `attached`, `ready`, `aux`, `offline`.                            |
| `auxReason` | Optional detail appended to the presence chip when the operator is in `aux`. |
| `certs`     | The operator's certifications. Each entry is a `skill`, a `level`, and an `expires` date. |

## How an operator gets on the roster

The roster is self-populating. You do not create a blank operator record and
fill it in. When the roster is empty, the screen says so explicitly:

> No operators yet. A row appears the first time someone opens the console.

So the sequence is: a person gets console access, they open the console, and
a row for them appears in the roster. From that point on the row is the thing
you administer — you set a region on it, and certifications attach to it.

A new row carries no certifications. Until one is granted, that operator
holds nothing, and no robot that requires a certification will be offered to
them.

## Presence states

Presence is reported on the record, not set from this screen. The roster
renders it as a colour-coded chip so you can read availability down the
column:

| Presence   | Chip colour | What it means for the operator                               |
| ---------- | ----------- | ------------------------------------------------------------ |
| `attached` | accent      | Attached to a robot — driving right now.                      |
| `ready`    | accent      | Available and waiting for a job offer.                        |
| `aux`      | warning     | Present but in an auxiliary state, not taking offers. The reason is shown next to the state, e.g. `aux · break`. |
| `offline`  | faint       | Not connected to the console.                                 |

`attached` and `ready` share the accent colour because both mean the operator
is on shift. `aux` is called out in the warning colour because it is the
state that silently removes someone from the available pool while they still
look logged in. `offline` is faint because an offline operator is not
expected to take work.

When `auxReason` is set it is appended to the chip text after a `·`
separator, so the roster shows *why* someone is unavailable without a second
click.

## Skills, levels, and certification expiry

A certification is a triple:

- **skill** — what class of work it authorises.
- **level** — the depth of authorisation within that skill. An operator may
  hold the same skill at more than one level; each `skill · level` pair is a
  separate certification chip with its own expiry.
- **expires** — the date the certification runs out.

The roster shows every certification an operator holds as a chip reading
`skill · level`, and adds one derived column, **Nearest expiry**: the
`expires` value of the certification that lapses soonest, found by sorting
the operator's certifications by expiry and taking the first. An operator
with no certifications shows `—` in both columns.

Nearest expiry is a *review* column. It answers "who lapses next" at a
glance so you can renew before someone drops off the roster. This screen
does not renew or extend a certification, and it does not report what
dispatch does at the moment one lapses — it reports the date.

## How a job offer is matched to a certified operator

Dispatch is certification-driven. A robot declares a required
certification. The automatic call distributor offers the job only to
operators holding that certification. That is the whole matching rule as far
as this screen is concerned, and it has two practical consequences:

1. **Certifications are the grant of access.** An operator who is `ready`
   but does not hold the robot's required certification is not offered that
   robot.
2. **Revoking is how you take someone off a robot.** There is no separate
   "unassign from robot" control, because there is no assignment — there is
   only the certification that makes an operator eligible.

## Revoking a certification

Click any certification chip on an operator's row to revoke it. The chip
reads `skill · level ✕` and its tooltip is *Revoke this certification*.

The console confirms first, naming the operator and the exact
`skill · level` being removed, and stating the effect:

> They stop being dispatched to robots requiring it, immediately.

Confirming calls `api.revokeCert(operatorId, skill, level)` and reloads the
roster. Revocation is scoped to that one `skill · level` pair — other levels
of the same skill, and all other skills, are untouched.

Note what this screen does *not* do: there is no control here for granting a
certification. The roster is where you review and revoke. Granting happens
outside this screen.

## Region

Region is a free-text grouping label on the operator record. Use the
**Region** button on a row to edit it:

- The prompt is pre-filled with the operator's current region.
- Cancelling the prompt makes no change at all.
- An **empty answer is a deliberate clear** — submitting nothing sets the
  region to empty, and the column falls back to `—`.
- The value is trimmed, then length-checked before any request is sent. A
  region longer than 64 characters is rejected locally with a `Region`
  length message; the API is not called.

A valid value calls `api.updateOperator({ userId, region })` and reloads the
roster.

## Errors and in-flight writes

Every write on this screen is per-row. While a region edit or a revocation is
in flight for an operator, that row's Region button and certification chips
are disabled, so you cannot stack two writes on the same record.

Failures — from the client-side region length check and from the API alike —
land in the same error strip at the bottom of the screen, showing the error
message. The strip is cleared when the next write starts, so what you see is
always the outcome of the most recent attempt.
