# Resolving the clinician

  A registration number without its regulator fails the same way a patient identifier without its
  document type fails. Neither one resolves, and both come back as a false "not found".

This page continues [01 · Identify](/rail/stops/Identify), which resolves the patient. Here you ask
the same question about whoever is treating them, and the answer is what keeps a billed line
defensible months later.

Every billed line carries the clinician who performed it, their licence, and the body that
regulates them. Resolve all three here and the line holds up later. This is also the cheapest
point at which you can find out that a licence has lapsed.

The clinician gets asked the same question as the patient: who are you, and does your paperwork
hold up? You answer it the same way, by picking the regulator, entering the registration number
and resolving one practitioner.

Populate the regulator picker the way you populated the document-type picker, from the
terminology service rather than a constant.

  `HEALTHCARE-WORKER-REGULATORS` is the name we expect this vocabulary to carry, and it has not been
  verified against the live catalogue. Confirm it before you wire it in. One call does it:
  `GET /code-systems/?search=regulator`, or [browse the catalogue](/rail/reference/Terminology-Getting-Data#for-a-program--paged-json).
  Short codes are stored upper-case, and the detail route resolves either a short code or an id, so a
  404 is a real answer rather than an addressing mistake.

<ValueSet
  system="HEALTHCARE-WORKER-REGULATORS"
  label="Healthcare worker regulators"
  what="The bodies that register practitioners: KMPDC for medical practitioners and dentists, the Clinical Officers Council, the Nursing Council of Kenya and the rest. Each issues its own numbering, so the regulator is half of the identifier."
/>

<Wireframe
  title="Attending doctors"
  actor="Clinician selection"
  rows={[
    [
      { label: "Regulator", kind: "field", span: 2, from: "regulator", note: "KMPDC · Clinical Officers Council · Nursing Council of Kenya" },
      { label: "Registration number", kind: "field", span: 1, note: "As issued by that regulator" },
      { label: "Search", kind: "action", span: 1 },
    ],
  ]}
  caption={<>A registration number means nothing without the body that issued it. Same pairing rule as a patient identifier.</>}
/>

Searched, with a result to select:

<Wireframe
  title="Attending doctors, 1 match"
  actor="Clinician selection"
  rows={[
    [
      { label: "KMPDC", kind: "field", span: 2, from: "regulator" },
      { label: "A8***1", kind: "field", span: 1, from: "registration_number" },
      { label: "Search", kind: "action", span: 1 },
    ],
    [
      { label: "A8***1", kind: "text", span: 1, from: "registration_number", note: "Masked" },
      { label: "Dr John W*** M***", kind: "text", span: 2, from: "name", note: "Masked. Show only what confirms the right clinician" },
      { label: "Not suspended", kind: "field", tone: "positive", span: 1, from: "suspension", note: "Checked live rather than cached" },
    ],
    [
      { label: "Select doctor", kind: "action", span: 4, note: "Attaches this practitioner to the case" },
    ],
  ]}
  caption={<>Licence status is the point of the call. A suspended or lapsed practitioner means a claim rejected days later, or a refusal here while it can still be fixed.</>}
/>

The same screen when the licence does not hold up:

<Wireframe
  title="Attending doctors, 1 match"
  actor="Clinician selection"
  rows={[
    [
      { label: "KMPDC", kind: "field", span: 2, from: "regulator" },
      { label: "B2***7", kind: "field", span: 1, from: "registration_number" },
      { label: "Search", kind: "action", span: 1 },
    ],
    [
      { label: "B2***7", kind: "text", span: 1, from: "registration_number", note: "Masked" },
      { label: "Dr Alice N*** K***", kind: "text", span: 2, from: "name", note: "Masked" },
      { label: "Suspended", kind: "field", tone: "negative", span: 1, from: "suspension", note: "Live from the regulator rather than a cached copy" },
    ],
    [
      { label: "Cannot be selected", kind: "text", span: 4, note: "Say why here, while another clinician can still be chosen, instead of at billing days after the care was given." },
    ],
  ]}
  caption={<>The same layout with one cell different, and that cell is the reason for making the call.</>}
/>

<Mermaid chart={`sequenceDiagram
    autonumber
    actor C as Clinical user
    participant H as Your HMIS
    participant R as The rail
    participant W as Health worker registry

    C->>H: Picks regulator, enters registration number
    H->>R: Practitioner search (regulator + number)
    R->>W: Resolve and check licence
    W-->>R: Practitioner, cadre, speciality, suspension state
    R-->>H: One practitioner to select
    C->>H: Select doctor
    Note over H,R: Keep the registration number and its regulator.<br/>Licence status is re-checked at preauth and billing.
`} />

<Mermaid chart={`flowchart TD
    A["Regulator + registration number"] --> B{Practitioner found?}
    B -- No --> N["Re-check the regulator<br/>before re-typing the number"]
    B -- Yes --> C{Licence current, not suspended?}
    C -- No --> D["Refuse now. Do not attach.<br/>Choose another clinician"]
    C -- Yes --> E["Attach to the case, keeping the<br/>registration number and its regulator"]
`} />

<Operation
  method="GET"
  path="/api/v1/professionals"
  summary="Resolve one practitioner, with a licence verdict."
  params={[
    { name: "X-Facility-Id", type: "header", required: true, description: "The facility this call is made from." },
    { name: "X-Facility-Id-Type", type: "header", required: true, description: "How to read it: mfl, license-number, fr-code, registration-number or fid." },
    { name: "identification_number", type: "query", required: true, description: "The number as the regulator issued it." },
    { name: "identification_type", type: "query", required: true, description: "registration_number is preferred." },
    { name: "regulator", type: "query", required: false, description: "KMPDC, COC or NCK. Required for every lookup except a PUID one." },
  ]}
  response={`{
  "registration_number": "A8***1",
  "name": "Dr J*** W*** M***",
  "regulator": "KMPDC",
  "licence": {
    "status": "ACTIVE",
    "valid_to": "2027-03-31",
    "checked_at": "2026-08-13T09:41:02+03:00"
  },
  "professional_details": {
    "cadre": "Medical Practitioner",
    "specialty": "General Surgery",
    "discipline_name": "Surgery"
  },
  "next_action": { "type": "none" }
}`}
/>

`licence.status` is `ACTIVE`, `SUSPENDED`, `EXPIRED` or `UNKNOWN`. You get the verdict rather
than the raw material. The registry answers with an activity flag and a set of date ranges, and
every integrator who works a verdict out of those works it out slightly differently. At least
one of those versions is wrong, and that cost turns up days later as a rejected line.

`UNKNOWN` is the honest answer when the registry did not respond, and it must never render as
"not suspended". Treat it as a stop rather than a pass.

A suspended practitioner comes back as a 200 with a negative verdict rather than an error. The
API reports the fact and your UI refuses the selection. Only a malformed request, or a pair that
resolves to nothing, is a 4xx:

```bash
curl -sS "$RAIL_BASE_URL/api/v1/professionals?identification_number=A81234\
&identification_type=registration_number&regulator=KMPDC" \
  -H "Authorization: Bearer $RAIL_TOKEN" \
  -H "X-Facility-Id: FAC-2029" \
  -H "X-Facility-Id-Type: fr-code"
```

Drop `regulator` and you get `REGULATOR_REQUIRED` rather than a false not-found. It is the same
failure as sending an identifier without its type, and it is caught explicitly because it is the
mistake people actually make.

Four things the registry buys you, in order of how much they save.

<BuildSequence
  steps={[
    {
      do: "Refuse a lapsed licence early",
      detail:
        "Licence validity is checked again at preauthorisation and at billing. Checking it when the clinician is selected turns a claim rejected days later into a conversation you can still have. It is the most valuable check the registry gives you, and the one integrations most often skip.",
    },
    {
      do: "Reach a practitioner when you have to",
      detail:
        "Some interventions need a named clinician's sign-off before they can go ahead. The registry holds the contact details for reaching that person, where an internal directory may be stale. The request itself is made at 06 · Preauthorize; what matters here is that you resolved a real person instead of a typed name.",
    },
    {
      do: "Confirm speciality where the benefit demands it",
      detail:
        "Some procedures require a credentialed practitioner of a named speciality, sometimes an anaesthetist alongside a surgeon. Checking at scheduling is cheaper than discovering it at adjudication.",
    },
    {
      do: "Populate the picker from the registry",
      detail:
        "Build the clinician dropdown from the registry instead of from a table someone typed. Taking the licence and regulator from the registry removes a whole class of rejection.",
    },
  ]}
/>

Cadre and speciality are coded lists too. You need them when a benefit requires a named
speciality, and when a practitioner picker groups by discipline. `PRACTITIONER-CADRES` is
unconfirmed in the same way as the regulator list above, so search the catalogue for `cadre`
before you depend on the name.

<ValueSet
  system="PRACTITIONER-CADRES"
  label="Cadres and specialities"
  what="The discipline a practitioner is registered under, and the specialities within it. Needed where a benefit requires an approved practitioner of a named speciality, and for grouping a clinician picker by something other than surname."
/>

  KMPDC, the Clinical Officers Council and the Nursing Council of Kenya each issue their own
  numbers, and the same digits can be valid under two of them. A registration number without
  its regulator fails the way a national ID without its type fails, and it produces the same
  false "not found".

## What you keep

<Wire
  rows={[
    { call: "Registration number + regulator", does: "Together they are the clinician's identifier. Neither one is enough on its own.", keep: "both, on the encounter" },
    { call: "The licence verdict", does: "Read live at selection, then re-checked at preauthorisation and billing. Store when you read it as well as what it said.", keep: "status + when" },
    { call: "Cadre and speciality", does: "Needed where a benefit requires a named speciality, and for grouping a clinician picker.", keep: "the codes" },
  ]}
/>

## What breaks here

<CostTable
  rows={[
    {
      where: "Clinician selection",
      symptom: "\"That registration number does not exist\"",
      cause:
        "The wrong regulator. The same digits can be valid under two councils, so the number means nothing without the body that issued it.",
    },
    {
      where: "Billing",
      symptom: "\"Line rejected: practitioner not eligible\"",
      cause:
        "A lapsed or suspended licence, or the wrong speciality for the intervention. Licence status is checked live at billing; checking it at selection turns this into a fixable conversation.",
    },
    {
      where: "Billing",
      symptom: "\"We cannot say who performed this\"",
      cause:
        "The clinician was picked from a typed internal list rather than the registry, so the line carries a name with nothing behind it.",
    },
    {
      where: "Preauth",
      symptom: "\"The surgical request was refused on the practitioner\"",
      cause:
        "A surgical request needs a credentialed surgeon, not just a licensed doctor. Where an anaesthetist is required, that is a second named practitioner.",
    },
  ]}
/>

## What carries forward

The registration number and its regulator travel onto every billed line, and the licence is
re-checked at [06 · Preauthorize](/rail/stops/Preauth) and [07 · Bill](/rail/stops/Bill). Where an
intervention needs clinical sign-off, the request is routed to this practitioner, and that
workflow lives at Preauth rather than here.

  The patient's identity: the identifier pair, the four moves of a lookup, and what resolving it
  hands to every step after.

<AgentPrompt title="Resolve the clinician with an agent" filename="clinician-identity.instructions.md">{`You are integrating an HMIS with the Savannah unified payer rail.

Task: resolve the treating clinician, and keep what a claim will need.

THE IDENTIFIER IS A PAIR
  Registration number + the regulator that issued it. The same digits can be valid under
  two councils, so a number alone produces a false "not found".
  Populate the regulator picker from the terminology service, never from a constant.

CHECK THE LICENCE AT SELECTION
  Read the verdict live. Refuse a suspended or lapsed practitioner at the point they are
  chosen. Do not attach them and find out at billing, days after the care was given.
  Store what the verdict was AND when you read it.

SPECIALITY MATTERS WHERE THE BENEFIT SAYS SO
  Surgical requests need a credentialed surgeon, not merely a licensed doctor, and some
  procedures additionally require an anaesthetist as a second named practitioner.
  Cadre and speciality are coded lists; fetch and cache them like any other.

BUILD THE PICKER FROM THE REGISTRY
  Not from a typed internal table. Every billed line carries the performing clinician,
  their licence and their regulating body; sourcing those from the registry removes an
  entire class of rejection.

MASK ON SCREEN
  Show enough to confirm the right clinician, not the full record.`}</AgentPrompt>
