# What a cover pays for

  A cover pays for things arranged as a tree rather than a flat catalogue. The tree starts at the
  benefit, and every level takes the code you chose at the level above it.

This page picks up where [02 · Verify cover](/rail/stops/Entitle) leaves off. That page
answers *which cover pays*. Once a cover is selected, this one answers *what it pays for, here,
today*.

Nothing below works without a chosen cover. The tree is resolved per member and per cover, so the
same query with a different selection comes back different.

The next three views show the same structure: the screens an operator works through, the shape
itself, and the calls behind them.

<Wireframe
  title="Benefits on the selected cover"
  actor="Clinical user · level 1"
  rows={[
    [
      { label: "Cover: TSC · contributor Margaret N***", kind: "text", span: 3, from: "effective coverage", note: "Always visible. The tree below belongs to this cover." },
      { label: "Search all levels", kind: "field", span: 1, note: "Returns leaves with their path, and it is the only way to find something you cannot place." },
    ],
    [
      { label: "Eye Health · SHA-05", kind: "text", swatch: 1, span: 3, from: "benefit code", note: "Read what the benefit declares here, because some payers answer the preauth question at this level" },
      { label: "Open", kind: "action", span: 1 },
    ],
    [
      { label: "Inpatient Services · PMF-07", kind: "text", swatch: 1, span: 3, from: "benefit code", note: "A different fund, in the same list" },
      { label: "Open", kind: "action", span: 1 },
    ],
  ]}
  caption={<>Level one is the only level you can ask for without already holding a code. Everything below it takes the code you just chose.</>}
/>

<Wireframe
  title="Categories under Eye Health"
  actor="Clinical user · level 2"
  rows={[
    [
      { label: "Eye Health", kind: "text", span: 4, from: "breadcrumb", note: "It holds the resolved chain you will need to explain a refusal, so it is doing more than decorating" },
    ],
    [
      { label: "Optical Health Services · SHA-05-SC-01", kind: "text", swatch: 1, span: 2, from: "sub_benefit_code" },
      { label: "Outpatient", kind: "text", span: 1, from: "access_point", note: "Declared here on this payer" },
      { label: "Open", kind: "action", span: 1 },
    ],
  ]}
  caption={<>The access point is usually decided at this level. Filter against the visit you are in, so the operator cannot pick something that cannot be billed.</>}
/>

<Wireframe
  title="Procedures under Optical Health Services"
  actor="Clinical user · level 3"
  rows={[
    [
      { label: "Eye Health › Optical Health Services", kind: "text", span: 4, from: "breadcrumb" },
    ],
    [
      { label: "Frames · SHA-05-034", kind: "text", swatch: 1, span: 2, from: "procedure code" },
      { label: "Preauth: Yes", kind: "field", tone: "negative", span: 1, from: "resolved from the chain", note: "Inherited or declared, and either way resolved" },
      { label: "Select", kind: "action", span: 1 },
    ],
    [
      { label: "Spectacle magnifiers · SHA-05-030", kind: "text", swatch: 1, span: 2, from: "procedure code", note: "No children, so this row selects rather than opens" },
      { label: "Preauth: Yes", kind: "field", tone: "negative", span: 1, from: "resolved from the chain" },
      { label: "Select", kind: "action", span: 1 },
    ],
    [
      { label: "Balance for this procedure, on this cover", kind: "field", tone: "attention", span: 4, from: "balance call", note: "A sideways question, asked with the procedure code and the contributor rather than the next level down" },
    ],
  ]}
  caption={<>Offer select on any node with no children, and open on any node that has them. Take the depth from the response instead of assuming it.</>}
/>

