Enrollment
Manages the full quote-to-policy lifecycle — quoting, underwriting, binding, and all in-force policy transactions.
Overview
The Enrollment service is the policy factory for Olly. It handles the end-to-end journey from a rating quote through underwriting, binding, and ongoing policy management (endorsements, renewals, cancellations, reinstatements). It integrates with Policy Admin to fetch product configuration and rule sets, and uses the shared rule engine library to enforce underwriting eligibility rules at each pricing and underwriting step.
A Quote captures the member's plan selection and application data. Once underwritten and issued, it becomes a Policy with one or more PolicyTerm records (coverage periods) and PolicyElement items (individual coverages or riders). All in-force changes — cancellations, endorsements, renewals — create PolicyTransaction records that describe the delta applied to the policy. Field values for configurable product attributes are stored in separate *FieldValue tables to support flexible product designs without schema changes.
When a policy activates, Enrollment publishes enrollment.policy.activated so that Eligibility and Billing can react.
Responsibilities
- Accept quote creation requests and price them against Policy Admin product configuration
- Drive quotes through the underwriting workflow (price → underwrite → issue/refuse/discard)
- Bind issued quotes into active policies with coverage terms and elements
- Manage in-force transactions: endorsements, cancellations, reinstatements, renewals
- Enforce underwriting rules via the shared rule engine
- Store flexible field values for configurable product attributes
- Publish lifecycle events for Eligibility, Billing, and Notifications
- Expose internal endpoints for Broker API and Group Scheme Service to list policies
Database
Schema: enrollment
| Table | Purpose |
|---|---|
quotes | Quote root aggregate with pricing, status, and product version reference |
quote_events | Immutable event log for quote state transitions |
policies | Active policy records with account, broker, and term references |
policy_terms | Coverage period records (effective/expiry dates) per policy |
policy_transactions | All in-force changes (endorse, cancel, reinstate, renew) as transaction records |
policy_elements | Individual coverage items and riders within a policy term |
underwriting_flags | Flags raised during underwriting that require manual review |
quote_field_values | Configurable field values scoped to a quote |
policy_field_values | Configurable field values scoped to a policy |
transaction_field_values | Configurable field values scoped to a transaction |
outbox | Transactional outbox for reliable Kafka event publishing |
API Routes
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /quotes | JWT | Create a new quote |
GET | /quotes/{locator} | JWT | Get quote by locator |
PATCH | /quotes/{locator} | JWT | Update quote document |
PATCH | /quotes/{locator}/price | JWT | Price a quote |
PATCH | /quotes/{locator}/underwrite | JWT | Run underwriting on a quote |
PATCH | /quotes/{locator}/issue | JWT | Issue (bind) a quote into a policy |
PATCH | /quotes/{locator}/refuse | JWT | Refuse a quote |
PATCH | /quotes/{locator}/discard | JWT | Discard a draft quote |
POST | /quotes/{locator}/number/generate | JWT | Generate a display quote number |
GET | /policies/{locator} | JWT | Get policy by locator |
GET | /policies | JWT | List policies (filterable by account) |
POST | /policies/{locator}/cancel | JWT | Cancel an active policy |
POST | /policies/{locator}/reinstate | JWT | Reinstate a lapsed or cancelled policy |
POST | /policies/{locator}/renew | JWT | Renew a policy for a new term |
POST | /policies/{locator}/endorse | JWT | Apply an endorsement transaction |
POST | /policies/{locator}/number/generate | JWT | Generate a display policy number |
GET | /policies/{locator}/terms | JWT | List terms for a policy |
GET | /policies/{locator}/elements | JWT | List active elements for a policy |
GET | /policies/{locator}/transactions | JWT | List transactions for a policy |
GET | /transactions/{locator} | JWT | Get a transaction by locator |
PATCH | /transactions/{locator}/price | JWT | Price a pending transaction |
PATCH | /transactions/{locator}/underwrite | JWT | Underwrite a pending transaction |
PATCH | /transactions/{locator}/apply | JWT | Apply an approved transaction |
PATCH | /transactions/{locator}/decline | JWT | Decline a transaction |
PATCH | /transactions/{locator}/discard | JWT | Discard a draft transaction |
PATCH | /transactions/{locator}/reverse | JWT | Reverse an applied transaction |
Events
Publishes
| Topic | When |
|---|---|
enrollment.quote.created | A new quote is created |
enrollment.policy.activated | A quote is issued and a policy becomes active |
enrollment.policy.lapsed | A policy lapses due to non-payment |
enrollment.policy.cancelled | A policy is cancelled |
Consumes
The Enrollment service does not consume Kafka events. Changes are driven by external API calls.
Dependencies
| Service | How used |
|---|---|
| Policy Admin | Fetches product configuration, benefit rules, and rule sets for pricing and underwriting |
Key Design Decisions
Rule engine integration: Enrollment embeds the shared ruleengine library for in-process rule evaluation during underwriting. Rules are fetched from Policy Admin and persisted locally via ruleengine.NewWriter(gormDB) for fast repeated evaluation without cross-service calls on every request.
Field values for flexible products: Rather than schema migrations for every new product attribute, configurable fields are stored in *_field_values tables keyed to the quote, policy, or transaction locator. Field definitions are managed in Policy Admin and fetched at runtime.