# ACD algorithms and skill-to-hotline mapping

> How a skill's ACD algorithm is chosen, what EAD-MIA, MIA, UCD, LOA, LAR and Manual do, and how skill-to-hotline mapping affects agent selection.

A **skill** is the unit the ACD routes on. In TeleQuick a skill is a
row in `dim_skill` (`code`, `name`, optional `external_split_id`,
optional `acd_algorithm`, `active`) plus a set of hotline memberships in
`dim_skill_hotline_map`. Every skill belongs to one or more hotlines;
calls arrive on a hotline, and the hotline's skills determine which
agents are eligible.

The **ACD algorithm** decides which of the eligible agents gets the call.
It is stored per skill, may be left unset, and is applied by the picker
(`services/acd-picker.ts`). The Skills screen only records the choice —
the picker implements it.

## The selection algorithms, one by one

| Stored value | Label | What it selects |
| ------------ | ----- | --------------- |
| `ead_mia` | EAD-MIA | Expert first, then most-idle. Considers agent skill priority before falling through to the longest-idle eligible agent. This is the Avaya default and the platform fallback. |
| `mia` | MIA | Pure most-idle. Picks the longest-idle eligible agent and **ignores skill priority** entirely. |
| `ucd` | UCD | Uniform call distribution — round-robin across eligible agents rather than idle-time ordering. |
| `loa` | LOA | Least-occupied agent: selection by occupancy rather than idle time. Currently degrades to EAD-MIA (see below). |
| `lar` | LAR | Last-agent routing: optimises for caller continuity by preferring the agent the caller last spoke to. Degrades to EAD-MIA (see below). |
| `manual` | Manual | No automatic pick at all. A supervisor assigns the call. |
| *(unset)* | inherit | Do not decide here. Resolve from the hotline, then from the platform default. |

In the console these appear in the **ACD algo** column as a chip; hovering
the chip shows the same hint text the picker's label table carries. A skill
with no algorithm shows a dimmed `inherit` instead of a chip.

## Inheritance: skill, hotline, then EAD-MIA

`acd_algorithm` is nullable, and null means *inherit*. Resolution is a
three-step chain, in this order:

1. **The skill's own `acd_algorithm`**, if set. A value here wins outright
   and the hotline is not consulted.
2. **The hotline's routing algorithm**, if the skill leaves the field unset.
3. **EAD-MIA**, if neither level specifies one.

This is why the drawer's first `<select>` option reads
`Inherit from hotline (default: EAD-MIA)`. Leaving the field empty is a
deliberate choice, not an incomplete record: it delegates the decision to
the hotline so that a routing change made once on the hotline applies to
every skill that inherits.

Set the algorithm on the skill when the skill itself has a selection
requirement that differs from everything else on its hotline — for
example a skill that must round-robin (`ucd`) while the rest of the
hotline runs expert-first.

## When LOA and LAR degrade

Two of the six values do not yet select the way their names describe.
Both fall back to **EAD-MIA**:

- **LOA (least-occupied agent)** needs live CH occupancy data to rank
  agents by occupancy. Until that occupancy signal lands, the picker
  falls back to EAD-MIA.
- **LAR (last-agent routing)** falls back to EAD-MIA as well.

The practical consequence: selecting `loa` or `lar` today produces
EAD-MIA behaviour. It is still worth recording the intent, because the
stored value is what the picker will honour once the underlying signal is
available — you do not have to revisit every skill later. But do not
design a routing plan whose correctness depends on LOA or LAR selecting
differently from EAD-MIA right now.

The hint text under the `<select>` in the drawer states the fallback for
whichever value you pick, so the degradation is visible at the moment of
the decision.

## Mapping one skill to several hotlines

Hotline membership is edited inline on the skill row, not in the drawer.
Each hotline in the org renders as a chip in the **Hotlines** cell;
chips for mapped hotlines are accented. Clicking a chip toggles the
mapping:

- an unmapped chip calls `adminSkills.map.addHotline({ orgId, skillId, hotlineId })`
- a mapped chip calls `adminSkills.map.removeHotline({ orgId, skillId, hotlineId })`

