Loss Runs
Loss runs are the historical claims record that underwriters rely on to evaluate a risk. The @openinsure/loss-runs package handles the full lifecycle: requesting loss runs from prior carriers, extracting data from PDF and CSV formats, analyzing trends, and flagging risks.
Loss Run Lifecycle
Producer submits new business
│
▼
Request loss runs from prior carriers
│
▼
Carrier responds (PDF or CSV attachment)
│
▼
Extract structured data (AI + parser)
│
▼
5-year loss analysis + trend detection
│
▼
Risk flags surfaced in UW queue
│
▼
Underwriter reviews → Quote / Decline / Refer
Requesting Loss Runs
Loss run requests are sent via the submission workflow when a submission is received:
POST /v1/submissions/:id/request-loss-runs
Authorization: Bearer <token>
Content-Type: application/json
{
"priorCarrier": {
"name": "Acme Mutual Insurance",
"claimsEmail": "lossruns@acmemutual.com",
"policyNumber": "GL-2022-001234"
},
"yearsRequested": 5,
"effectiveDateRange": {
"from": "2020-01-01",
"to": "2025-01-01"
}
}
This sends a templated email to the prior carrier's claims department and creates a pending request record. When the carrier replies with an attachment, the shared mailbox cron routes the attachment to the loss run extractor.
Extraction
The @openinsure/loss-runs package includes both a structured CSV extractor and an AI-powered PDF extractor.
CSV extraction
import { extractFromCSV } from '@openinsure/loss-runs';
const lossRun = await extractFromCSV(csvString, {
mapping: {
claimNumber: 'Claim #',
dateOfLoss: 'Loss Date',
status: 'Status',
paidLoss: 'Paid Indemnity',
paidExpense: 'Paid Expense',
reserve: 'Outstanding Reserve',
totalIncurred: 'Total Incurred',
},
});
// lossRun.years[].claims[] — structured claim-level data
// lossRun.years[].totals — annual summary
PDF extraction (AI)
PDF loss runs are rendered to images and processed by the AI extraction bridge:
import { extractFromPDF } from '@openinsure/loss-runs';
const lossRun = await extractFromPDF(pdfBuffer, {
carrier: 'Acme Mutual',
policyNumber: 'GL-2022-001234',
});
// Same output structure as CSV extraction
// extractionConfidence: 0.0–1.0 (below 0.75 → manual review)
Loss Analysis
Once extracted, the analyzeLossRun() function produces a 5-year summary:
import { analyzeLossRun } from '@openinsure/loss-runs';
const analysis = analyzeLossRun(lossRun, {
earnedPremium: [180_000, 195_000, 210_000, 225_000, 240_000], // 5 years
});
// analysis.lossRatioByYear: [0.42, 0.38, 0.61, 0.35, 0.44]
// analysis.fiveYearLossRatio: 0.44
// analysis.largestSingleClaim: 95_000
// analysis.openClaimsCount: 2
// analysis.trendDirection: 'STABLE' | 'DETERIORATING' | 'IMPROVING'
// analysis.flags: [...]
Risk Flags
The analyzer automatically generates flags based on the loss history:
| Flag | Severity | Trigger |
|---|---|---|
HIGH_LOSS_RATIO | Critical | 5-year loss ratio > 70% |
DETERIORATING_TREND | Warning | Loss ratio increased ≥ 15 points in last 2 years |
OPEN_CLAIMS | Warning | ≥ 2 open claims at time of submission |
CATASTROPHIC_LOSS | Critical | Single claim > 50% of annual premium |
FREQUENCY | Warning | > 3 claims per year (small commercial) |
RAPID_RESERVE_DEVELOPMENT | Warning | Open reserves grew > 40% since last loss run |
CARRIER_NONRENEWAL | Info | Current carrier did not offer renewal |
analysis.flags.forEach((flag) => {
console.log(flag.code); // 'HIGH_LOSS_RATIO'
console.log(flag.severity); // 'CRITICAL'
console.log(flag.description); // '5-year loss ratio of 84% exceeds 70% threshold'
console.log(flag.year); // 2024 (if year-specific)
});
Flags are surfaced in the UW queue with color-coded badges and require acknowledgment before an underwriter can bind the policy.
Loss Run Generator
For policyholders requesting their own loss run (to submit with a new application elsewhere), the generateLossRun() function produces a formatted loss run from OpenInsure's claim records:
import { generateLossRun } from '@openinsure/loss-runs';
const lossRunPDF = await generateLossRun({
policyId: 'pol_01J8...',
yearsRequested: 5,
asOfDate: '2025-06-01',
requestedBy: 'Acme Roofing LLC',
format: 'pdf', // 'pdf' | 'csv' | 'json'
});
POST /v1/policies/:id/loss-run
Authorization: Bearer <token>
Content-Type: application/json
{
"yearsRequested": 5,
"asOfDate": "2025-06-01",
"requestedBy": "Acme Roofing LLC",
"format": "pdf"
}
The generated loss run is stored in R2 and a download link is returned. Producers can also trigger this from the producer portal via Policy → Request Loss Run.
UW Queue Integration
Loss run analysis results automatically populate the submission's Risk Scorecard in the Underwriting Workbench:
- Loss ratio by year (bar chart)
- Trend indicator (arrow + color)
- Flag summary with drill-down to individual claim data
- Comparison to program benchmark loss ratio