Specific SL
Per-individual protection — the reinsurer pays losses above the specific attachment point per claimant. Supports lasered (individual-specific) attachment overrides.
Stop-loss (excess of loss) insurance protects self-funded plans and captives from catastrophic claims. The @openinsure/stop-loss package provides actuarial calculation functions for specific stop-loss, aggregate stop-loss, loss run reporting, and loss ratio analysis.
All financial amounts use the Money type (integer cents) from @openinsure/rating, eliminating floating-point arithmetic errors in financial calculations. Identifiers use branded types (StopLossPolicyId, ClaimantId, StopLossClaimId) for compile-time safety. Aggregate factors are expressed in basis points (e.g. 12500 bps = 125.00%) rather than float percentages.
Specific SL
Per-individual protection — the reinsurer pays losses above the specific attachment point per claimant. Supports lasered (individual-specific) attachment overrides.
Aggregate SL
Total loss protection — the reinsurer pays when cumulative losses exceed the aggregate attachment (typically 120–125% of expected losses, expressed in basis points).
The package uses branded types to prevent accidental misuse of plain strings and numbers:
import { type StopLossPolicyId, type ClaimantId, type AttachmentPoint, stopLossPolicyId, claimantId, attachmentPoint,} from '@openinsure/stop-loss';import { fromCents } from '@openinsure/rating';
const policyId = stopLossPolicyId('pol_abc123');const claimant = claimantId('clm_jane_doe');const attachment = attachmentPoint(fromCents(25_000_00, 'USD')); // $250,000| Type | Description |
|---|---|
Money | Integer cents with currency code — all financial amounts use this type from @openinsure/rating |
StopLossPolicyId | Branded string identifying a stop-loss policy |
ClaimantId | Branded string identifying a covered individual |
AttachmentPoint | Branded Money value representing a threshold |
StopLossClaimId | Branded string identifying a claim submission |
Specific stop-loss protects against a single large claimant (health: single individual; casualty: single occurrence). Claims must strictly exceed the attachment point for reimbursement to apply.
import { calculateSpecificReimbursement, type SpecificStopLossPolicy, type StopLossClaimSubmission, stopLossPolicyId, claimantId, stopLossClaimId, attachmentPoint,} from '@openinsure/stop-loss';import { fromCents } from '@openinsure/rating';
const policy: SpecificStopLossPolicy = { id: stopLossPolicyId('pol_2026_001'), attachmentPoint: attachmentPoint(fromCents(25_000_00, 'USD')), // $250,000 maximumBenefit: fromCents(200_000_00, 'USD'), // $2,000,000 policyPeriod: { effectiveDate: new Date('2026-01-01'), expirationDate: new Date('2027-01-01'), }, coveredLives: 450, laseredAttachments: [],};
const claim: StopLossClaimSubmission = { id: stopLossClaimId('sc_001'), policyId: stopLossPolicyId('pol_2026_001'), claimantId: claimantId('emp_4821'), incurredAmount: fromCents(82_500_00, 'USD'), // $825,000 incurredDate: new Date('2026-06-15'), submittedDate: new Date('2026-07-01'), status: 'open', description: 'Cardiac surgery and rehabilitation',};
const result = calculateSpecificReimbursement(claim, policy);// result.reimbursed → $575,000 (825K - 250K attachment)// result.retained → $250,000 (attachment point)// result.excess → $0 (within $2M max benefit)High-risk individuals can have a higher attachment point (lasering):
import { type LaseredAttachment } from '@openinsure/stop-loss';
const policy: SpecificStopLossPolicy = { // ...same as above laseredAttachments: [ { claimantId: claimantId('emp_9102'), attachmentPoint: attachmentPoint(fromCents(50_000_00, 'USD')), // $500,000 laser }, ],};Claims for emp_9102 are evaluated against the $500,000 lasered attachment instead of the standard $250,000 policy attachment.
Specific stop-loss attachment is evaluated on the per-individual total across all claims, not per-claim:
import { aggregateClaimantCosts } from '@openinsure/stop-loss';
const claimantTotals: Map<ClaimantId, Money> = aggregateClaimantCosts(allClaims, policy);// Map { 'emp_4821' => $825,000, 'emp_1204' => $142,000, ... }Key concepts:
| Term | Definition |
|---|---|
| Attachment point | The threshold — the captive/plan retains 100% up to this amount per individual |
| Maximum benefit | Maximum reinsurer payout per individual per policy period |
| Lasered attachment | Individual-specific attachment override for high-risk claimants |
| Retained | Amount below the attachment (plan’s responsibility) |
| Excess | Amount above the maximum benefit (also plan’s responsibility) |
Aggregate stop-loss protects against adverse total loss experience for the year. The attachment factor is expressed in basis points to avoid floating-point arithmetic.
import { calculateAggregateAttachmentPoint } from '@openinsure/stop-loss';import { fromCents } from '@openinsure/rating';
const expectedClaims = fromCents(380_000_00, 'USD'); // $3,800,000const factorBps = 12500; // 125.00%
const aggAttachment = calculateAggregateAttachmentPoint(expectedClaims, factorBps);// aggAttachment → $4,750,000 (3,800,000 * 125%)import { calculateAggregateReimbursement, calculateAggregateAttachmentPoint, type AggregateStopLossPolicy,} from '@openinsure/stop-loss';
const totalClaims = fromCents(520_000_00, 'USD'); // $5,200,000const attachment = calculateAggregateAttachmentPoint( fromCents(380_000_00, 'USD'), // expected claims 12500 // 125% factor);const corridorBps = 0; // no corridorconst maxBenefit = fromCents(700_000_00, 'USD'); // $7,000,000
const result = calculateAggregateReimbursement(totalClaims, attachment, corridorBps, maxBenefit);// result.reimbursed → $450,000 (5.2M - 4.75M attachment)// result.retained → $4,750,000// result.excess → $0Aggregate attachment is typically set at 120–125% of expected losses (12000–12500 bps), negotiated with the reinsurer based on historical loss ratio volatility.
The corridor is a band above the attachment where no recovery occurs, also expressed in basis points:
// 500 bps corridor = 5% of the aggregate attachmentconst corridorBps = 500;
// If attachment = $4,750,000, corridor = $237,500// Effective threshold = $4,987,500Track aggregate stop-loss on a month-by-month basis. The accumulator detects when cumulative claims breach the attachment and calculates per-month reimbursements:
import { runAggregateAccumulator, type AggregateStopLossPolicy, stopLossPolicyId,} from '@openinsure/stop-loss';import { fromCents } from '@openinsure/rating';
const policy: AggregateStopLossPolicy = { id: stopLossPolicyId('agg_2026_001'), attachmentFactorBps: 12500, // 125% expectedClaims: fromCents(380_000_00, 'USD'), // $3,800,000 corridorBps: 0, minimumAggregate: fromCents(100_000_00, 'USD'), maximumAggregate: fromCents(700_000_00, 'USD'), policyPeriod: { effectiveDate: new Date('2026-01-01'), expirationDate: new Date('2027-01-01'), }, policyMonths: 12,};
const monthlyClaims = [ fromCents(30_000_00, 'USD'), // Jan: $300K fromCents(35_000_00, 'USD'), // Feb: $350K fromCents(42_000_00, 'USD'), // Mar: $420K // ...remaining months];
const entries = runAggregateAccumulator(monthlyClaims, policy);
for (const entry of entries) { console.log( `Month ${entry.month}: cumulative=${entry.cumulativeClaims.cents / 100}`, `breached=${entry.attachmentBreached}`, `reimbursement=${entry.reimbursementThisMonth.cents / 100}` );}Each AccumulatorEntry includes:
| Field | Description |
|---|---|
month | Month number (1–12) |
claimsThisMonth | Claims incurred this month |
cumulativeClaims | Running total of all claims |
attachmentBreached | Whether cumulative claims exceed the aggregate attachment |
reimbursementThisMonth | Stop-loss recovery for this month (capped by maximum aggregate) |
cumulativeReimbursement | Running total of all reimbursements |
Generate a comprehensive loss run summary for a specific stop-loss policy:
import { generateLossRunSummary } from '@openinsure/stop-loss';
const summary = generateLossRunSummary(policy, allClaims);
// summary.totalEligibleClaims → 142// summary.totalIncurred → $4,320,000// summary.totalAboveAttachment → $1,175,000// summary.totalBelowAttachment → $3,145,000// summary.claimantCount → 89// summary.claimantsExceedingAttachment → 4// summary.statusBreakdown → [{ status: 'closed', count: 98, ... }, ...]The LossRunSummary includes a status breakdown across all claims (including denied), while financial totals only count eligible claims within the policy period.
Calculate the loss ratio to evaluate stop-loss carrier performance:
import { calculateLossRatio } from '@openinsure/stop-loss';import { fromCents } from '@openinsure/rating';
const result = calculateLossRatio( fromCents(50_000_00, 'USD'), // $500,000 premium paid fromCents(32_500_00, 'USD') // $325,000 reimbursements received);
// result.lossRatioBps → 6500 (65.00%)// result.lossRatioDecimal → 0.65// result.premiumPaid → $500,000// result.reimbursementsReceived → $325,000A loss ratio above 10000 bps (100%) means the carrier paid out more than it collected in premium.
Stop-loss calculations feed the Captive Admin module’s Loss Fund dashboard:
See Loss Fund Tracking for the dashboard reference.
Stop-loss programs are often structured as part of the broader reinsurance program. The cession to the stop-loss reinsurer appears in the quarterly cession statement alongside quota share and XOL treaties.
See Reinsurance Management for how cession statements are compiled.