Claims Lifecycle
A Claim represents a member's request for reimbursement of covered medical services. Claims are submitted complete — there is no DRAFT status in Olly's model. Once submitted, the claim moves through adjudication and, if approved, to payment.
Claim State Machine
What Happens at Each Transition
| Transition | Trigger | Side effects |
|---|---|---|
[*] → SUBMITTED | Claims service receives and validates payload | claims.submitted Kafka event published; EDI 837 generated by Mirth |
SUBMITTED → UNDER_REVIEW | Kafka consumer begins adjudication | Eligibility checked (gRPC), provider NPI verified (gRPC), accumulators loaded |
UNDER_REVIEW → PENDING_INFO | Adjudicator requests more docs | ClaimDocument record expected; member/provider notified |
PENDING_INFO → UNDER_REVIEW | Required docs uploaded | Adjudication resumes |
UNDER_REVIEW → APPROVED | All rules pass | AmountAllowed and AmountPaid computed on each ClaimLine; EOB PDF generated; claims.adjudicated Kafka event published |
UNDER_REVIEW → REJECTED | Coverage gap, exclusion, or prior auth missing | Denial reason codes written to ClaimLine.ReasonCode; member notified |
APPROVED → PAID | Billing service processes provider payment | billing.payment.completed event consumed by Claims; 835 remittance generated by Mirth |
PAID → CLOSED | Final reconciliation | Claim record sealed; no further state changes permitted |
ClaimLine Explained
Each Claim is itemised into one or more ClaimLine records — one per procedure or service rendered.
| Field | Type | Notes |
|---|---|---|
LineNumber | int | Ordering within the claim (matches 837 loop position) |
ElementID | uuid | The PolicyElement (insured person) this line applies to |
CoverageTermKey | string | The specific benefit category (e.g., "INPATIENT_HOSPITAL") |
Description | string | Human-readable service description |
AmountClaimed | decimal | What the provider billed |
AmountAllowed | decimal | CMS fee schedule or contracted rate |
AmountPaid | decimal | What the plan pays after deductible/coinsurance |
ReasonCode | string | ANSI X12 adjustment reason code (e.g., "CO-97") |
Metadata | jsonb | ICD-10/CPT codes, modifier codes, place of service |
The difference between AmountClaimed and AmountAllowed is the contractual write-off. The difference between AmountAllowed and AmountPaid is the member's cost-sharing responsibility (deductible + coinsurance + copay).
Prior Authorization Flow
Some procedures require pre-approval from the insurer before they are performed. If a claim arrives for a procedure that required prior auth but none was obtained, it is automatically rejected with reason code CO-15.
PriorAuth key fields:
| Field | Notes |
|---|---|
ProviderID | Provider requesting the auth |
PolicyID | Policy under which the member is covered |
ProcedureCode | CPT or HCPCS code requiring authorization |
Status | PENDING, APPROVED, or DENIED |
SubmittedAt | When the request was received |
ReviewedAt | When the clinical decision was made |
Appeal Process
When a claim is REJECTED, the member or provider may file an appeal within the statutory window (typically 180 days). Appeals are handled as a new ClaimEvent with EventType = "APPEAL_SUBMITTED". The claim re-enters UNDER_REVIEW with an escalated reviewer assignment. A successful appeal transitions the claim back to APPROVED; an unsuccessful one closes with EventType = "APPEAL_DENIED".
The full audit trail of all state changes, actors, and notes is preserved in ClaimEvent records — these are append-only and never modified.
EDI 837 / 835 Context
Olly uses Mirth Connect as its EDI translation layer:
- 837 (claim submission): Mirth parses inbound EDI 837P (professional) or 837I (institutional) files from clearinghouses and POSTs the structured payload to
/internal/claims/edi-ingest. The Claims service creates theClaimandClaimLinerows from this payload. - 835 (remittance advice): When a claim reaches
APPROVED, Mirth subscribes to theclaims.adjudicatedKafka topic and generates an 835 remittance advice file delivered to the provider's clearinghouse, containing theAmountPaidand ANSI adjustment reason codes per line.
Key Go Struct Fields at a Glance
type Claim struct {
ID uuid.UUID
Locator string // e.g. "CLM-2026-009871"
PolicyID uuid.UUID
TermID uuid.UUID // the PolicyTerm in effect on IncidentDate
ClaimantPartyID uuid.UUID // the member (Party) making the claim
Status ClaimStatus // SUBMITTED | UNDER_REVIEW | PENDING_INFO | APPROVED | REJECTED | PAID | CLOSED
IncidentDate time.Time // date of service
Document []byte // jsonb: raw submission payload, ICD-10/CPT codes
}