Skip to content

Stop-Loss Insurance

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
TypeDescription
MoneyInteger cents with currency code — all financial amounts use this type from @openinsure/rating
StopLossPolicyIdBranded string identifying a stop-loss policy
ClaimantIdBranded string identifying a covered individual
AttachmentPointBranded Money value representing a threshold
StopLossClaimIdBranded 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:

TermDefinition
Attachment pointThe threshold — the captive/plan retains 100% up to this amount per individual
Maximum benefitMaximum reinsurer payout per individual per policy period
Lasered attachmentIndividual-specific attachment override for high-risk claimants
RetainedAmount below the attachment (plan’s responsibility)
ExcessAmount 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.

Calculating the Aggregate Attachment Point

Section titled “Calculating the Aggregate Attachment Point”
import { calculateAggregateAttachmentPoint } from '@openinsure/stop-loss';
import { fromCents } from '@openinsure/rating';
const expectedClaims = fromCents(380_000_00, 'USD'); // $3,800,000
const 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,000
const attachment = calculateAggregateAttachmentPoint(
fromCents(380_000_00, 'USD'), // expected claims
12500 // 125% factor
);
const corridorBps = 0; // no corridor
const 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 → $0

Aggregate 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 attachment
const corridorBps = 500;
// If attachment = $4,750,000, corridor = $237,500
// Effective threshold = $4,987,500

Track 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:

FieldDescription
monthMonth number (1–12)
claimsThisMonthClaims incurred this month
cumulativeClaimsRunning total of all claims
attachmentBreachedWhether cumulative claims exceed the aggregate attachment
reimbursementThisMonthStop-loss recovery for this month (capped by maximum aggregate)
cumulativeReimbursementRunning 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,000

A 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:

  • Monthly fund ratio tracking
  • Specific SL recovery tracking per claimant
  • Aggregate corridor visualization
  • Actuarial projection (run-out of current year)

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.