Documentation Index
Fetch the complete documentation index at: https://handbook.mhchq.ai/llms.txt
Use this file to discover all available pages before exploring further.
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.
How the Rating Engine Works
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
}
Factor Execution Order
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)
Audit Trail
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 }
}
Supported Lines of Business
Configuring Rate Tables
Rate tables are managed through the Admin UI (/admin/rate-tables) or via the API.
Rate Table Structure
{
"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
}
Upload via API
POST /v1/rate-tables
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"programId": "prog_gl_standard",
"lineOfBusiness": "GL",
"state": "VT",
"effectiveDate": "2025-01-01",
"baseRates": [ ... ],
"limitFactors": [ ... ]
}
:::tip
Rate table versions are immutable once active. To update rates, create a new version with a future
effectiveDate. The rating engine automatically selects the version in effect on the quote’s
effective date.
:::
The Underwriting Workbench
The Underwriting Workbench is a real-time web application for underwriters to review, decision, and bind submissions that require human judgment.
When Submissions Hit the Queue
A submission is automatically escalated to the Workbench when:
- The quoted premium exceeds the program’s auto-bind threshold (configured per program).
- The risk falls in an excluded class that requires prior approval.
- The experience mod exceeds 1.5 (poor loss history).
- The submission was referred by DA enforcement (exceeds per-policy or aggregate limits).
- An underwriter manually escalates it from the producer portal.
Workbench Features
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:
- Insured Information — Company details, virtualized contacts grid (paste from Excel), addresses
- Commercial Automobile — Vehicles (VIN-decoded), drivers (MVR), coverage & limits, mileage distribution, filings, additional interests
- Prior Carrier & Loss History — Prior carriers, loss records with summary panel, work experience for new ventures
- Notes & Attachments — Categorized notes, file uploads with optimistic UI updates
- Other Coverages — Cargo, physical damage, trailer interchange, officers, knockout underwriting questions with risk indicators
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.
Quote Readiness Engine
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 === 0
Common 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):
- FMCSA inactive status → Blocker
- Unsatisfactory safety rating → Blocker
- High vehicle out-of-service rate (>30%) → Warning
- Invalid VINs → Warning
- Missing ADAS safety tech → Info
Per-tab completion badges are derived from the readiness items and displayed in the tab navigation bar (green check / yellow warning / red X).
Referral Workflow
Submission exceeds auto-bind threshold
│
▼
MGA Underwriter reviews
│
┌─────┴──────┐
│ │
Decline Refer to carrier
│ │
DENY Carrier TU reviews
│
┌────┴─────┐
│ │
Approve Request info
│ │
BIND Producer responds
│
BIND or DENY
Underwriting Rules Engine
The @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.
Rule structure
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' };
Example rules
[
{
"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"]
}
}
]
Rule execution
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/rules
PUT /v1/rules/:id
DELETE /v1/rules/:id
AI Risk Adjustment
After the rating waterfall completes, the UnderwritingAgent optionally applies an AI risk adjustment of ±5% based on factors the rate tables don’t capture:
- Revenue trend (growing vs. contracting business)
- Geographic risk concentration
- Prior carrier’s reason for non-renewal
- Industry-specific news (WorldIntelligenceAgent feed)
- Management quality signals from the application
The adjustment is:
- Calculated by
claude-sonnet-4-6 with the full submission context
- Bounded to ±5% (hard limit enforced in code — not adjustable by prompting)
- Logged to
rating_audit as step 11 (“ai_risk_adjustment”)
- Flagged as an adverse action if the adjustment increases premium — the adverse action notice lists each contributing factor
{
"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"]
}
Binding Authority Enforcement
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.
Standalone LOB Raters
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:
- MVR Score (0–100): Fleet average motor vehicle record score. Color-coded: ≥80 green, ≥60 amber, <60 red.
- CDL Experience (0–30 years): Fleet average years of commercial driving experience.
- Major Violations (0–10): Total major violations across the fleet (DUI, reckless, etc.).
- Minor Violations (0–10): Total minor violations (speeding, lane change, etc.).
- At-Fault Accidents (0–10): Total at-fault accidents in prior 3 years.
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).
Package Policy Bundle Rating
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.
Eligibility Engine (Pre-Rater)
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:
- CSA BASIC percentile > 75th → auto-decline
- Prior loss ratio > 150% → auto-decline
- Fleet MVR score < 30 → auto-decline
- New authority < 6 months → refer to underwriter
- Fleet avg CDL experience < 1 year → refer
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 }.
What-If Scenario Modeling
The what-if engine (what-if.ts) enables side-by-side premium comparison across scenarios. Underwriters can ask:
- “What if we exclude driver #3?”
- “What if we change the deductible to $2,500?”
- “What if we switch to local radius?”
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 driver
changeDeductibleScenario() — adjusts comp/coll deductibles
changeRadiusScenario() — changes operating radius
Segment-Level Minimum Premium Floors
Beyond 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 Table Versioning
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 DESC
LIMIT 1
Tables with future effective dates don’t affect current quotes until their date arrives.
Floating-Point Safety
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:
- Decline triggers (loss ratio, experience mod)
- Referral thresholds (loss ratio, premium, schedule debit)
- Deviation band breach detection
- Eligibility predicate matching
All financial arithmetic uses the integer-cent Money type internally. Dollar values are converted via dollarsToSafeCents() using string parsing to avoid IEEE 754 drift.
Submission-to-Rater Mapping
When a submission has driver and vehicle data in D1, the submissionToRatingInputs() mapper automatically populates the rater:
- Drivers: Aggregates MVR points → fleet score, CDL dates → average experience, sums violations/accidents, filters excluded drivers
- Vehicles: Sums ACV for fleet total, determines predominant GVW class, calculates average age band
- Coverage: Maps PD deductibles, cargo endorsements, coverage limits
- Operations: Maps radius, authority type, DOT number
The InteractiveRater accepts an optional submission prop to pre-populate all 4 LOB tabs from D1 data.