<Mermaid chart={`flowchart TD
    B["Benefit<br/><i>level 1, always present</i>"] --> C["Sub-benefit / category<br/><i>level 2</i>"]
    C --> P["Procedure<br/><i>level 3, the billable unit</i>"]
    P --> S["Service<br/><i>level 4, where a payer has one</i>"]
    S --> R["Protocol"]
    R --> D["Drug / item"]
    B -. "benefit code" .-> BAL(["Balance API<br/><i>asked, not traversed</i>"])
    P -. "procedure code" .-> BAL
`} />

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

    H->>R: Benefits for this cover
    R-->>H: Benefit codes + what each declares
    C->>H: Picks Eye Health
    H->>R: Categories under SHA-05
    R-->>H: Sub-benefit codes + access points
    C->>H: Picks Optical Health Services
    H->>R: Procedures under SHA-05-SC-01
    R-->>H: Procedure codes + rules, tariffs, flags
    Note over H: Merge each level's attributes over<br/>what was inherited. Keep the chain.
    C->>H: Selects a procedure
    H->>R: Balance for this procedure, this contributor
    R-->>H: What is left, or "not tracked"
`} />

Whichever view you read, two things matter more than the levels themselves.

Balances are not a level of the tree. They are a separate question you ask *about* a node. Take the
benefit code, or the procedure code where the payer tracks it that finely, pass it to the balance
call, and you get what is left if that payer exposes it. Walking further down will never tell you
anything about a balance.

Fund, access point, tariff and preauthorisation are not levels either. They are *attributes* that
hang on whichever level the payer chose to attach them to. That is the part that trips people who
have integrated one payer and assume the next behaves the same way.

## The same rule can live at different depths

Of everything on this page, this is the part to hold on to, and it is why a hard-coded traversal
breaks on the second payer you integrate.

<Wire
  rows={[
    { call: "A private insurer", does: "May decide preauthorisation at the BENEFIT level: if the benefit is inpatient, preauthorisation is required, full stop. Nothing below it needs to be read to know that.", keep: "the flag, read at level 1" },
    { call: "The national scheme", does: "Decides it at the PROCEDURE level and downwards: two procedures inside the same category differ, and one of them needs approval while its neighbour does not.", keep: "the flag, read at level 3" },
  ]}
/>

So "does this need preauthorisation?" has no fixed home. Resolve the attribute against the whole
chain, from the benefit down to the node you have selected, and take the most specific value you
find. A traversal that only reads the leaf misses the insurer that declared it at the top, and one
that only reads the benefit misses the scheme that varies it per procedure.

The same is true of access point, of document requirements, and of combination rules.

  Carry a resolved context down the walk rather than a single node. At each level, merge what that
  level declares over what you inherited, and keep the node that declared each value so you can
  explain a refusal later. If your traversal returns a leaf object with no memory of its ancestors,
  you cannot answer "why is preauthorisation required here?", and somebody will ask.

## You walk it one level at a time

No call returns the whole tree, and that is not a limitation to route around. The tree is too large
to hand over, and the national scheme's procedure list alone runs to thousands of codes.

So every level takes the code chosen at the level above it — passed to the same route, as
`parent_code`, rather than to a route of its own.

<BuildSequence
  steps={[
    { do: "Ask for the benefits on the chosen cover", detail: "The call with no parent_code. This is the only level you can ask for without already having a code in hand." },
    { do: "Pass the benefit code as parent_code", detail: "You get its categories. Read what the benefit itself declared before you go further, because some payers have already answered your question here." },
    { do: "Pass the category code as parent_code", detail: "You get the billable procedures, each with its own attributes, which override anything inherited." },
    { do: "Pass the procedure code as parent_code", detail: "Where a payer exposes them, you get services, protocols and items. Many payers stop above this, and that is normal." },
    { do: "Ask the balance separately", detail: "A sideways question rather than the next step down — and one the rail does not yet answer against a selection. See below." },
  ]}
/>

Because the depth is a filter rather than a path, a client that stops when a level comes back empty
is already correct for every payer. One that hard-codes four calls is correct for exactly the payers
it was written against.

That has three consequences for the picker you build.

Take the depth from the response instead of assuming it. One payer's tree ends at the procedure,
another carries services and drugs below it. Follow what comes back, and offer a select action on
any node with no children.

A returned list may already start below the top. Because the tree is large, some responses answer
at the category or sub-procedure level rather than at the benefit, depending on the payer and how the
question was asked. Read the parent codes on every node and rebuild the chain rather than trusting
position in the response.

Search has to cross levels. A clinician looking for "dialysis" does not know which benefit it sits
under, and drill-down on its own will never get them there. Pair the walk with a search that returns
leaves and shows the path above each hit, so selecting a search result still gives you the resolved
chain.

## One shape, several vocabularies

The words change per payer while the structure stays the same, and the rail normalises to the
left-hand column.

| On the rail | SHA calls it | Others may call it |
|---|---|---|
| Benefit | Benefit (`SHA-07`, `PMF-07`) | Package, category, class |
| Sub-benefit / category | Sub-benefit, or "sub-package" (`SHA-07-SC-01`) | Sub-package, benefit group |
| Procedure | Intervention (`SHA-07-001`) | Service, item, tariff line, product |
| Service | Intervention component | Sub-service, activity |
| Protocol | Session or regimen rules | Care pathway, clinical protocol |
| Drug / item | Product | Item, consumable, SKU |

  Intervention is SHA's word. A private insurer's catalogue does not contain interventions, it
  contains services and items priced against a tariff. Writing the rail's docs in one payer's
  dialect makes every other integration read like an exception. Where a payload uses the payer's
  own name, the examples below keep it, and this table is how you map it back.

The traversal is what an operator sees as three screens. In your code it is one graph, and the
same query with a different member, facility or cover comes back different.

<ApiRef method="GET" path="/v1/benefits/coverage/" to="/rail-api/02-verify-cover" keep="benefit code">
  Level one of the tree: the benefits this member's cover carries, each with the parent codes needed to rebuild the chain. It is paginated, so read every page.
</ApiRef>

Read three things off that response.

The tree is per member. `beneficiary_cr_id` is a parameter, not a filter you could apply
afterwards. Two members at the same facility get different trees, and caching one member's tree
as "the benefit catalogue" is how a patient gets offered something they are not entitled to.

Several funds share one tree. `PMF-*` and `SHA-*` codes arrive interleaved, sorted by code
rather than grouped by fund. Group them yourself, and label the fund on screen, because a
clinician choosing "Inpatient" needs to know whether they are spending the national benefit or
the employer fund.

The pages are small by default. You get `count: 30` over `totalPages: 2` here, so a sync that
reads page one only ships two thirds of a tree and looks like it worked.

## What a procedure declares

<ApiRef method="GET" path="/v1/benefits/{sub_benefit_code}/interventions/" to="/rail-api/02-verify-cover" keep="procedure code">
  The billable procedures under one category, with every rule that governs them: tariffs per facility level, preauthorisation flags, payment mechanism, multi-session.
</ApiRef>

This is the densest object in the step. Every field on it changes behaviour somewhere later, and a
payer may declare any of them higher up instead, in which case what arrives here is what it
inherited.

<Wire
  rows={[
    { call: "level2Tariff … level6Tariff", does: "The price depends on the facility's level. A null at a level means not payable there at all, not free.", keep: "the tariff for YOUR level" },
    { call: "overallTariff", does: "0.00 alongside populated level tariffs does not mean free. It means price is resolved per level, so never quote this field to a patient.", keep: "—" },
    { call: "needsPreauth", does: "Sends you to 06 · Preauthorize before the work may be done. Readable here, hours before a patient is on a table.", keep: "the flag" },
    { call: "needsManualPreauthApproval", does: "A human has to approve, not just a system. That is a different waiting time and should be a different message on screen.", keep: "the flag" },
    { call: "requiresRenalPreauth, requiresOncologyPreauth", does: "Speciality-specific gates on top of the general one. Absent from most procedures, decisive on the ones that carry them.", keep: "the flags" },
    { call: "paymentMechanism", does: "FEE FOR SERVICE prices per item; PER DIEM prices per day. It changes what your billing screen even asks for.", keep: "the mechanism" },
    { call: "isMultisession", does: "One authorisation spanning several sessions, such as dialysis or chemotherapy cycles. Do not open a new case per session.", keep: "the flag" },
    { call: "activeForUhc", does: "Whether this procedure is live for the UHC fund specifically. False here while the cover is UHC means it is not payable.", keep: "the flag" },
  ]}
/>

  A procedure carries more than one price. Alongside the general level tariffs it may hold a separate
  set for a public-officer fund, and a fallback set that applies after a configured number of days.
  Which one applies depends on the fund the item is being billed to, and that fund is the effective
  coverage you froze earlier rather than a property of the procedure.

  So the price of an item depends on three things: the procedure, the facility level, and the cover
  it is billed against. Change the cover at billing and the price can change with it.

  Tariffs are keyed by facility level, and the level comes from the facility context you set at
  [00 · Authenticate](/rail/stops/Authenticate). A wrong or stale facility code does not fail
  here. It silently prices every line against another level's tariff, so read the level from the
  registry rather than storing it.

## The calls that walk it

<ApiRef method="GET" path="/api/v2/patients/{unique_patient_id}/eligibility/{selection_ref}/benefits" to="/rail-api/02-verify-cover" keep="code, access_point">
  Every level of the tree. Omit `parent_code` for the top level, then pass the code you chose to descend. `search` crosses levels, and `access_point` narrows a level to what is payable in the visit you are in.
</ApiRef>

The handle is the `selection_ref` from `covers/select`, not an eligibility reference: v2 has none.
Reading the tree also takes no consent proof — the proof is redeemed later, at
`POST /api/v2/claims/authorize`.

  Several benefits are not balance-tracked at all, among them ambulance and emergency, outpatient
  primary care, critical care and eye health. There is no pool to subtract from, so a screen
  that renders a balance for one of these is showing a number that means nothing.

  Public-officer schemes narrow it further, showing balances for a limited set of packages by
  default: outpatient, inpatient, eye, oral, maternity inpatient and excess-of-loss. Anything
  outside that list has no meaningful figure either.

  So a benefit has four honest states rather than two: available, nearly exhausted, exhausted,
  and not balance-tracked. Collapsing the fourth into "0.00" is the mistake to avoid.

  `allocated` and `available` come back null on procedures with no monetary ceiling, per-diem
  inpatient care being the usual case. Rendering null as `KES 0.00` tells a clinician the patient has
  nothing left, which is the opposite of what it means. Show "not applicable" and keep the
  distinction.

## Most limits count visits rather than money

Anyone arriving from private insurance has to unlearn this one. A limit here is as likely to be a
number of times as a sum of money.

<Wire
  rows={[
    { call: "Count-based", does: "One per year, one per week, dialysis twice weekly up to a yearly maximum. The limit is a tally of sessions or visits.", keep: "utilised visits, allocated visits" },
    { call: "Money-based", does: "A monetary ceiling per benefit, the shape private insurance uses throughout.", keep: "utilised amount, allocated amount" },
    { call: "Both, on one member", does: "Public-officer add-on covers behave like traditional insurance, so a member can hold count-based limits on their standard cover and money-based limits on the add-on, at the same time.", keep: "both, per cover" },
  ]}
/>

A balance screen that only knows how to render currency will show nothing useful for most
benefits. Read which kind you are holding before you format it.

Some employer schemes carry their own fund balances alongside the national ones. Where a cover
exposes them, they are read per benefit rather than per procedure.

<ApiRef method="GET" path="/api/v1/patients/benefits/utilization" to="/rail-api/02-verify-cover" keep="utilised, allocated">
  What has been used against a benefit, by patient and intervention code. It takes `patient_id` and `intervention_code` as query parameters and answers 400 without both.
</ApiRef>

  This one is not scoped to a `selection_ref`, so it does not answer "what is left on the cover I
  just froze onto this encounter". Read it as a utilisation figure for a patient and an
  intervention, and do not present it as the balance of the selected cover.

  The balance calls that were scoped that way — the per-procedure balance under an eligibility
  reference, and per-cover balances — answer 404 on dev. Everything above about untracked benefits,
  nulls and counts still holds for the figures you do get; there is simply no selection-scoped route
  to get them from today.

## What you keep

<Wire
  rows={[
    { call: "The procedure code", does: "The unit everything later prices against, with its access point and payment mechanism.", keep: "code, access_point" },
    { call: "The resolved chain", does: "Which level declared each attribute. It is what lets you explain a refusal rather than report one.", keep: "benefit → category → procedure" },
    { call: "The balance you read", does: "Where one exists. Note whose limit it was, and when it renews.", keep: "available, limit_applies_to" },
  ]}
/>

## What breaks here

<CostTable
  rows={[
    {
      where: "Bill",
      symptom: "\"The line was refused and the code looks correct\"",
      cause:
        "Access point mismatch. An inpatient-only procedure was selected during an outpatient visit, and nothing failed at selection time.",
    },
    {
      where: "Preauth",
      symptom: "\"Nobody told us this needed approving\"",
      cause:
        "The preauthorisation flag was declared somewhere in the chain and only the leaf was read. Resolve attributes against the whole chain.",
    },
    {
      where: "Any picker",
      symptom: "\"A clinician cannot find dialysis\"",
      cause:
        "Drill-down alone. A search that returns leaves with their path is the other half of the navigation.",
    },
    {
      where: "Bill",
      symptom: "\"It said covered, and the amount came back zero\"",
      cause:
        "A null allocated amount rendered as zero, or a household limit already spent. Null is not zero, and not every benefit is balance-tracked.",
    },
  ]}
/>

## What carries forward

The procedure code and its access point go to [06 · Preauthorize](/rail/stops/Preauth) where approval is
required, and to [07 · Bill](/rail/stops/Bill) where it is priced.

  Which cover pays: the covers a member holds, who contributes to each, what gets frozen onto the
  encounter, and when that choice stops being changeable.

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

Task: walk the benefit tree for a selected cover, and read a balance.

THE TREE
  Benefit -> sub-benefit/category -> procedure -> service/protocol/item.
  It starts at the benefit and is resolved PER MEMBER and PER COVER. Do not cache one
  member's tree as "the catalogue".
  There is no call that returns the whole tree. ONE route returns every level:
    GET /api/v2/patients/{unique_patient_id}/eligibility/{selection_ref}/benefits
    no parent_code  -> the top level
    parent_code=<code chosen above>  -> that node's children
    also takes: search (crosses levels), access_point, page, page_size
  The handle is selection_ref from POST /api/v2/patients/{unique_patient_id}/covers/select.
  There is no eligibility_ref in v2, and reading the tree takes no consent proof.
  Do not assume a fixed depth: follow what comes back, and offer select on any node with no
  children.
  Responses may answer below the top level. Read parent codes and rebuild the chain rather
  than trusting position.

ATTRIBUTES CAN BE DECLARED AT ANY LEVEL
  One payer sets preauthorisation at the BENEFIT level; another sets it per PROCEDURE, with
  neighbours differing. The same is true of access point, document requirements and
  combination rules.
  Carry a resolved context down the walk: at each level merge what that level declares over
  what you inherited, take the most specific value, and keep which node declared it so a
  refusal can be explained.

BEFORE SELECTING A PROCEDURE, READ FOUR THINGS
  code, access_point (filter to the CURRENT visit), needs_preauth, payment_mechanism.
  An inpatient-only procedure chosen in an outpatient visit is refused at Bill, not here.

BALANCES
  A sideways question, not a level. There is no selection-scoped balance route on the rail
  today: GET /api/v1/patients/benefits/utilization takes patient_id + intervention_code and
  is NOT scoped to the cover you selected. Do not present it as the selected cover's balance.
  NOT EVERY BENEFIT IS BALANCE-TRACKED. Render a "not tracked" state; never show 0.00 for
  an untracked benefit.
  Limits are as often COUNTS as money. Detect which you hold before formatting.
  Informational, not a gate: an absent balance must not block selection, the visit or the
  claim.

NAVIGATION
  Pair drill-down with a cross-level search that returns leaves and shows the path above
  each hit.`}</AgentPrompt>
