Real-Time Queue
New submissions appear instantly via WebSocket (Durable Objects). Underwriters claim submissions to prevent double-decisioning.
The @openinsure/rating and @openinsure/underwriting packages together implement the actuarial and operational heart of the platform. The rating engine is deterministic and auditable — given identical inputs and rate table version, it always produces the same output with a full step-by-step calculation log.
Every quote starts with a RatingInput object that captures the risk characteristics:
interface RatingInput { orgId: string; programId: string; lineOfBusiness: 'GL' | 'CYBER' | 'EO' | 'WC' | 'PROPERTY' | 'UMBRELLA'; state: string; // 2-letter state code effectiveDate: string; // ISO 8601 expirationDate: string;
// Risk characteristics naicsCode: string; annualRevenue?: number; payroll?: number; // WC only squareFootage?: number; // Property tiv?: number; // Property total insured value yearsInBusiness?: number; priorCarrier?: string;
// Requested terms occurrenceLimit: number; aggregateLimit: number; deductible: number;
// Loss history (from prior carrier's loss run) lossHistory?: LossYear[]; // Past 5 years}The engine applies factors in a fixed order. Each step records the intermediate premium value, the factor applied, and the source table:
1. Base Rate └── Lookup: program_id + state + naics_code → base_rate_per_$1k_revenue └── Base Premium = (annualRevenue / 1000) × base_rate
2. Limit Factor └── Lookup: occurrence_limit + aggregate_limit → limit_factor └── Premium × limit_factor
3. Deductible Credit └── Lookup: deductible_amount → deductible_credit (negative factor) └── Premium × (1 - deductible_credit)
4. Territory/State Modifier └── Lookup: state → state_modifier └── Premium × state_modifier
5. Industry Class Modifier └── Lookup: naics_code → class_modifier └── Premium × class_modifier
6. Revenue Band Modifier └── Lookup: revenue_band(annualRevenue) → revenue_modifier └── Premium × revenue_modifier
7. Loss History Modifier (Experience Rating) └── Compute: loss_ratio = total_incurred / expected_losses └── Apply: experience_mod = credibility × (loss_ratio - 1.0) + 1.0 └── Premium × experience_mod [capped at program min/max]
8. Schedule Rating └── Underwriter-applied credits/debits: -25% to +25% └── Must be documented with reason code
9. Minimum Premium Check └── max(computed_premium, program_minimum_premium)
10. Fees & Taxes └── Policy fee (flat) └── Inspection fee (flat, if applicable) └── State surplus lines tax (% of premium, if non-admitted) └── SLSF fee (if applicable)Every rating step is stored in the rating_audit table:
{ "quoteId": "quo_01J8...", "steps": [ { "step": 1, "name": "base_rate", "factor": 0.0042, "input": 2500000, "output": 10500, "tableRef": "rt_gl_vt_238160_v3" }, { "step": 2, "name": "limit_factor", "factor": 1.15, "input": 10500, "output": 12075, "tableRef": "rt_limits_1m_2m" }, { "step": 7, "name": "experience_mod", "factor": 0.92, "input": 13124, "output": 12074, "credibility": 0.45, "lossRatio": 0.83 }, ... ], "grossPremium": 14250, "netPremium": 13087, "fees": { "policyFee": 150, "inspectionFee": 0, "surplusLinesTax": 1013 }}Key rating factors: NAICS code, annual revenue, state, occurrence/aggregate limit, deductible, loss history.
Supported forms: CG 00 01 (ISO CGL), plus program-specific endorsements for contractors, habitational, products liability.
Experience rating: Applied when ≥ $25,000 standard premium with 3+ years of loss history available.
Minimum premium: Configurable per program, typically $500–$2,500 depending on class.
Key rating factors: Annual revenue, data records count, industry sector, security controls score (0–100 from intake questionnaire), prior cyber incidents.
Coverage components: First-party (data recovery, business interruption, ransomware) + Third-party (network security liability, privacy liability, media liability).
Security discount: Carriers can configure credits for MFA adoption, EDR deployment, SOC 2 certification, and cyber insurance training programs.
Sublimits: Ransomware, social engineering, and PCI fines are typically sublimited. Configured per program in the rate table.
Key rating factors: Profession type, annual billings/fees, claims history, years in practice, state.
Supported professions: Technology companies, consultants, architects/engineers, accountants, real estate agents, insurance agents (Meta E&O).
Retroactive date: Mandatory field — policies are claims-made, so the retroactive date determines prior acts coverage.
Tail coverage: Extended reporting period (ERP) endorsements are handled as a separate one-time premium calculation in the billing module.
Key rating factors: Payroll by class code (NCCI or independent bureau), experience modification factor (EMF), state, industry.
EMF integration: The WC rating engine accepts the NCCI EMF directly from the producer’s submission. Verification against NCCI’s data feed is optional but recommended for large accounts.
Split payroll: Employees working across multiple class codes are pro-rated by actual payroll allocation.
Audit basis: WC policies are issued on estimated payroll and audited at expiration. The @openinsure/billing module supports mid-term and annual audit adjustments.
Rate tables are managed through the Admin UI (/admin/rate-tables) or via the API.
{ "id": "rt_gl_vt_v3", "programId": "prog_gl_standard", "lineOfBusiness": "GL", "version": 3, "effectiveDate": "2025-01-01", "state": "VT", "baseRates": [ { "naicsCode": "238160", "description": "Roofing Contractors", "ratePerThousand": 4.2, "minimumPremium": 1500 } ], "limitFactors": [ { "occurrence": 500000, "aggregate": 1000000, "factor": 0.85 }, { "occurrence": 1000000, "aggregate": 2000000, "factor": 1.0 }, { "occurrence": 2000000, "aggregate": 4000000, "factor": 1.22 } ], "stateModifier": 1.05, "minimumPremium": 750}POST /v1/rate-tablesAuthorization: Bearer <admin_token>Content-Type: application/json
{ "programId": "prog_gl_standard", "lineOfBusiness": "GL", "state": "VT", "effectiveDate": "2025-01-01", "baseRates": [ ... ], "limitFactors": [ ... ]}The Underwriting Workbench is a real-time web application for underwriters to review, decision, and bind submissions that require human judgment.
A submission is automatically escalated to the Workbench when:
Real-Time Queue
New submissions appear instantly via WebSocket (Durable Objects). Underwriters claim submissions to prevent double-decisioning.
Risk Scorecard
AI-generated summary of the risk with flagged items, peer comparisons, and recommended premium adjustments.
Schedule Rating
Apply credits/debits (-25% to +25%) with required reason codes. Changes are logged to the audit trail.
Referral Notes
Threaded notes between MGA underwriters and carrier technical underwriters. Full history preserved on the submission record.
The Workbench includes a full-featured Aspire form for commercial auto submissions, organized into five tabs:
setInsured call.All editable tables (vehicles, drivers, contacts, prior carriers, loss records, additional interests) use TanStack Table + React Virtual for performance with large datasets, and useTableSpreadsheet for keyboard navigation and clipboard paste.
The submissions index surfaces the same row columns that power the detail page — quote_number (MHC#######), insured_name, mc_number, usdot_number, effective_date, gross_premium, status — via assembleApplicationDetail. A page-size selector lets underwriters choose 25, 50, 100, or All per page; the value is persisted per user.
Quote numbers are minted at draft creation (format MHC#######). The submission carries the quote number from intake all the way to bound policy — no late-binding or renumbering.
FMCSAPanel on the Insured Info tab renders the full carrier profile returned from /v1/motor-carriers/dot/:dotNumber: authority badge, CSA/ISS scores, crash/inspection counts with OOS-rate vs national-average bars, safety rating, MCS-150 form date/mileage, HM shipper/carrier flags, physical + mailing address, DUNS, and any active L&I OCO/OSO orders. Enrichment preserves user input — the auto-populate only fills empty fields, never overwrites. Snapshots also persist to fmcsa_snapshots so the carrier-alerts cron sees the same data.
The QuoteReadinessService evaluates submission completeness using a strategy pattern — each carrier (Southwind, Bulldog, Commodore) can define custom checks on top of the common checks.
Scoring formula:
Score = max(0, 100 − (blockerCount × 20) − (warningCount × 5))Ready = blockerCount === 0Common checks:
| Check | Severity | Tab |
|---|---|---|
| Company name missing | Blocker | Insured |
| Policy state missing | Blocker | Insured |
| No vehicles | Blocker | Commercial Auto |
| No contacts | Warning | Insured |
| No prior carriers (established business) | Warning | Loss History |
| Knockout “yes” answers (referral triggers) | Warning | Other Coverages |
External data checks (when available):
Per-tab completion badges are derived from the readiness items and displayed in the tab navigation bar (green check / yellow warning / red X).
Submission exceeds auto-bind threshold │ ▼ MGA Underwriter reviews │ ┌─────┴──────┐ │ │ Decline Refer to carrier │ │ DENY Carrier TU reviews │ ┌────┴─────┐ │ │ Approve Request info │ │ BIND Producer responds │ BIND or DENYThe @openinsure/underwriting package evaluates a set of configurable rules against every submission before and after rating. Rules either auto-bind (no UW touch), auto-refer, auto-decline, or flag specific conditions for manual review.
interface UWRule { id: string; name: string; programId: string; lineOfBusiness: LineOfBusiness; condition: RuleCondition; // When this rule fires action: RuleAction; // What happens when it fires priority: number; // Lower number = evaluated first}
type RuleCondition = | { field: 'annualRevenue'; op: '>' | '<' | '>=' | '<='; value: number } | { field: 'lossRatio'; op: '>' | '<'; value: number } | { field: 'state'; op: 'in' | 'not_in'; values: string[] } | { field: 'naicsCode'; op: 'startsWith'; value: string } | { field: 'yearsInBusiness'; op: '<'; value: number } | { field: 'openClaimsCount'; op: '>='; value: number } | { field: 'experienceMod'; op: '>'; value: number } | { and: RuleCondition[] } | { or: RuleCondition[] };
type RuleAction = | { type: 'AUTO_BIND' } | { type: 'REFER'; reason: string; requiresInfo?: string[] } | { type: 'DECLINE'; reason: string } | { type: 'FLAG'; message: string; severity: 'INFO' | 'WARNING' | 'CRITICAL' };[ { "name": "High Revenue — Refer", "condition": { "field": "annualRevenue", "op": ">", "value": 5000000 }, "action": { "type": "REFER", "reason": "Revenue exceeds $5M — senior UW review required" } }, { "name": "Poor Loss History", "condition": { "and": [ { "field": "lossRatio", "op": ">", "value": 0.75 }, { "field": "yearsInBusiness", "op": ">=", "value": 3 } ] }, "action": { "type": "FLAG", "message": "5-year loss ratio > 75%", "severity": "CRITICAL" } }, { "name": "Excluded States", "condition": { "field": "state", "op": "in", "values": ["NY", "CA", "FL"] }, "action": { "type": "DECLINE", "reason": "State not eligible for this program" } }, { "name": "New Venture", "condition": { "field": "yearsInBusiness", "op": "<", "value": 2 }, "action": { "type": "REFER", "reason": "New venture — requires business plan and financials", "requiresInfo": ["business_plan", "financial_statements"] } }]import { evaluateRules } from '@openinsure/rules';
const result = evaluateRules({ submission, ratingAudit, programRules: await fetchProgramRules(programId),});
// result.decision: 'AUTO_BIND' | 'REFER' | 'DECLINE'// result.flags: UWFlag[]// result.requiredInfo: string[] (if REFER with requiresInfo)// result.triggeredRules: UWRule[] (which rules fired)Rules are managed via the Admin Portal → Programs → Underwriting Rules, or via API:
GET /v1/rules?programId=&lineOfBusiness=POST /v1/rulesPUT /v1/rules/:idDELETE /v1/rules/:idAfter the rating waterfall completes, the UnderwritingAgent optionally applies an AI risk adjustment of ±5% based on factors the rate tables don’t capture:
The adjustment is:
claude-sonnet-4-6 with the full submission contextrating_audit as step 11 (“ai_risk_adjustment”){ "step": 11, "name": "ai_risk_adjustment", "factor": 1.03, "rationale": "3% debit: risk concentration in high-CAT territory; prior carrier non-renewed for losses", "adverseAction": true, "adverseFactors": ["geographic_concentration", "prior_carrier_nonrenewal"]}The rating engine integrates with the DA module to enforce binding authority at quote time, not just at bind time. If a risk would exceed the DA limits, the system flags it during rating so underwriters know upfront.
See Delegated Authority for full configuration details.
The platform has two production commercial-auto raters; understanding which one is authoritative at each step matters.
| Rater | Role | Where it runs |
|---|---|---|
V64 (rateCommercialAutoV64 in @openinsure/rating) | Triage signal + audit trail. result.decision drives auto-decline/auto-refer/go-to-PCCR routing on the UW board. | Submission intake, Aspire validation, Interactive Rater |
PCCR (@openinsure/rating/pccr) | Binding premium. PCCR’s output is what lands on the quote PDF the producer receives. | POST /v1/submissions/:id/quote and the bind saga |
V64 still runs as the triage lane (see project_rater_v64_040826 in memory). Once V64 routes a submission to PCCR, PCCR produces the authoritative premium. The quote PDF generated on draft creation reflects PCCR’s numbers; V64 output is captured only for audit.
As of 2026-04-17, PCCR’s premium is the one surfaced through /v1/submissions/:id/quote, persisted to policies.gross_premium, and rendered on the producer-facing quote PDF.
The rating engine supports 4 standalone lines of business, each with its own dedicated module, input panel, and factor tables. Each LOB can be rated independently or bundled into a package policy.
| LOB | Module | Rating Basis | Key Factors |
|---|---|---|---|
| Commercial Auto Liability | engine.ts (main path) | Per vehicle | MVR score, CDL experience, territory, fleet size, radius, operating authority, CSA/FMCSA safety, vehicle GVW, new entrant surcharge |
| General Liability | cgl-rating.ts | Per $1K revenue | 8 ISO class codes (7219–8380), occurrence limits, territory tiers, loss history, deductible credits |
| Physical Damage | apd-rating.ts | Per $100 ACV | 9 vehicle classes, 5 age bands, garaging type, anti-theft devices, comp/coll deductibles, radius |
| Motor Truck Cargo | cargo-rating.ts | Per $100 limit | 12 commodity classes × 3 coverage forms, loading method, 8 optional endorsements |
The interactive rater in the Underwriting Workbench exposes all 4 LOBs as tabs. Switching tabs preserves inputs for each LOB.
The Auto Liability rater includes a Driver Profile section with fleet-averaged inputs:
These inputs feed into driver-rating.ts which applies an 8-bracket MVR factor (0.75×–1.75×), a 7-tier experience factor (0.80×–1.30×), and a surplus driver surcharge (1.0×–1.25× when drivers exceed vehicles).
A trucking account can bundle multiple LOBs into a single package quote with multi-line discounts:
| Lines Bundled | Discount |
|---|---|
| 2 LOBs | 5% |
| 3 LOBs | 10% |
| 4 LOBs (full bundle) | 15% |
The Bundle tab in the interactive rater lets underwriters select which LOBs to include. The system fires parallel rating calls for each selected LOB, then applies the package discount to the combined premium.
Package rating logic lives in package-rating.ts. Decision aggregation: decline if ANY LOB declines, refer if ANY refers.
Before running the full rating waterfall, the eligibility engine (eligibility.ts) performs a fast go/no-go check. This saves compute and provides structured declination reasons for FCRA compliance.
Default rules include:
Rules can be per-carrier (e.g., Bulldog won’t write in New York) and per-LOB. Custom rules are passed via the EligibilityRule interface.
API: POST /v1/rating/eligibility-check — returns { eligible, action, triggeredRules, declineReasons, referralReasons }.
The what-if engine (what-if.ts) enables side-by-side premium comparison across scenarios. Underwriters can ask:
The compareScenarios() function rates a baseline input, then rates each scenario with overrides applied. It returns per-scenario premium deltas and factor-level diffs showing exactly which factors changed.
Preset scenario builders:
excludeDriverScenario() — recalculates fleet MVR/experience averages after removing a driverchangeDeductibleScenario() — adjusts comp/coll deductibleschangeRadiusScenario() — changes operating radiusBeyond the base minPrem in rate tables, the engine supports configurable minimum premium floors with predicate-based matching:
{ predicate: { maxYearsInBusiness: 0, lob: 'Commercial Auto' }, minPremium: 14000, label: 'New venture minimum'}Predicates can match on: lob, state, maxYearsInBusiness, operatingAuthority, cargoType. The highest matching floor wins. Applied in all rating paths including standalone LOBs.
Rate tables support effective-dated changes. When Greg says “bump base rates 15% effective April 1,” a new rate table version is created with effectiveDate: '2026-04-01'.
The API automatically selects the most recently effective active table:
WHERE org_id = ? AND line_of_business = ? AND is_active = true AND (effective_date IS NULL OR effective_date <= NOW())ORDER BY effective_date DESCLIMIT 1Tables with future effective dates don’t affect current quotes until their date arrives.
All threshold comparisons in the engine use epsilon-tolerant arithmetic (FP_EPSILON = 1e-9) to prevent IEEE 754 precision issues from causing incorrect decisions. For example, 0.1 + 0.2 = 0.30000000000000004 will NOT incorrectly cross a maxLossRatio: 0.3 threshold.
This applies to:
All financial arithmetic uses the integer-cent Money type internally. Dollar values are converted via dollarsToSafeCents() using string parsing to avoid IEEE 754 drift.
When a submission has driver and vehicle data in D1, the submissionToRatingInputs() mapper automatically populates the rater:
The InteractiveRater accepts an optional submission prop to pre-populate all 4 LOB tabs from D1 data.