@openinsure/policy package implements a deterministic finite state machine for every insurance policy. All transitions are server-enforced — the API returns a 422 Unprocessable Entity for any invalid transition attempt.
State Machine
Lifecycle States
| State | Description | Billable |
|---|---|---|
draft | Submission data being collected | No |
quoted | Premium calculated, awaiting acceptance | No |
bound | Accepted by producer, pending underwriter review | Yes (pro-rata from effective date) |
issued | Underwriter reviewed and finalized | Yes |
active | Currently in force | Yes |
endorsed | Mid-term change applied (policy remains active) | Yes (adjusted premium) |
cancelled | Terminated before expiration | Partial (by cancellation type) |
expired | Policy period ended without renewal | No |
non-renewed | Carrier elected not to renew | No |
renewed | New term policy created | Yes (new term) |
Transition Rules
All transitions are enforced inpackages/policy/src/index.ts. The valid transitions are:
Endorsement Flow (Mid-Term Changes)
Endorsements apply changes to an active policy and produce a new premium calculation for the remaining policy term. OpenInsure models the policy as a timeline of segments — each endorsement creates a new segment with its own annual premium rate, pro-rated to the days it was in effect.Endorsement Types
| Type | Code | Description |
|---|---|---|
| Limit change | LIMIT_CHANGE | Increase or decrease the policy limit |
| Deductible change | DEDUCTIBLE_CHANGE | Modify the per-occurrence deductible |
| Additional insured | ADD_INSURED | Add a named additional insured |
| Location change | LOCATION_CHANGE | Add or remove scheduled locations |
| Coverage add | COVERAGE_ADD | Add an endorsement form (e.g., EPLI) |
| Coverage remove | COVERAGE_REMOVE | Remove a coverage endorsement |
| Named insured change | NAME_CHANGE | Update the named insured (e.g., after acquisition) |
| Driver add/remove | DRIVER_ADD/REMOVE | Add or remove a scheduled driver (auto/trucking) |
| Vehicle add/remove | VEHICLE_ADD/REMOVE | Add or remove a scheduled vehicle |
| Correction | CORRECTION | Administrative fix — no premium impact |
Endorsement API
Timeline Engine Architecture
A policy is a function of time. The timeline engine in@openinsure/rating (timeline.ts) models this as an ordered sequence of segments:
- An effective date (= endorsement effective date, or policy inception for segment 0)
- An expiration date (= next segment’s effective date, or policy expiration)
- A
RatingInputsnapshot (the rated state for that segment) - An annual premium (from
rateSubmission()) - A pro-rated segment premium:
annualPremium × segmentDays / 365
@openinsure/rating):
| Function | Source | Purpose |
|---|---|---|
buildTimeline() | timeline.ts | Build full segment timeline from inception + endorsements |
insertEndorsement() | timeline.ts | Insert one endorsement (possibly backdated) and return delta |
computeEarnedToDate() | timeline.ts | Earned premium as of a given date |
validateEndorsementDate() | timeline.ts | Validate effective date against policy bounds |
computeCascadeImpact() | timeline.ts | Compute premium impact on downstream endorsements after OOS insertion |
@openinsure/policy):
| Function | Source | Purpose |
|---|---|---|
detectOutOfSequence() | endorsement-timeline.ts | Detect if a new endorsement is out of chronological order |
deriveEndorsementChronology() | endorsement-timeline.ts | Derive chronological ordering of all endorsements |
computeScheduleChangePremium() | endorsement-timeline.ts | Pro-rata past/future premium split for schedule changes |
Out-of-Sequence Endorsements
An out-of-sequence (OOS) endorsement has an effective date earlier than a previously issued endorsement. This is common in insurance — a backdated driver addition, a retroactive limit increase, or a correction with an earlier effective date. Example: Policy has ENT-001 (effective June 1) and ENT-002 (effective September 1) already issued. A new endorsement arrives with effective date March 1 — this is out-of-sequence because it predates both existing endorsements. When an OOS endorsement is created, OpenInsure:- Detects the OOS condition via
detectOutOfSequence()— compares the candidate’s effective date against all issued/approved endorsements - Identifies affected endorsements — all downstream endorsements whose premium may change
- Rebuilds the timeline —
insertEndorsement()re-derives all segments from the insertion point forward - Computes cascade impact —
computeCascadeImpact()calculates the premium delta on each downstream endorsement - Splits past/future adjustments — for backdated endorsements, the premium adjustment is split:
- Past period: endorsement effective date → processing date (retroactive earned premium delta)
- Future period: processing date → policy expiration (going-forward billing delta)
- Row-locks the policy during the transaction to prevent concurrent endorsement conflicts
endorsements table tracks OOS state explicitly:
| Column | Type | Purpose |
|---|---|---|
sequence_number | integer | Timeline position (1-based, unique per policy) |
is_out_of_sequence | boolean | Was this endorsement OOS when created? |
supersedes_id | uuid | Self-referencing FK for cascade-reshuffled endorsements |
past_period_adj | numeric(12,2) | Retroactive premium adjustment |
future_period_adj | numeric(12,2) | Going-forward premium adjustment |
prior_rating_input | jsonb | Rating snapshot before the change |
new_rating_input | jsonb | Rating snapshot after the change |
rating_result | jsonb | Full rating result for the new state |
Same-Day Endorsements
Multiple endorsements with the same effective date are allowed. They are ordered by sequence number (issue order) and do not trigger OOS detection — only endorsements with a later effective date are considered “downstream.” Same-day endorsements generate a warning (creates a zero-day segment) but are not blocked, because legitimate scenarios exist (e.g., adding a driver and changing limits on the same day).
Endorsement Validation
Before an endorsement is created,validateEndorsementDate() checks:
| Check | Result |
|---|---|
| Effective date before policy inception | Error (blocked) |
| Effective date at or after policy expiration | Error (blocked) |
| Effective date matches existing endorsement | Warning (zero-day segment) |
| Effective date in the past | Warning (backdated) |
Batch Endorsements
Portfolio-wide rate changes (carrier-mandated rate increases, surcharges, factor overrides) are processed via the batch endorsement engine in@openinsure/rating (batch-endorsement.ts):
Driver & Vehicle Endorsement Changes
For commercial auto and trucking lines, the@openinsure/rating package (endorsement-changes.ts) provides structured change builders that translate driver/vehicle roster mutations into RatingInput deltas:
Endorsement Documents
When an endorsement is issued,@openinsure/documents generates an Endorsement Certificate — a PDF showing the policy number, endorsement number, effective date, change description, and premium impact. The certificate is stored in R2 and linked to the endorsement record.
Event Processing
The queue consumer handlesendorsement.issued events by:
- Creating a notification record
- Posting a GL journal entry for the premium delta (if non-zero)
- Sending an email to the producer via the configured email provider
- Posting to the Slack channel (if configured)
- Triggering webhook delivery to external systems
Cancellation Types
| Type | Code | Return Premium Calculation |
|---|---|---|
| Flat cancel | FLAT | 100% return — as if policy never incepted |
| Pro-rata | PRO_RATA | Return = full premium × (days remaining / policy days) |
| Short-rate | SHORT_RATE | Return = pro-rata × 90% — penalty for insured-requested cancellations |
Cancel API
@openinsure/compliance rule engine. The API will reject a cancellation
effective date that violates the notice period and return the earliest permissible date.
:::
Cancellation Reasons
Common cancellation reason codes:NON_PAYMENT— Premium invoice unpaid beyond grace periodINSURED_REQUEST— Named insured requested cancellationUNDERWRITING— Risk no longer acceptable (requires advance notice)FRAUD— Material misrepresentation (immediate, state-specific rules apply)REWRITE— Policy being rewritten to another carrier on same terms
Reinstatement
A cancelled policy can be reinstated within 30 days of cancellation (state rules may override this window).lapseCoverage: false, the reinstated policy covers the gap period (subject to underwriter approval). If true, the policy is reinstated with a coverage gap — common for non-payment reinstatements.
Renewal Automation
Renewals are created automatically 90 days before expiration by the nightly renewal scheduler job. The scheduler:- Identifies policies expiring within 90 days.
- Creates a
draftrenewal submission with the current policy’s data pre-filled. - Re-rates using the current rate tables (the new rates may differ from the expiring term).
- Sends renewal offer to the producer via the notification system.
Renewal vs. Rewrite
A renewal maintains the same policy number with a new term suffix (e.g.,GL-2025-000001-R1). A rewrite creates a new policy number — used when the carrier, program, or coverage terms change materially.