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.
| Transition | Outbox event |
|---|---|
→ UNDER_REVIEW | claim.review_required |
→ APPROVED | claim.approved |
→ REJECTED | claim.rejected |
→ PAID | claim.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 toamount_claimedand is only reduced by rule-engine effects:0onDENY/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 toamount_allowedon thePAIDtransition; 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 literalEXCLUDED); it is not an ANSI X12 CARC code.element_idresolves to aPolicyElement(bystatic_id) for benefit/limit lookup.
Prior authorization
PriorAuth is a pre-approval for a procedure. Its lifecycle is SUBMITTED (on create) → PENDING/PENDING_REVIEW → APPROVED | 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
statusand no total; amounts live onClaimLine. REJECTEDis the only adverse Claim status;DENIEDis aPriorAuthstatus, 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 singleClaimconflates loss-capture with adjudication, andAPPROVED/PAIDhave no Socotra counterpart.
