# Value sets and code systems

  Every coded list in this documentation can be fetched from the terminology service. Copy one
  into a constant and it will be wrong the next time the list changes.

## Why you fetch rather than embed

Coded lists move. Identifier types get added and withdrawn, document types grow as new
interventions are onboarded, and diagnosis vocabularies are reversioned. A list you copy today
will be quietly out of date in a few months, and when that happens the failure tends to look
like something wrong with the patient's record rather than something wrong with your data.

Every list below is served by the terminology service, versioned, and safe to cache. Fetch on a
schedule, keep a local copy, and key that copy on the `version` you were handed.

This page covers which coded lists the rail uses and how to fetch one. The same list also comes
in other shapes (paged JSON, FHIR, CSV, Excel, a bare code list, and the import path back in), and
those are in [getting the data you need](/rail/reference/Terminology-Getting-Data). The rest of the
service, meaning validating a code, walking a hierarchy, pinning to a released version, translating
between vocabularies, or resolving a rate, is in
[the terminology service](/rail/reference/Terminology-Service).

## Which list do you need?

Every row below is a code system on the terminology service, and all of them are read with the
single call in [Retrieving a list](#retrieving-a-list-from-the-terminology-service). Swap the
`system` parameter and nothing else changes. Each row links to the page where the list is
explained in context.

| Code system | What it is for | Taught at |
|-------------|----------------|-----------|
| `IDENTIFIER-TYPES` | The document types a front desk may choose from when resolving a patient | [01 · Identify](/rail/stops/Identify) |
| `HEALTHCARE-WORKER-REGULATORS` † | The bodies that register practitioners, which is half of a clinician's identifier | [01 · Identify](/rail/stops/Identify) |
| `PRACTITIONER-CADRES` † | Discipline and speciality, where a benefit requires a named one | [01 · Identify](/rail/stops/Identify) |
| `ICD-11` | Diagnosis coding at preauthorisation, with an ICD-10 crosswalk for payers still on it | [5 · Preauth](/rail/stops/Preauth) |
| Error codes | Every error the rail can return, published as terminology so you can generate an enum | [Error dictionary](/rail/reference/Errors) |

† These two short codes are unconfirmed. They are the names we expect rather than names checked
against the live catalogue. `IDENTIFIER-TYPES` is confirmed and these two are not, so resolve them
before you wire them in, with `GET /code-systems/?search=regulator` and `?search=cadre`. A 404 on
the detail route is a real answer, because the route accepts either a short code or an id.

Two habits apply whichever list you are using.

Pre-load rather than fetching during a visit. A pick-list fetched while a patient waits can also
fail while that patient waits, so sync on a schedule instead.

Filter when you are building a picker, but not when you are reading history. Only
`is_active: true` belongs in a dropdown, and withdrawn codes still have to render when you are
interpreting a record from last year.

## One call, whichever list you need

There is no per-vocabulary endpoint. Identifier types have no `/identifier-type` route of their
own. They are one code system among many on the terminology service, read through the same call as
every other list on this page. The only thing that changes between them is the `system` you pass.

<TryIt
  title="Read any coded list"
  path="/concepts/?system=IDENTIFIER-TYPES&version=serving&page_size=50"
  keep="code, display, version"
>
  Swap `IDENTIFIER-TYPES` for any short code in the table above and the rest of the call is
  identical: same envelope, same paging, same `version=serving`. That is why a terminology layer in
  your own code should take the system as an argument instead of carrying one function per list.
</TryIt>

<ApiRef method="GET" path="/concepts/?system={short code}&version=serving" to="/terminology-api/concepts" keep="code, display">
  Every parameter this call accepts, in the terminology reference: filters for hierarchy, status
  and concept class, and the full `?version=` contract.
</ApiRef>

Two things to notice in the envelope it returns.

`status` matters. A withdrawn code may still appear, marked inactive, so that historical records
referencing it stay readable. Filter on `status: "active"` when you populate a picker, and do not
filter when you are interpreting stored data.

`version` is your cache key. Compare it instead of re-fetching blindly, and log it. When a coded
value stops being accepted, the first question anyone asks is which version you were on.

## Retrieving a list from the terminology service

Every coded list the rail uses is held in the terminology service as a code system, and every one
of them is fetched the same way: by API for a running integration, or as a spreadsheet for someone
who just needs to read it. None of it is specific to one vocabulary. Swap the `system` and the call
is identical.

```bash
BASE=https://ilm-dev.dha.go.ke/dev/ts/api/v2
```

### What needs a token, and what does not

The service splits in two, and knowing which half you are working in saves you an authentication
problem that did not need to happen.

<Wire
  rows={[
    { call: "GET /code-systems/", does: "Which code systems exist, with their short codes and ids. Public, so it answers with no credential at all.", keep: "short_code, id" },
    { call: "GET /code-systems/{id}/", does: "One code system's detail. Also public.", keep: "canonical url" },
    { call: "GET /value-sets/", does: "Which value sets exist. Also public.", keep: "id" },
    { call: "GET /concepts/…", does: "The codes themselves. Requires a token issued by the terminology service's own realm.", keep: "code, display" },
    { call: "GET /code-systems/{id}/versions/, /summary/, /changed/, $lookup, expansions", does: "Everything else: versioned reads, FHIR operations, expansions. All of it needs that token.", keep: "—" },
  ]}
/>

Two consequences follow. You can discover the catalogue before you hold any credentials, which is
the quickest way to confirm a short code instead of guessing at it. And the public catalogue is
filtered: without a token you see only what is published openly, so a system you were expecting may
be invisible rather than missing.

### By API: the call to build against

```bash
curl -sS "$BASE/concepts/?system=IDENTIFIER-TYPES&version=serving&page_size=50" \
  -H "authorization: Bearer $TOKEN"
```

`version=serving` is the part that matters. It resolves to the latest approved release of that
code system. Omit it and you get whatever is in the source, drafts included, which is what you want
while you are authoring terminology and wrong in every integration.

```json
{
  "count": 11,
  "next": null,
  "page_size": 50,
  "current_page": 1,
  "total_pages": 1,
  "results": [
    {
      "code": "NATIONAL_ID",
      "display": "National ID",
      "code_system_short_code": "IDENTIFIER-TYPES",
      "code_system_canonical_url": "https://terminology.sil.advantage/CodeSystem/identifier-types",
      "uri": "https://terminology.sil.advantage/CodeSystem/identifier-types|NATIONAL_ID",
      "is_active": true,
      "status": "PUBLISHED",
      "parent_code": null,
      "depth": 0,
      "child_count": 0
    }
  ]
}
```

What to read from each concept, and what to ignore:

<Wire
  rows={[
    { call: "code", does: "The value you send on the wire, and the only thing you store", keep: "code" },
    { call: "display", does: "The label you render, never stored and never sent", keep: "—" },
    { call: "is_active + status", does: "Only a PUBLISHED and active concept belongs in a picker; a DRAFT never does", keep: "is_active" },
    { call: "parent_code, depth, child_count", does: "Hierarchy, where the vocabulary has one. Render it as a tree rather than a flat list", keep: "parent_code" },
    { call: "next, total_pages", does: "Paging. A sync that reads only the first page ships part of the vocabulary and still looks like it worked", keep: "next" },
  ]}
/>

Some systems have a hierarchy and some do not. Identifier types are flat: every concept is
`depth: 0`. A diagnosis system is nested, so `SYS-CARD` (a `Category`) parents `DIAG-CHF`, which in
turn parents left- and right-sided failure. Use `concept_class` to tell a grouping node from a
selectable one, and render a category as a heading in your dropdown rather than as an option.

  This is the intended use. Fetch with `version=serving` on a schedule, cache locally, filter
  to `is_active: true`, render `display`, submit `code`. It works the same way for identifier
  types, diagnoses, document types and everything after them.

### As a spreadsheet: the three-call release job

Excel and CSV do not come from `/concepts/`. They come from an asynchronous release job. You ask
for an export, poll until it finishes, then download it.

You need two ids: the code system's `artifact_id`, and a `version_id`.

```bash
# 1 · find the version to export
curl -sS "$BASE/code-systems/1251/" \
  -H "authorization: Bearer $TOKEN" -H 'x-variant: hie'
# -> draft_version: 1199, current_version: null
```

```bash
# 2 · create the job  (format: "CSV" for CSV instead)
curl -sS -X POST "$BASE/release-jobs/" \
  -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -H 'x-active-tenant: <your tenant id>' \
  -H 'x-variant: hie' \
  -d '{
        "artifact_kind": "codesystem",
        "artifact_id": 1251,
        "version_id": 1199,
        "mode": "SNAPSHOT",
        "format": "XLSX",
        "include_inactive": false
      }'
# -> 201 { "id": 11, "status": "queued" }
```

```bash
# 3 · poll, then download
curl -sS "$BASE/release-jobs/11/"
# -> "status": "completed", "row_count": 11

curl -sSL -o identifier-types.xlsx "$BASE/release-jobs/11/download/" \
  -H "authorization: Bearer $TOKEN" -H 'x-variant: hie'
```

The export carries `code | display | definition | concept_class | parent | status |
effectiveTime`, one row per concept.

Four things are worth knowing before you build this.

`version_id` is required. Omit it and the create returns
`400 {"version_id":["This field is required."]}`. Take it from step 1: `draft_version` while a
system is still being authored, or a released version id once one exists.

Do not cache `output_file_url`. It is a shortened presigned link that is regenerated on every
poll, so each poll hands you a different URL. Hit `/release-jobs/{id}/download/` instead, which
redirects to the current one.

Polling needs no auth, but creating and downloading do. Job create needs
`terminology-service.releasejobviewset.create` and download needs `.read`. Reading job status is
open.

This path is for people, not for runtime. Use it to hand someone a list to review, or to seed a
mapping exercise. An integration reads `/concepts/?version=serving` rather than downloading
spreadsheets.

## Diagnosis coding, and the crosswalk

Diagnoses are coded in ICD-11. Exactly one primary diagnosis is required on a
preauthorisation, and duplicates are rejected.

Some payers are still on ICD-10. You do not code twice. A concept map on the terminology service
resolves it, through the same `$translate` operation any crosswalk uses:

<ApiRef method="GET" path="/concept-maps/{short code}/$translate/?code={code}" to="/terminology-api/concept-maps" keep="the target code, and its equivalence">
  The ICD-10 equivalent of an ICD-11 code, for payers that have not migrated.
</ApiRef>

Code once in ICD-11 and let the map resolve the rest. A system that stores ICD-10 as its primary
representation will have to migrate later.

Reading the answer takes a little more than lifting the target code out of it. The equivalence
matters, and a negative result can mean two different things, which is covered in
[translating between vocabularies](/rail/reference/Terminology-Concept-Maps).

## Standard codes and mapping

Before an item may be billed, it must be declared: you say what one of your price-list items
maps to, and the rail validates that the standard code is real and recognised, then computes
the paths onward to each payer's revenue codes.

This runs in one direction only. The rail does not infer what your item is; you declare it.
Anything never declared, or declared and rejected, stays off the claim entirely as
`UNMAPPED_CODE`.

  Medicines are validated against the pharmacy regulator's registry rather than the general
  standard-code space. A product that is not registered is not reimbursable, though it may
  still be sold as cash. That is an answer about payability, not about whether you may dispense
  it.

## Error codes as a value set

The error dictionary is itself published as terminology, so you can generate your error enum
rather than typing it:

<div className="rail-op-actions">
  <a className="rail-btn rail-btn--primary" href="/terminology/rail-errors.json" download>
    Flat JSON
  </a>
  <a className="rail-btn" href="/terminology/rail-errors.codesystem.json" download>
    CodeSystem
  </a>
  <a className="rail-btn" href="/terminology/rail-errors.valueset.json" download>
    ValueSet
  </a>
</div>

See the [error dictionary](/rail/reference/Errors) for the readable version.

## How to keep a local copy honest

<BuildSequence
  steps={[
    "Sync on a schedule rather than on demand. A pick-list fetched during a patient interaction can also fail during that interaction.",
    "Key your cache on the returned version string, and log it with every coded value you store.",
    "Page properly. A sync written against the default first page ships part of the vocabulary and still looks like it worked.",
    "Filter on status for pickers, and do not filter when you are interpreting stored history. Withdrawn codes still need to render.",
    "Store the code and never the display text. A display string you stored is impossible to defend once the wording changes.",
    "Generate enums at build time from the published value sets, so a new code turns up when you compile instead of in production.",
  ]}
/>

**Next:** [Getting the data you need](/rail/reference/Terminology-Getting-Data) ·
[Error dictionary](/rail/reference/Errors) · or see coded lists in use at
[02 · Verify cover](/rail/stops/Entitle)
