# 02 · Verify cover

  This is where an identified person becomes a claim someone will pay. The answer is not a yes or a
  no; it is a named cover, plus the items that cover will pay for at this facility today.

## What this step is

[01 · Identify](/rail/stops/Identify) told you who is in front of you. This step answers the money
question, and it asks two separate questions to do it. Do not run them together.

<Wire
  rows={[
    { call: "Which cover pays?", does: "A member holds covers, plural. Someone has to say which one today's visit runs against, and that choice has to be visible rather than guessed.", keep: "the selected cover" },
    { call: "What does it pay for?", does: "Benefits, then categories, then procedures, each with an access point, a payment mechanism, whether it needs preauthorisation, and a balance.", keep: "procedure code" },
  ]}
/>

Keeping them separate is what stops a member with nine active covers waiting on nine balance checks
before anyone has asked which one they are using.

They are also ordered. Coverage, meaning whether this member is paid up and current, is settled
first by the systems that handle contributions. Only then is it worth asking what a cover
makes available here. Calling both at once, or reading the second before the first has answered,
produces a benefit list you cannot trust.

The rule that makes it work is that a person is not covered. A person holds covers, and exactly
one of them pays for this visit.

Two things are worth knowing before the detail starts, because they decide how much of this you
have to build yourself.

<Wire
  rows={[
    { call: "Your part is small", does: "List the covers, let a person choose one, send the choice back. Which fund pays, which tariff applies, whose balance is read and how the claim is assessed are all the rail's problem.", keep: "one call" },
    { call: "Nothing here is final", does: "The cover chosen at visit start is a default. It can be changed per item and changed again until the request is submitted.", keep: "room to correct" },
  ]}
/>