The current set comes from `adminSkills.map.listForSkill({ orgId, skillId })`,
which returns the hotline ids for that skill. The drawer's footnote
("Hotline mapping is managed inline in the row") exists because the form
deliberately does not duplicate this control.

Mapping one skill to several hotlines matters for agent selection because
of the inheritance chain above. A skill that sets its own
`acd_algorithm` selects agents the same way on every hotline it is
mapped to. A skill that inherits resolves step 2 against *the hotline the
call arrived on* — so if two hotlines carry different routing algorithms,
the same skill will select agents differently depending on which hotline
the call came in on. If you want one skill to behave identically
everywhere, pin the algorithm on the skill rather than relying on
inheritance.

If the **Hotlines** cell reads `No hotlines defined yet`, no hotlines
exist in the org at all; create hotlines first, then come back and toggle
the chips.

## Legacy split IDs and CMS migration

`external_split_id` is an optional integer, labelled *Legacy split ID*
with the placeholder `From CMS migration`. It holds the split (or skill)
number the queue had in the legacy ACD, carried over so that CMS-era
historical reporting can be joined to the new `dim_skill` rows. It plays
no part in routing and no part in algorithm resolution — the picker never
reads it.

Leave it blank for skills created natively on TeleQuick; the column
renders `—` and the row behaves identically. Populate it only where a
pre-existing split number needs to stay resolvable, and keep it stable
afterwards, since it is the join key your migrated reports rely on.

## Deactivating a skill without breaking history

Skills are never erased from the console. The drawer's destructive action
is **Deactivate**, which calls `adminSkills.remove({ orgId, id })` and
flips the row to `active = false`. The confirmation spells out the
contract:

> Deactivated, not erased. Routing skips it, but historical timeline
> records still reference it.

So after deactivation:

- The ACD stops selecting agents through that skill — routing skips it.
- The row and its `code` survive, so timeline records, CDRs and reports
  that reference the skill still resolve to a name instead of a dangling id.
- The row disappears from the default list. Tick **Include inactive** to
  see it again; `adminSkills.list` takes `includeInactive` and the
  **Status** column shows an `Inactive` chip.

You can reverse it from the drawer: the **Active** checkbox is rendered in
edit mode only, and saving sends `active` in the update patch. Because
create never sends `active`, new skills start active.

Deactivating does not clear hotline mappings. If you are retiring a skill
permanently, un-toggle its hotline chips as well so that a later
reactivation does not quietly put it back into the selection pool.

## Skill fields and the procedures behind them

| Field | Console control | Notes |
| ----- | --------------- | ----- |
| `code` | Code | Required. Typed input is upper-cased and whitespace is replaced with `-`. Max length 40. Whitespace-only input is rejected client-side before the mutation fires. |
| `name` | Name | Required, trimmed. Max length 120. |
| `external_split_id` | Legacy split ID | Optional integer, minimum 0. Empty input is sent as `null`. |
| `acd_algorithm` | ACD algorithm | Optional. Empty selection is sent as `null` (inherit). |
| `active` | Active | Edit mode only. Sent in the update patch. |

The screen uses these procedures:

| Procedure | Input |
| --------- | ----- |
| `adminSkills.list` | `{ orgId, search?, includeInactive, page, perPage }` |
| `adminSkills.create` | `{ orgId, row }` |
| `adminSkills.update` | `{ orgId, id, patch }` |
| `adminSkills.remove` | `{ orgId, id }` — deactivates |
| `adminSkills.map.listForSkill` | `{ orgId, skillId }` |
| `adminSkills.map.addHotline` | `{ orgId, skillId, hotlineId }` |
| `adminSkills.map.removeHotline` | `{ orgId, skillId, hotlineId }` |
| `adminHotlines.list` | `{ orgId, page, perPage }` — supplies the chips |

The list is paged at 50 rows and searchable on code or name. Skills also
carry tags under the resource kind `dim_skill`; tags are resolved for the
whole page in one call and edited per row.

## Related

- [Telephony Metrics](/glossary/metrics) — ASA, service level and the other numbers that a change of ACD algorithm moves
- [Telemetry](/platform/telemetry) — where per-call routing outcomes land
