Skip to content
Updated Aug 11, 2026

Claims Lifecycle

A Claim is a request for payment for medical services under a policy, submitted by the member (reimbursement) or by a provider (direct settlement). Claims are submitted complete (there is no DRAFT); intake is a deliberately thin contract that returns 202 Accepted and adjudicates asynchronously.

Field reference: full columns, types and nullability live in the catalog: glossary terms Claim, ClaimLine, ClaimEvent, ClaimDocument, PriorAuth, ClaimSubmission. This page is the narrative.

The submission contract

ClaimSubmission carries only: the policy and term locators, the claimant party, an incident_date (the loss/service date), and an opaque document blob. The term is supplied by the caller, not derived; an absent incident_date currently defaults to 0001-01-01 rather than being rejected. The richer FNOL-style intake (diagnosis/procedure/provider/document lists) is aspirational.

Claim state machine

ClaimStatus = SUBMITTED · UNDER_REVIEW · PENDING_INFO · APPROVED · REJECTED · PAID · CLOSED. REJECTED is the only adverse status; there is no DENIED on a Claim (DENIED exists only on PriorAuth).

Each status change is written inside one service transaction that row-locks the claim, updates status, appends a ClaimEvent, and inserts a Kafka outbox row. The claims row holds only the current status; the full history is the append-only ClaimEvent trail.

TransitionOutbox event
→ UNDER_REVIEWclaim.review_required
→ APPROVEDclaim.approved
→ REJECTEDclaim.rejected
→ PAIDclaim.paid

As-built adjudication is a pass-through

The wired review path auto-approves at the billed amount. The rule-engine adjudicator that would compute per-line allowed amounts exists but is effectively dead code on this path; treat rule-driven adjudication as designed-but-not-active.

ClaimLine and amounts

Each Claim is itemised into ClaimLine rows (one per service). There is no monetary total on the claim header; a claim's total is the sum of its lines.

  • amount_claimed: what was billed.
  • amount_allowed: defaults to amount_claimed and is only reduced by rule-engine effects: 0 on DENY/EXCLUDE, capped at a benefit cap, or scaled (e.g. ×0.8). There is no contracted/plan rate and no CMS fee schedule.
  • amount_paid: set equal to amount_allowed on the PAID transition; member cost-sharing is applied to eligibility accumulators rather than netted into this column.
  • reason_code: the rule-engine effect's reason string (or the literal EXCLUDED); it is not an ANSI X12 CARC code.
  • element_id resolves to a PolicyElement (by static_id) for benefit/limit lookup.

Prior authorization

PriorAuth is a pre-approval for a procedure. Its lifecycle is SUBMITTED (on create) → PENDING/PENDING_REVIEWAPPROVED | DENIED. The intent is that a claim for a procedure needing prior auth but lacking it would be rejected, but the claims service does not enforce any prior-auth gate today (no lookup exists on the adjudication path), and that check likely belongs upstream in eligibility/enrollment. US-payer denial codes (e.g. CARC 197/CO) are not part of Olly's model - Olly is UK; there is no CARC/RARC surface anywhere in the service.

ClaimEvent: the audit trail

Every status change appends a ClaimEvent (from_status, to_status, actor, note). These are append-only and never modified; they are the claim's history. from_status is an empty string (not null) on the first event.

Invariants

  • The claim header stores the current status and no total; amounts live on ClaimLine.
  • REJECTED is the only adverse Claim status; DENIED is a PriorAuth status, not a Claim one.
  • One ClaimEvent + one outbox row are written per status change, in the same transaction as the status update.
  • amount_allowed ≤ amount_claimed; both default such that an un-adjudicated line is allowed at the billed amount.

Caveats

  • Not a US payer. CMS fee schedules, ANSI X12 CARC/RARC codes, and EDI 837/835 clearinghouse exchange (via Mirth) are not implemented; any such framing is aspirational. Intake is the thin JSON contract above, not an 837.
  • Appeals are not implemented: there is no appeal status, event, or re-review path in the service.
  • Socotra structural mismatch. Socotra has no runtime Claim; it models loss capture as an FNOL (draft → validated → onClaim → completed/rejected/discarded) distinct from the promoted claim. Olly's single Claim conflates loss-capture with adjudication, and APPROVED/PAID have no Socotra counterpart.

Olly Health Insurance Platform