Both are expanded below, under [what you are responsible for](#the-multiple-cover-case) and
[when the choice becomes final](#when-the-cover-choice-becomes-final). If you read nothing else on
this page, those two sentences are the ones that change how you build.

### The four moves

There are four of them, and the split between the first two is the part that matters most.

<BuildSequence
  steps={[
    {
      do: "List the covers",
      detail: (
        <>
          Every scheme this member can use here, with who contributes to it and how long it
          runs. It is light enough to render a chooser without waiting on{" "}
          <a href="/rail-api/02-verify-cover">balances</a>.
        </>
      ),
    },
    {
      do: "Select one, visibly",
      detail: (
        <>
          An operator chooses. <strong>Do not auto-select silently.</strong>{" "}
          This is a financial decision, and nobody can audit a choice they cannot see.
        </>
      ),
    },
    {
      do: "Walk down to a billable service",
      detail:
        "Benefit, then category, then procedure. The procedure is the unit that carries the access point, the payment mechanism and the preauthorisation flag.",
    },
    {
      do: "Check the balance where there is one",
      detail: (
        <>
          Many benefits are not balance-tracked, so there is often nothing to check and that is
          normal. Where a figure does exist,{" "}
          <a href="/rail-api/02-verify-cover">read it</a> before quoting an amount, because
          limits can apply to the whole household rather than this member, so one that looks
          generous may already be partly spent.
        </>
      ),
    },
  ]}
/>

None of that is a gate. A missing balance does not block care and rarely blocks a claim. It means
this benefit is not tracked that way. Read it when it is there, say so plainly when it is not, and
carry on.

If you only remember one thing: <u>the cover is chosen once, explicitly, and everything
priced afterwards hangs off that choice.</u>

### What it looks like at the desk

Three ways to look at the step: the screens, the sequence and the branch points.

<Wireframe
  title="Member covers"
  actor="Front desk · after Identity"
  rows={[
    [
      { label: "This member is covered", kind: "field", tone: "positive", span: 4, from: "coverage.status", note: "One banner for the whole member. Below it, one row per cover." },
    ],
    [
      { label: "Jubilee Health · active", kind: "text", swatch: 1, span: 2, from: "payer, scheme, category", note: "A private insurer. Member demo-20-1234-00 · category demo-20-CAT A · relationship SPOUSE" },
      { label: "Until Feb 20, 2042", kind: "text", swatch: 1, span: 1, from: "validity" },
      { label: "Use this cover", kind: "action", span: 1 },
    ],
  ]}
  caption={<>The simple case, and also the common one: one usable cover, selected and carried forward. The banner is the verdict on the member, and the row beneath it is the cover that will pay.</>}
/>

The same screen when nothing pays.

<Wireframe
  title="Member covers"
  actor="Front desk · after Identify"
  rows={[
    [
      { label: "This member is not covered", kind: "field", tone: "negative", span: 4, from: "coverage.status", note: "Red banner, and it never appears on its own" },
    ],
    [
      { label: "Contributions not up to date", kind: "field", tone: "attention", span: 2, from: "coverage.reason", note: "Why, in the payer's words" },
      { label: "Member may pay via the payer portal or USSD, then retry", kind: "field", tone: "attention", span: 2, from: "coverage.message", note: "What the member can do about it, right now" },
    ],
    [
      { label: "Continue as cash", kind: "action", span: 2, note: "The identity is still resolved. Care is still possible." },
      { label: "Retry coverage check", kind: "action", span: 2, note: "After the member pays. Nothing needs re-identifying." },
    ],
  ]}
  caption={<>A red banner is not the end of the encounter. The patient exists and is identified, only the money has failed, and the response carried both the reason and a remedy.</>}
/>

With a cover chosen, the step continues downward: benefits, then categories, then the billable unit.

<Wireframe
  title="Available categories"
  actor="Clinical user · cover selected"
  rows={[
    [
      { label: "Search", kind: "field", span: 3, note: "These lists are long. A picker without search is unusable at a desk." },
      { label: "Filter by access point", kind: "field", span: 1, from: "access_point", note: "OP · IP · both" },
    ],
    [
      { label: "Inpatient services · SHA-07-SC-01", kind: "text", swatch: 2, span: 2, from: "sub_benefit_code", note: "National scheme. SHA calls this a sub-package." },
      { label: "Inpatient only", kind: "text", swatch: 2, span: 1, from: "access_point", note: "Choosing this inside an outpatient visit fails at Bill, not here" },
      { label: "View procedures", kind: "action", span: 1 },
    ],
    [
      { label: "Inpatient · PMF-07-SC-01", kind: "text", swatch: 3, span: 2, from: "sub_benefit_code", note: "Employer fund, same shape, different pot" },
      { label: "Inpatient only", kind: "text", swatch: 3, span: 1, from: "access_point" },
      { label: "View procedures", kind: "action", span: 1 },
    ],
    [
      { label: "Outpatient consultation · JUB-OP-01", kind: "text", swatch: 4, span: 2, from: "sub_benefit_code", note: "Private insurer. Its catalogue holds services and items, not interventions." },
      { label: "Outpatient", kind: "text", swatch: 4, span: 1, from: "access_point" },
      { label: "View services", kind: "action", span: 1 },
    ],
  ]}
  caption={<>Access point is what catches people on this screen. An inpatient-only category selected during an outpatient visit is refused three stops later, and the error there mentions codes rather than access points.</>}
/>

<Wireframe
  title="Available procedures · Inpatient"
  actor="Clinical user"
  rows={[
    [
      { label: "Management of medical cases", kind: "text", swatch: 2, span: 1, from: "code", note: "SHA-07-001, an intervention in SHA's vocabulary" },
      { label: "IP", kind: "text", span: 1, from: "access_point" },
      { label: "Preauth: No", kind: "field", tone: "positive", span: 1, from: "needs_preauth", note: "A Yes here sends you to 06 · Preauthorize before anything may be done" },
      { label: "Per diem", kind: "text", span: 1, from: "payment_mechanism", note: "Priced per day, not per item" },
    ],
    [
      { label: "Management of medical cases", kind: "text", swatch: 3, span: 1, from: "code", note: "PMF-07-001, the same name on an employer fund, with its own tariffs" },
      { label: "IP", kind: "text", span: 1, from: "access_point" },
      { label: "Preauth: Yes", kind: "field", tone: "negative", span: 1, from: "needs_preauth", note: "The same clinical work, gated differently by a different pot" },
      { label: "Fee for service", kind: "text", span: 1, from: "payment_mechanism" },
    ],
    [
      { label: "Select", kind: "action", span: 2, note: "This is the unit everything later prices against" },
      { label: "View balance", kind: "action", span: 2, note: "Do this before promising anything to the patient" },
    ],
  ]}
  caption={<>Four attributes decide how the rest of the encounter behaves: the code, the access point, whether preauthorisation is required, and how it is paid. The same clinical work can differ on all four between two funds, which is why the fund belongs on the row.</>}
/>

<Wireframe
  title="Procedure balance"
  actor="Clinical user · balance check"
  rows={[
    [
      { label: "Available to member: Yes", kind: "field", tone: "positive", span: 2, from: "available", note: "The answer the desk is waiting for" },
      { label: "Next available: Aug 13, 2026", kind: "text", span: 2, from: "next_available_on", note: "When a spent limit renews" },
    ],
    [
      { label: "Limits apply to: HOUSEHOLD", kind: "field", tone: "attention", span: 4, from: "limit_scope", note: "Not this member. A sibling's admission can exhaust this." },
    ],
    [
      { label: "Utilised: 22 visits · KES 11,237.80", kind: "text", span: 2, from: "utilisation" },
      { label: "Allocated / available: not applicable", kind: "text", span: 2, from: "allocated", note: "Per-diem procedures often carry no monetary ceiling. Absent is not zero." },
    ],
  ]}
  caption={<>Two fields on this screen cause most disputes: whose limit it is, and whether a blank means unlimited or unknown. Render both explicitly rather than defaulting them to zero.</>}
/>

<Mermaid chart={`sequenceDiagram
    autonumber
    actor D as Front desk
    participant H as Your HMIS
    participant R as The rail
    participant P as Payer

    H->>R: Eligibility for this patient at this facility
    R->>P: Coverage and contribution status
    P-->>R: schemes[], one entry per scheme per contributor
    R-->>H: Member, covers, banner status
    D->>H: Selects one cover
    H->>R: Benefits for that cover
    R-->>H: Categories, filtered by access point
    H->>R: Procedures in the chosen category
    R-->>H: Codes, access point, preauth flag, payment mechanism
    H->>R: Balance for the chosen procedure
    R-->>H: Utilised, available, whose limit it is
    Note over H,R: Keep the cover selection and the procedure.<br/>Everything after this prices against them.
`} />

<Mermaid chart={`flowchart TD
    A["Identity resolved at 01"] --> B["Eligibility check"]
    B --> C{Any cover active?}
    C -- No --> D["Red banner + reason + remedy<br/>continue as cash, or retry after payment"]
    C -- Yes --> E["List every cover:<br/>scheme x contributor"]
    E --> F["Operator selects one, visibly"]
    F --> G["Benefit to category to procedure"]
    G --> H{Access point matches the visit?}
    H -- No --> I["Wrong category.<br/>Fails at Bill if it proceeds"]
    H -- Yes --> J{Balance available?}
    J -- No --> K["Show next available date.<br/>Check whose limit it is first"]
    J -- Yes --> L{Needs preauthorisation?}
    L -- Yes --> M["06 · Preauthorize before anything is done"]
    L -- No --> N["Carry on to 03 · Consent"]
`} />

## A member can hold many covers, and be a dependant on some of them

This is the part that surprises people, so it is worth stating plainly before any payload.

<Wire
  rows={[
    { call: "One payer, several schemes", does: "A single payer can grant more than one scheme at once, such as UHC and SHIF, with different windows and different rules.", keep: "one row each" },
    { call: "Several payers", does: "A member may hold covers from more than one payer: a national scheme, an employer scheme, a private insurer.", keep: "one row each" },
    { call: "Principal or dependant, per cover", does: "On some covers the member is the contributor. On others they are somebody's dependant, and both can be true of the same person on the same day.", keep: "memberType" },
  ]}
/>

So the list is a product rather than a set. There is one row per scheme, per contributor, which
means a member with three contributors across three schemes has nine rows, all of them valid.

  Without it, nine rows look like a duplication bug and an operator picks the first one. With it,
  the screen reads correctly: *SHIF, contributed by her employer* is a different cover from
  *SHIF, contributed by her father*, with a different policy number and a different balance.

`memberType` tells you which case you are in: `PRIMARY` when the member contributes,
`BENEFICIARY` when they are covered through someone else. On a beneficiary row,
`principalContributor` names the person whose cover it is, their relationship and their
employer. Store it with the encounter: every audit conversation about a dependant's claim opens
with "under whose cover?".

On screen, that looks like this.

<Wireframe
  title="Member covers"
  actor="Front desk · after Identify"
  rows={[
    [
      { label: "This member is covered", kind: "field", tone: "positive", span: 4, from: "coverage.status", note: "The verdict on the member. Every row beneath it is a separate cover." },
    ],
    [
      { label: "UHC · covered", kind: "text", swatch: 1, span: 2, from: "schemes[].schemeName", note: "Principal contributor: the member herself" },
      { label: "Until Sep 30, 2034", kind: "text", swatch: 1, span: 1, from: "coverage.endDate" },
      { label: "Use this cover", kind: "action", span: 1 },
    ],
    [
      { label: "SHIF · covered", kind: "text", swatch: 1, span: 2, from: "schemes[].schemeName", note: "Principal contributor: the member herself" },
      { label: "Until Sep 10, 2026", kind: "text", swatch: 1, span: 1, from: "coverage.endDate" },
      { label: "Use this cover", kind: "action", span: 1 },
    ],
    [
      { label: "SHIF · covered", kind: "text", swatch: 1, span: 2, from: "memberType: BENEFICIARY", note: "Same scheme, but the contributor is a parent. The member is a dependant on this one." },
      { label: "Until Sep 10, 2026", kind: "text", swatch: 1, span: 1, from: "coverage.endDate" },
      { label: "Use this cover", kind: "action", span: 1 },
    ],
    [
      { label: "TSC · covered", kind: "text", swatch: 1, span: 2, from: "schemes[].schemeName", note: "An employer scheme, contributed to by a parent. It has its own balances." },
      { label: "Until Jun 30, 2027", kind: "text", swatch: 1, span: 1, from: "coverage.endDate" },
      { label: "Use this cover", kind: "action", span: 1 },
    ],
    [
      { label: "Jubilee Health · active", kind: "text", swatch: 1, span: 2, from: "payer, scheme, category", note: "A private insurer. Member number demo-20-1234-00 · category demo-20-CAT A · relationship SPOUSE" },
      { label: "Until Feb 20, 2042", kind: "text", swatch: 1, span: 1, from: "validity" },
      { label: "Use this cover", kind: "action", span: 1 },
    ],
  ]}
  caption={<>Five rows for one person, all of them valid. The rows are uncoloured on purpose, because green belongs to the banner, which is the verdict on the member, rather than to the covers underneath it. What distinguishes a row is its payer and its contributor, so say those in words.</>}
/>

### There is no ranking, so do not build one

A member with several covers is not a puzzle with a correct answer. Pick any of them and you see
the benefits that cover supports. The covers frequently overlap, since the same eye-health benefit
can exist on two of them, so there is nothing to sort and no precedence to apply.

<Wire
  rows={[
    { call: "What choosing a cover does", does: "Scopes the benefit tree to what that cover supports. A different cover means a different tree, not a better or worse one.", keep: "the tree you get" },
    { call: "What it does not do", does: "It does not rank, exclude or consume anything. Selecting a cover is not spending it.", keep: "—" },
    { call: "Overlap is normal", does: "Two covers can both carry the same benefit, with different limits and tariffs. Neither is the right one in the abstract.", keep: "compare when it matters" },
  ]}
/>

So the chooser has one job, which is to show what each cover is and who contributes to it, then let
a person decide. Group rows by payer if that helps the eye, but do not pre-select across payers and
do not imply an order that the rail does not enforce.

  Which payer each row belongs to, and who contributes to it. A list of five schemes without those two
  facts cannot be used correctly by anyone, and the operator will pick the first row.

Two consequences follow from the same member holding several covers.

A dependant can be covered under more than one principal at the same time. A child whose mother
holds one public-officer fund and whose father holds another comes back with covers from both
households. One of them is used for a given item, and which one decides the tariff, the limits, the
required documents and the combination rules, so the choice is not cosmetic. Nor is it irreversible:
see [when the choice becomes final](#when-the-cover-choice-becomes-final).

Benefits are also filtered by member type. Principal, spouse and child see different things on the
same scheme: maternity is hidden from child dependants regardless of gender, and paediatric benefits
disappear at 18. The same scheme genuinely offers the mother and the child different sets, so do not
cache one member's benefit list and reuse it for a relative.

  What should happen when a member holds two covers with different validity windows, or one has
  expired, is not yet defined. Until it is, show both covers with their dates and let a person
  decide, rather than encoding a rule that may turn out to be wrong.

  A member may legitimately hold a public-officer cover that you cannot use. Empanelment is per
  facility, so if the site you are calling from is not empanelled for that fund, the cover comes back
  inactive with a reason, and its benefits are absent from the benefit list entirely.

  That is the correct answer rather than a fault, and it is why an empty benefit list is worth
  reading carefully. The member is covered, but this facility is not a place where that cover pays.
  Surface the reason on the row and let the operator select a different cover, rather than showing
  an unexplained empty list.

## The household behind the covers

A cover usually belongs to a household rather than to a person. That is where the extra covers on
the previous screen come from: a member appears on their own cover as principal, and on a relative's
cover as a dependant. One person, one day, two entries.

<Wireframe
  title="Household"
  actor="Front desk · principal's record"
  rows={[
    [
      { label: "Name", kind: "text", span: 1, note: "First name in full, the rest masked" },
      { label: "Registry identifier", kind: "text", span: 1, note: "unique_patient_id, masked" },
      { label: "Date of birth", kind: "text", span: 1 },
      { label: "Relationship", kind: "text", span: 1, from: "relationship" },
    ],
    [
      { label: "Margaret N***", kind: "field", tone: "positive", span: 1, from: "principal", note: "The contributor. Their cover is the one the others sit on." },
      { label: "CR46***334-2", kind: "text", span: 1 },
      { label: "12 Mar 1984", kind: "text", span: 1 },
      { label: "PRINCIPAL", kind: "text", span: 1 },
    ],
    [
      { label: "Charles G***", kind: "text", swatch: 1, span: 1 },
      { label: "CR36***771-2", kind: "text", swatch: 1, span: 1 },
      { label: "3 Aug 1981", kind: "text", swatch: 1, span: 1 },
      { label: "SPOUSE", kind: "text", swatch: 1, span: 1, note: "Also a contributor on a cover of their own" },
    ],
    [
      { label: "Esther W***", kind: "text", swatch: 1, span: 1 },
      { label: "CR54***778-8", kind: "text", swatch: 1, span: 1 },
      { label: "26 Aug 2002", kind: "text", swatch: 1, span: 1 },
      { label: "CHILD", kind: "text", swatch: 1, span: 1, note: "Adult child, still a dependant on this cover" },
    ],
    [
      { label: "Brian K***", kind: "text", swatch: 1, span: 1 },
      { label: "CR54***779-4", kind: "text", swatch: 1, span: 1 },
      { label: "14 Feb 2011", kind: "text", swatch: 1, span: 1 },
      {
        label: "CHILD",
        kind: "text",
        swatch: 1,
        span: 1,
        note: (
          <>
            A minor. Under 18s are{" "}
            <a href="/rail/stops/Identify#proving-it-is-the-same-person">
              verified differently
            </a>
          </>
        ),
      },
    ],
    [
      { label: "Faith W***", kind: "text", swatch: 1, span: 1 },
      { label: "CR54***780-1", kind: "text", swatch: 1, span: 1 },
      { label: "9 Sep 2015", kind: "text", swatch: 1, span: 1 },
      { label: "CHILD", kind: "text", swatch: 1, span: 1 },
    ],
    [
      { label: "Household limits are shared", kind: "field", tone: "attention", span: 4, note: "Where a limit applies to the household, all five draw on the same pool, and one admission can exhaust what another needs." },
    ],
  ]}
  caption={<>Five people, one cover. Two of them contribute to covers of their own, which is exactly why this member's cover list has more rows than they expect.</>}
/>

An empty household is also a real answer.

<Wireframe
  title="Household"
  actor="Front desk · principal's record"
  rows={[
    [
      { label: "Esther W***", kind: "text", span: 2, from: "principal", note: "Principal on her own cover" },
      { label: "CR54***778-8", kind: "text", span: 1 },
      { label: "Current member", kind: "text", span: 1, note: "Already selected" },
    ],
    [
      { label: "No listed dependants", kind: "field", tone: "attention", span: 4, note: "A real answer, not a failed fetch. Do not retry it, and do not render it as an error." },
    ],
  ]}
  caption={<>Reaching a dependant through a principal is covered at 01 · Identify. What matters here is that their spending may come out of the same limit as everyone else's.</>}
/>

## One cover, or several

Everything so far has assumed you pick a cover and move on. That is the common case and it is worth
stating separately from the harder one, because the two behave differently and the difference decides
who pays.

### The single-cover case

One usable cover. Select it, and it becomes the effective coverage for the encounter: a snapshot
of that cover taken at the moment of choosing, stored against the authorisation. Everything after it
resolves against that snapshot rather than re-reading the member's covers, including the benefits,
the balances, the tariffs, the preauthorisation and the claim.

Snapshotting matters because coverage is live data. A member's contribution status can change during
an admission; the encounter should not silently re-price itself when it does.

### The multiple-cover case

A member may hold several usable covers at once: their own, plus one or more where they are a
dependant on somebody else's. All of them may be valid, and only one pays for a given item.

<Wire
  rows={[
    { call: "Chosen at visit start", does: "The cover selected when the encounter opens becomes the PRIMARY cover. It is the default for everything billed under this visit.", keep: "effective coverage" },
    { call: "Changeable at billing", does: "When an item is billed, the operator can bill it against a different valid cover, typically one where the patient is a dependant and the benefit sits with that contributor.", keep: "the cover on that item" },
    { call: "Recorded per item", does: "Each billed item and preauthorisation carries the scheme it was raised under, so one encounter can legitimately draw on two contributors.", keep: "scheme_code, scheme_name" },
  ]}
/>

It helps to picture where you are by this point. The member was identified, a cover was chosen when
the visit opened, and care has happened. You are now in the billing workflow, adding items to a bill
or a preauthorisation, and the operator can be offered the same choice again, because the cover on
those items is still theirs to set.

<Wireframe
  title="The selected cover will be used for billing"
  actor="Billing · one member, two contributors"
  rows={[
    [
      { label: "Margaret N*** · PRIMARY COVER", kind: "field", tone: "positive", span: 2, from: "principal_cr_no + badge", note: "Chosen at visit start. Eye health KES 60,000 · Inpatient KES 1,829,936 · Outpatient KES 299,500" },
      { label: "Charles G***", kind: "text", swatch: 1, span: 2, from: "principal_cr_no", note: "Also valid. Balances load when selected, so empty here means unread rather than zero." },
    ],
    [
      { label: "Items on this bill", kind: "text", span: 4, note: "Every item below is billed under the selected cover unless it is given its own" },
    ],
    [
      { label: "Spectacle magnifiers · SHA-05-030", kind: "text", span: 2, from: "procedure" },
      { label: "KES 2,343", kind: "text", span: 1, from: "amount" },
      { label: "Margaret N*** · TSC", kind: "field", tone: "positive", span: 1, from: "scheme + contributor" },
    ],
    [
      { label: "Frames · SHA-05-034", kind: "text", span: 2, from: "procedure" },
      { label: "KES 8,000", kind: "text", span: 1, from: "amount" },
      { label: "Margaret N*** · TSC", kind: "field", tone: "positive", span: 1, from: "scheme + contributor" },
    ],
    [
      { label: "Consultation · SHA-12-004", kind: "text", span: 2, from: "procedure" },
      { label: "KES 1,500", kind: "text", span: 1, from: "amount" },
      { label: "Margaret N*** · TSC", kind: "field", tone: "positive", span: 1, from: "scheme + contributor" },
    ],
  ]}
  caption={<>The green card is the cover in force, and every item inherits it. Badge it explicitly: an operator who cannot tell which of two covers is active will not trust either.</>}
/>

Now the operator changes their mind, perhaps because this benefit has more left on the father's
cover, or because the mother's limit is nearly spent. They pick the other card.

<Wireframe
  title="The selected cover will be used for billing"
  actor="Billing · after switching cover"
  rows={[
    [
      { label: "Margaret N***", kind: "text", swatch: 1, span: 2, from: "principal_cr_no", note: "No longer in force. Still valid, still selectable." },
      { label: "Charles G*** · PRIMARY COVER", kind: "field", tone: "positive", span: 2, from: "principal_cr_no + badge", note: "Now in force. Balances loaded for this contributor: Eye health KES 45,000 · Inpatient KES 2,000,000" },
    ],
    [
      { label: "Items on this bill", kind: "text", span: 4, note: "Re-priced against the new cover, so the amounts moved and the screen says so" },
    ],
    [
      { label: "Spectacle magnifiers · SHA-05-030", kind: "text", span: 2, from: "procedure" },
      { label: "KES 2,100", kind: "field", tone: "attention", span: 1, from: "amount", note: "Was 2,343, because a different fund carries a different tariff" },
      { label: "Charles G*** · TSC", kind: "field", tone: "positive", span: 1, from: "scheme + contributor" },
    ],
    [
      { label: "Frames · SHA-05-034", kind: "text", span: 2, from: "procedure" },
      { label: "KES 8,000", kind: "text", span: 1, from: "amount", note: "Unchanged on this one" },
      { label: "Charles G*** · TSC", kind: "field", tone: "positive", span: 1, from: "scheme + contributor" },
    ],
    [
      { label: "Consultation · SHA-12-004", kind: "text", span: 2, from: "procedure" },
      { label: "KES 1,500", kind: "text", span: 1, from: "amount" },
      { label: "Charles G*** · TSC", kind: "field", tone: "positive", span: 1, from: "scheme + contributor" },
    ],
  ]}
  caption={<>The same bill, a different payer behind it. One call moved the effective cover, the items re-priced, and every line now names the contributor it will be assessed against.</>}
/>

One call makes that switch. You send the cover the operator chose against the encounter, and the
rail updates the effective coverage, which is what every later balance, tariff and assessment reads
from. See
[the effective-coverage call](/rail-api/02-verify-cover) in the reference.

  List the covers, let the person at the desk choose one, and send that choice back. That is the
  whole of your responsibility for cover coordination.

  Everything underneath it is the rail's problem: which fund pays, which tariff applies to that fund,
  whose balance is read, how a dependant's cover relates to a contributor's, what a household limit
  has already consumed, and how the claim is assessed against the cover it carries. You do not
  reconcile payers, rank schemes or compute what is left. You surface a choice and you report it.

### What gets stored, and why it is scoped to a contributor

The chosen cover is persisted as a snapshot alongside the authorisation, and it carries the
contributor as well as the scheme.

<ApiRef method="GET" path="/api/v1/authorizations/{token}/effective-coverage" to="/rail-api/02-verify-cover" keep="scheme_code + principal_cr_no"
  run="/api/v1/authorizations/AUTH-TOKEN/effective-coverage">
  The cover this encounter is currently billing against, with the contributor behind it and any secondary coverages. You can change it until the request is submitted.
</ApiRef>

  Both effective-coverage calls — reading it and setting it — answer 404 on the development
  gateway today. What the snapshot means has not changed, and `covers/select` is what freezes the
  choice; treat the pair below as the shape to expect rather than a call to wire in now.

`principal_cr_no` is the field that does the work here, for three reasons.

<Wire
  rows={[
    { call: "Balances are read per contributor", does: "A member's balance data holds one entry per policy, keyed by the contributor behind it. The chosen contributor selects which entry is read.", keep: "principal_cr_no" },
    { call: "Reserved amounts are scoped the same way", does: "Approved preauthorisation amounts are subtracted per contributor, so a sibling's reservation under the same encounter does not eat this contributor's limit.", keep: "principal_cr_no" },
    { call: "Getting it wrong is silent", does: "Without the contributor, the first policy would be read, quietly returning a different person's balance. That is why an unmatched contributor is an error rather than a fallback when several policies exist.", keep: "—" },
  ]}
/>

One encounter holds more than one cover through `primary_coverage` and `secondary_coverages`. The
cover frozen at visit start is the primary, and a cover chosen for a particular item links to it as a
secondary. The structure exists so that "who paid for what" is answerable per item rather than per
visit.

  Do not model the cover as a single value on the visit. Model it as a default on the visit and an
  override per item, then show the fund on each line. A provider who bills two contributors in one
  encounter and cannot see which line went where will not be able to answer a query about either.

### The two views, side by side

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

    H->>R: Valid covers for this member here
    R-->>H: Covers, public and private, each with its contributor
    D->>H: Selects one at visit start
    H->>R: Freeze it as effective coverage (primary)
    R-->>H: Snapshot stored on the authorisation
    Note over H,R: Benefits, balances and tariffs now resolve<br/>against the snapshot, not live coverage.
    C->>H: Bills an item
    H->>R: Balance for this procedure, for this contributor
    R-->>H: What that contributor has left
    C->>H: Chooses a different cover for this item
    H->>R: Link it as a secondary coverage
    R-->>H: Item carries its own scheme and fund
`} />

<Mermaid chart={`flowchart TD
    A["Valid covers returned"] --> B{How many are usable?}
    B -- One --> C["Select it, and it becomes the primary<br/>effective coverage"]
    B -- "More than one" --> D["Group by payer, rank funds inside<br/>each, operator chooses"]
    D --> C
    C --> E["Benefits, balances and tariffs resolve<br/>against the snapshot"]
    E --> F{Does this item belong to<br/>a different contributor's cover?}
    F -- No --> G["Bill against the primary"]
    F -- Yes --> H["Select that cover for the item<br/>linked as a secondary coverage"]
    H --> I["Balance and reserved amounts read<br/>for THAT contributor"]
    G --> J["Item carries its scheme and fund"]
    I --> J
`} />

## When the cover choice becomes final

The most useful thing to know about cover selection is that it sets a default rather than a
commitment. Right up until you submit, it can be changed.

<BuildSequence
  steps={[
    {
      do: "At visit start, a default is set",
      detail:
        "The cover chosen when the encounter opens becomes the primary. Everything billed under the visit inherits it unless told otherwise.",
    },
    {
      do: "At billing, changeable per item",
      detail:
        "An item can be raised against a different valid cover, typically one where the patient is a dependant of another contributor. The item carries the fund it was raised under.",
    },
    {
      do: "Before submission, still changeable",
      detail:
        "A preauthorisation or a claim that has not been submitted can have its cover changed. Amounts re-resolve against the new fund, because tariffs and limits belong to the cover rather than to the procedure.",
    },
    {
      do: "On submission, fixed",
      detail:
        "Once submitted, the cover on that preauthorisation or claim is what it will be assessed against. Changing it afterwards means a new request, not an edit.",
    },
  ]}
/>

  Providers assume the first screen commits them, so they either stall the encounter while somebody
  works out the "right" cover, or they proceed and treat a wrong choice as an unrecoverable error.
  Neither is necessary. Open the visit on any valid cover, treat care as the priority, and settle the
  cover question before the request goes out.

Two things follow for the software.

Make the cover visible and editable on the request, not only on the visit. If the only place a
cover can be chosen is the screen that opens the encounter, every correction becomes a cancellation.
Put it on the preauthorisation and the claim, with the fund shown per item.

Re-price on change, and say so. Because a procedure carries a tariff per fund, switching cover can
change the amount. Recompute rather than carrying the old figure forward, and show the operator that
the number moved, since a silently changed amount is worse than a visibly changed one.

<Mermaid chart={`flowchart LR
    A["Visit opens<br/><i>cover set as default</i>"] --> B["Items billed<br/><i>per-item override allowed</i>"]
    B --> C["Preauth / claim drafted<br/><i>still changeable</i>"]
    C --> D{Submitted?}
    D -- "Not yet" --> C
    D -- Yes --> E["Fixed, and assessed against<br/>the cover on the request"]
`} />

## Once a cover is chosen

Everything above answers *which cover pays*. The second half of the question, what that cover pays
for, is a tree you walk, and it is long enough to have its own page.

  Benefits, categories and procedures, one level at a time. How the tree is shaped, why the same rule
  lives at different depths for different payers, what a procedure declares, and where balances come
  from, with the screens, the sequence and the calls to go with them.

## The call

Two calls answer the cover question, and they run in order: coverage first, then what that cover
makes available here. The calls that walk the benefit tree are on
[the next page](/rail/guides/Benefit-Tree).

<Wire
  rows={[
    { call: "GET · eligibility", does: "Member, cover and the flags later stops depend on. Answers whether this member is current before anything else is asked.", keep: "member identifier" },
    { call: "GET · coverage", does: "Every scheme the member is covered under, one entry per contributor. This is the response that returns several rows for one person.", keep: "schemes[], policy numbers" },
  ]}
/>

Open [**02 · Verify cover in the Rail API reference**](/rail-api/02-verify-cover) to
read or run them, with a **Try it** console for when the sandbox is live.

  The field names quoted below come from one payer's current responses, included so they are
  recognisable when you meet them. Other payers use their own names for the same things, and the rail
  is normalising them, so treat the names as illustrative and the shapes as real. Exact names come
  from the reference once the contracts are signed off.

### What an eligibility response carries

Every payer answers this call with the same three things, whatever they call them: who the member
is, which cover the answer was resolved against, and a set of flags that later stops depend on.
The reference has it field by field. What follows is what to do with the flags, because those are
the ones that catch people out.

<ApiRef method="GET" path="/api/v1/beneficiaries/{unique_patient_id}/eligibility" to="/rail-api/02-verify-cover" keep="member identifier">
  Member, contacts, cover and the flags later stops depend on, including the biometric and one-time-code flags that decide what Consent can offer.
</ApiRef>

  The `/api/v1/beneficiaries/…` calls on this page — eligibility, coverage, and the retired
  `covers/valid` — answer 404. The prefix is registered nowhere in the service, so nothing behind the
  gateway serves them. What is live is
  [`GET /api/v2/patients/{unique_patient_id}/covers`](/rail-api/02-verify-cover), which returns the
  covers, their schemes, `status` and `selectable`, and a `next_action`.

  It does not carry the biometric and one-time-code flags described below, and nothing else does
  either. That is by design rather than by omission: in v2 the factors on offer are decided
  server-side per consent request and surfaced by the launch, not fetched by the integrator. Read
  what follows as the shape of a payer's eligibility answer, and let
  [03 · Consent](/rail/stops/Consent) decide the factor.

Some fields here are not about cover at all, and they matter later.
`isBiometricsEnabled`, `isFingerprintEnrolled`, `otpOnlyAllowed` and `biometricStatus` decide
which consent factors are even offered at [03 · Consent](/rail/stops/Consent). A member who is
not enrolled for fingerprints cannot consent by fingerprint, and finding that out at Consent
looks like a broken scanner.

`benefits: []` is worth reading too. An empty array here does not mean "no benefits". It means
this response did not carry them, and you fetch them per cover in the calls below.

### Coverage: one answer per scheme, per contributor

This is the response that produces several rows for one person, and it behaves the same way whoever
the payer is. A national fund returns one entry per scheme per contributor; a private insurer returns
one per policy, and a member holding both gets both sets. Nothing deduplicates them for you, and
nothing ranks them.

<ApiRef method="GET" path="/api/v1/beneficiaries/{unique_patient_id}/coverage" to="/rail-api/02-verify-cover" keep="schemes[].policy.number">
  Every scheme the member is covered under, one entry per contributor. This is the response that legitimately returns five rows for one person.
</ApiRef>

Four things to read correctly, on any payer's version of it:

<Wire
  rows={[
    { call: "The list is not deduplicated", does: "Two entries naming the same scheme are two different covers when the contributor differs. Group by contributor when you render, and use the policy number as the row identity.", keep: "policy number" },
    { call: "Status is a code, not a word", does: "Payers signal covered with a code and a human-readable message beside it. Switch on the code; show the message. Never pattern-match the text.", keep: "status" },
    { call: "Two date ranges, not one", does: "The policy window and the coverage window are different things and can differ. Coverage is the one that decides whether today is paid for.", keep: "the coverage window" },
    { call: "An empty relationship is meaningful", does: "Where the member is the contributor, the relationship field is legitimately blank. Render it as \"self\", not as missing data.", keep: "member type" },
  ]}
/>

## Public-officer and teachers' covers are a different rule set

They look like another scheme in the list. They are closer to a parallel rule set, and four
differences matter enough that treating them as a variation produces claims that cannot be paid.

<Wire
  rows={[
    { call: "Benefits downgrade, they do not vanish", does: "Where a facility has no active contract of that type, public-officer benefits are hidden and the universal or standard equivalent is shown instead. An empty list is the wrong reading; a substituted list is the right one.", keep: "which set you are looking at" },
    { call: "Teachers' members are panel-restricted", does: "Outside their panel, they see only their standard benefits. The same member gets different answers at two facilities, correctly.", keep: "—" },
    { call: "Tariffs come from a different matrix", does: "Public-officer tariffs are different figures, published separately. If the member is a public-officer member, the standard tariffs do not apply, and reading the standard matrix misprices every one of those claims.", keep: "the public-officer tariff" },
    { call: "Combination rules differ", does: "The same procedure codes, different rules about what may be billed together.", keep: "—" },
  ]}
/>

  A procedure carries both sets of figures. The cover being billed decides which one applies, so the
  same code, at the same facility, on the same day, prices differently for two patients. That is the
  rule rather than an inconsistency to reconcile.

## What you keep

<Wire
  rows={[
    { call: "The selected cover", does: "Which scheme and which contributor. Tariff, limits, documents and combination rules all follow from it.", keep: "policy number + memberType" },
    { call: "The principal contributor", does: "On a beneficiary row, whose cover is being used. The first question any audit asks.", keep: "crNumber, relationship" },
    { call: "The procedure code", does: "The unit everything later prices against, with its access point and payment mechanism.", keep: "code, access_point" },
    { call: "The effective coverage", does: "The snapshot of the chosen cover, including the contributor behind it. Everything prices against this rather than against live coverage.", keep: "scheme_code + principal_cr_no" },
    { call: "The selection reference", does: "The handle the benefit tree is read against, returned by covers/select. Short-lived and visit-scoped by design: re-run rather than caching it across days. There is no separate eligibility reference in v2.", keep: "selection_ref" },
    { call: "Consent hints", does: "Biometric enrolment and OTP flags, read here and needed at the next stop.", keep: "isFingerprintEnrolled, otpOnlyAllowed" },  ]}
/>

## What breaks here

<CostTable
  rows={[
    {
      where: "Cover selection",
      symptom: "\"There are nine identical covers, which one do I pick?\"",
      cause:
        "The contributor is not rendered. Each row is a scheme-and-contributor pair; without the contributor they look like duplicates and the operator picks the first.",
    },
    {
      where: "Cover selection",
      symptom: "\"We picked a cover and the claim was rejected as not the member's\"",
      cause:
        "A BENEFICIARY row was treated as the member's own. The claim has to carry the principal contributor for that cover, not the patient.",
    },
    {
      where: "Cover selection",
      symptom: "\"The system says not covered, so we sent them away\"",
      cause:
        "coverage.reason and message were dropped instead of shown. Identity resolved; only the money failed, and the remedy was in the response.",
    },
    {
      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: "Bill",
      symptom: "\"It said covered, but the amount came back zero\"",
      cause:
        "A null allocated amount rendered as zero, or a household limit already spent by a relative. Read limit_applies_to before quoting a figure.",
    },
    {
      where: "Preauth",
      symptom: "\"Nobody told us this needed approving\"",
      cause:
        "needs_preauth was on the procedure and was not read. The flag is available at selection, hours before the patient is on a table.",
    },
    {
      where: "Consent",
      symptom: "\"The fingerprint reader is not working\"",
      cause:
        "The member is not enrolled for fingerprints. isFingerprintEnrolled said so at this stop.",
    },
    {
      where: "Balance screen",
      symptom: "\"The balance shown belongs to the wrong parent\"",
      cause:
        "The contributor was not sent with the balance request. Where a member has policies under several contributors, the first one is not a safe default, because it is a different person's limit.",
    },
    {
      where: "Claim query",
      symptom: "\"We cannot say which cover paid for which item\"",
      cause:
        "The cover was modelled as one value on the visit. It is a default on the visit and an override per item, and each item carries the scheme it was raised under.",
    },
    {
      where: "Bill",
      symptom: "\"The price changed when we switched cover\"",
      cause:
        "Correct. A procedure holds a tariff per fund, so the amount is a function of procedure, facility level and the cover being billed.",
    },
    {
      where: "Balance screen",
      symptom: "\"It shows zero left on emergency care\"",
      cause:
        "That benefit is not balance-tracked. There is no pool, so there is no figure, and rendering it as 0.00 tells a clinician the opposite of the truth.",
    },
    {
      where: "Balance screen",
      symptom: "\"The limit says 2 and the amount is blank\"",
      cause:
        "A count-based limit. Most limits here are numbers of visits or sessions rather than money, and a screen that only formats currency shows nothing useful.",
    },
    {
      where: "Cover selection",
      symptom: "\"We used the mother's cover and it was refused for the child\"",
      cause:
        "Benefits are filtered by member type, and a dependant may be covered under two principals. Whichever cover the request carries at submission is the one it is assessed against, so correct it before submitting rather than after.",
    },
    {
      where: "Preauth or Bill",
      symptom: "\"The document list on the procedure looked short\"",
      cause:
        "Requirements inherit from the benefit above it. Reading the leaf without resolving the chain misses everything set higher up.",
    },
    {
      where: "Next day",
      symptom: "\"The eligibility reference stopped working\"",
      cause:
        "It is visit-scoped and short-lived. Re-run eligibility rather than persisting the reference.",    },
  ]}
/>

One pattern runs through most of that list. The response said so and the screen did not. The
contributor, the access point, the limit scope, the preauth flag and the biometric flags are all
present at this stop, and every one of them, dropped here, becomes somebody else's problem later.

### Errors

<ErrorDictionary stop="entitle" />
## What carries forward

<Wire
  rows={[
    { call: "Selected cover + contributor", does: "Taken by Consent, Visit, Preauth, Bill and the claim. The single most consequential choice on this page.", keep: "Store on the encounter" },
    { call: "Procedure code", does: "Priced at Bill, approved at Preauth where required.", keep: "With its access point" },
    { call: "Consent hints", does: "They feed the factor decision at 03 · Consent. You do not make that decision, but these are among its inputs.", keep: "Pass forward, do not re-derive" },
  ]}
/>

**Next:** [03 · Consent](/rail/stops/Consent). With a cover chosen, the member has to confirm
they are present and willing.

<AgentPrompt title="Build eligibility and cover selection with an agent" filename="eligibility-and-cover.instructions.md">{`You are integrating an HMIS with the Savannah unified payer rail.

Task: implement stop 02, eligibility and cover selection. An identified patient becomes a
claim someone will pay.

ORDER MATTERS
  Coverage first (is the member paid up?), then benefits (what does the cover make
  available here?). Do not call them in parallel and do not read benefits before
  coverage has answered.

STEP 1: LIST COVERS
  Fetch eligibility for the patient at this facility.
  The response carries schemes[] with ONE ENTRY PER SCHEME PER CONTRIBUTOR. It is not
  deduplicated. The same person legitimately appears many times:
    memberType: PRIMARY      -> the member contributes to this cover
    memberType: BENEFICIARY  -> the member is covered through principalContributor
  A member may hold several schemes from one payer, and covers from several payers.
  Render every row with its principalContributor name and relationship. Without it the
  list looks like duplicates and operators pick the first row.
  Group rows by contributor. Use policy.number as the row identity, not schemeName.

STEP 2: SELECT ONE, VISIBLY
  An operator selects. Never auto-select silently: this is a financial decision and it
  must be auditable.
  There is NO precedence order between covers. Choosing a cover scopes the benefit tree to
  what that cover supports; covers often overlap, so nothing needs sorting. Do not
  pre-select across payers and do not imply a ranking.
  A dependant may be covered under MORE THAN ONE principal at once. Only one is used, it
  becomes the default for the visit, and it determines tariff, limits, documents and
  combination rules. It can be overridden per item and changed until the request is
  submitted; after submission it is fixed.
  Benefits are filtered by member type (principal / spouse / child) and by age. Never
  reuse one member's benefit list for a relative.
  Persist the selected policy number, the scheme, memberType, and the principal
  contributor's crNumber and relationship.

STEP 3: WALK TO A BILLABLE PROCEDURE, ONE LEVEL AT A TIME
  There is no call that returns the whole tree; a national scheme's procedure list alone is thousands
  of codes. ONE route returns every level, and the depth is a query filter:
    GET /api/v2/patients/{unique_patient_id}/eligibility/{selection_ref}/benefits
    no parent_code -> the top level; parent_code=<the code you chose> -> its children
    also takes: search (crosses levels), access_point, page, page_size
  selection_ref comes 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 — the proof is redeemed at
  POST /api/v2/claims/authorize.
  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 on every node and rebuild
  the chain rather than trusting position.
  Pair drill-down with a cross-level search that returns leaves and shows the path above
  each hit: a clinician searching "dialysis" does not know which benefit it sits under.
  ATTRIBUTES CAN BE DECLARED AT ANY LEVEL. A private insurer may set preauthorisation at
  the BENEFIT level ("inpatient benefit -> preauth required"); the national scheme sets it
  at PROCEDURE level and below, varying between neighbours in one category. The same is
  true of access point, document requirements and combination rules.
  So 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 you can explain a refusal.
  Balances are NOT a level, and there is no selection-scoped balance route today: the
  per-procedure balance under an eligibility reference answers 404. The nearest live call is
  GET /api/v1/patients/benefits/utilization (patient_id + intervention_code), which is not
  scoped to the selected cover.
  Read four attributes before selecting a procedure:
    code, access_point (OP/IP), needs_preauth, payment_mechanism
  Filter sub-benefits and procedures by the access point of the CURRENT visit. An
  inpatient-only procedure chosen in an outpatient visit is refused at Bill, not here.
  If needs_preauth is true, route to 06 · Preauthorize before anything is done.

STEP 4: CHECK THE BALANCE WHERE THERE IS ONE
  Read utilisation where you can get it. This is informational, NOT a gate: an absent
  balance must not block selection, the visit or the claim. Treat "no route to ask" the same
  way you treat "not balance-tracked" — as unknown, never as zero.
  NOT EVERY BENEFIT IS BALANCE-TRACKED. Ambulance and emergency, outpatient primary care,
  critical care and eye health have no pool. Render a fourth state, "not tracked",
  alongside available, nearly exhausted and exhausted. Never show 0.00 for an untracked
  benefit.
  Limits are as often COUNTS as money: visits or sessions per week or year. Detect which
  kind you hold before formatting; a currency-only renderer shows nothing for most
  benefits.
  limit_applies_to may be HOUSEHOLD rather than the member: a relative's spending can
  exhaust it.
  allocated and available may be null on per-diem procedures. NULL IS NOT ZERO. Render
  "not applicable"; never display null as KES 0.00.
  Show next_available_on when a limit is spent.

COVERAGE STATUS
  Switch on coverage.status codes, not on message text.
  When nothing is active, surface coverage.reason AND coverage.message together, and keep
  the encounter open: the identity resolved, so cash or another cover is still possible.

EFFECTIVE COVERAGE: THE PART MOST INTEGRATIONS MISS
  The cover chosen at visit start is FROZEN as a snapshot (the effective coverage) and
  stored against the authorisation. Benefits, balances, tariffs, preauthorisation and the
  claim all resolve against that snapshot, not against live coverage.
  The snapshot carries the CONTRIBUTOR (principal_cr_no), not just the scheme. Send it with
  every balance request: a member with policies under several contributors has one balance
  entry per contributor, and defaulting to the first reads a different person's limit.
  Approved preauthorisation amounts are also scoped per contributor, so a sibling's
  reservation in the same encounter must not reduce this contributor's remaining limit.
  At billing, an item may be raised against a DIFFERENT valid cover, typically one where
  the patient is a dependant of another contributor. Model the cover as a default on the
  visit plus an override per item, link the override as a secondary coverage, and show the
  fund on every line.
  THE CHOICE IS NOT FINAL UNTIL SUBMISSION. A drafted preauthorisation or claim can have
  its cover changed; expose that on the request itself, not only on the visit screen, so a
  correction is not a cancellation. Once submitted it is fixed, and a change then means a
  new request.
  Re-price when the cover changes, and show the operator that the amount moved. Tariffs
  belong to the cover, not to the procedure.
  A procedure carries a tariff per fund plus fallback tariffs. Price is a function of
  procedure + facility level + the cover being billed. Re-price when the cover changes.

CARRY FORWARD
  An inactive public-officer cover with a reason is a correct answer: empanelment is per
  facility, so a cover the member holds may not pay at your site and its benefits will be
  absent from the list. Show the reason; do not render an unexplained empty list.

  Selected cover + principal contributor, procedure code + access point, and the
  consent hints read here: isBiometricsEnabled, isFingerprintEnrolled, otpOnlyAllowed,
  biometricStatus. Those decide which factors Consent can offer.
  Treat the eligibility reference as visit-scoped and short-lived; re-run rather than
  caching it across days.`}</AgentPrompt>
