Skip to main content

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:

FlagSeverityTrigger
HIGH_LOSS_RATIOCritical5-year loss ratio > 70%
DETERIORATING_TRENDWarningLoss ratio increased ≥ 15 points in last 2 years
OPEN_CLAIMSWarning≥ 2 open claims at time of submission
CATASTROPHIC_LOSSCriticalSingle claim > 50% of annual premium
FREQUENCYWarning> 3 claims per year (small commercial)
RAPID_RESERVE_DEVELOPMENTWarningOpen reserves grew > 40% since last loss run
CARRIER_NONRENEWALInfoCurrent 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