Skip to content
Updated Jun 9, 2026

Eligibility & Accumulators

The eligibility service answers, fast, the two questions adjudication asks of every claim line: is this member covered right now? and how much of their benefit have they already used? It owns no source of truth; it is a CQRS read-model built from enrollment events.

Field reference: full columns, types and nullability live in the catalog: glossary terms MemberCoverage, CoverageAccumulator, DeductibleStatus, Coverage. This page is the narrative.

A read-model, not a source of truth

MemberCoverage is a denormalised projection. The enrollment service is the source of truth for policies and elements; eligibility consumes enrollment lifecycle events from Kafka (tracking position in projection_checkpoints) and writes flattened MemberCoverage rows so adjudication never has to join across enrollment tables.

This means it is eventually consistent: a just-issued policy becomes adjudicable only once its event has been projected. The trade is deliberate: adjudication reads are single-table and fast.

The three entities

  • MemberCoverage: one flattened row per covered member × element × term: the party/policy/element references, the element_static_id continuity key, the in-force coverage_terms snapshot, and the coverage window. The window is a half-open interval [effective_from, effective_to): effective_to is null while active and is filled in on termination/cancellation. Status is a code-enforced closed set (ACTIVE | INACTIVE | TERMINATED), with no Go enum type.
  • CoverageAccumulator: a running balance of how much of a benefit limit (a deductible, an out-of-pocket maximum, a category cap) a member has consumed within a term. Adjudication reads and updates accumulators to decide how much the plan pays on each ClaimLine. Continuity across element re-versioning is by element_static_id.
  • DeductibleStatus: a computed, non-persisted snapshot: the configured limit vs the amount consumed, exposing the remaining balance. It is derived on demand from CoverageAccumulator + coverage-term data.

Invariants

  • MemberCoverage is uniquely keyed by (party, element_static_id, term, effective_from).
  • A member's live entitlement is the set of MemberCoverage rows with an open or in-range window on the claim's incident date.
  • Accumulators are scoped to a term; they reset at term boundaries and track continuously across element versions via element_static_id.

Caveats

  • Eventually consistent: eligibility owns no source of truth; enrollment does. A coverage gap usually means an unprojected or out-of-order event, not missing enrollment data.
  • The deductible / out-of-pocket / copay vocabulary is US-style; on this UK-context platform these are placeholder benefit semantics.
  • term_id ranges over olly:Term while the physical record is PolicyTerm (enrollment.policy_terms), an unreconciled naming question (see Policies).

Olly Health Insurance Platform