Quote Flow
The Safeco Auto quote flow is a multi-step sequence. Each step is defined in flow-config.ts and rendered by the Safeco Auto flow router.
Flow Steps
| Step ID | Path | Label | Icon |
|---|---|---|---|
| drivers-confirm | drivers/confirm | Confirm Drivers | users |
| excluded-drivers | drivers/excluded | Excluded Drivers | users |
| vehicles-confirm | vehicles/confirm | Confirm Vehicles | car |
| policy-coverages | coverages/policy | Policy Coverages | shield |
| vehicle-coverages | coverages/vehicle | Vehicle Coverages | shield-check |
| discounts | discounts | Discounts | tag |
| summary | summary | Summary | clipboard-list |
| checkout | checkout | Checkout | shopping-cart |
| checkout-complete | checkout/complete | Complete | check-circle |
| success | success | Success | check-circle |
| knockout | knockout | Eligibility Error | alert-circle |
Source: apps/fastlane-portal/src/app/pages/carriers/safeco/auto/flow-config.ts
Quote API Endpoints
| Endpoint | Method | When Used |
|---|---|---|
POST /ghcms/safeco/initial-quote | Initial quote | First load of policy coverages page |
POST /ghcms/safeco/update-quote | Update quote | After coverage/discount changes ("Update Pricing") |
POST /ghcms/safeco/rate-with-reports | Rate with reports | Final rate on Summary page before checkout |
POST /ghcms/safeco/rate | Direct RC2 | Direct RC2 rate call |
POST /ghcms/safeco/payment-plans | Payment plans | RC3 payment plan retrieval |
Initial Quote (CRN Bootstrap)
POST /ghcms/safeco/initial-quote does not call Safeco's API. Instead, it loads the existing quote from the CRN legacy database.
Flow:
getExistingCrnQuote(sessionData, quoteId, state)queriesCrnQuoteLookupService- Extracts from CRN:
CompanysQuoteNumber,CompanyClientID,carrierUrl,entityRefs,initialPricing,paymentOptions - Creates a
CarrierQuoteSessionrecord in Prisma (30-day TTL) - Returns CRN pricing with synthesized payment plans
This avoids an unnecessary API call since DA already rated the quote with Safeco.
Update Quote (Plain RC1)
POST /ghcms/safeco/update-quote runs a single RC1 call — no CC/ADD sub-transactions.
Flow:
- Bootstrap session from CRN if missing (
bootstrapSessionIfMissing) - Resolve active pricing — prefers
latestPricingoverinitialPricingfrom session metadata - Map to ACORD XML as plain RC1 (no sub-transaction type)
- Single API call to
apiClient.rateQuote() - Parse response, persist updated pricing
The previous pricing is sent as "echo-back" so Safeco maintains premium context.
Rate With Reports (Full 4-Step Flow)
POST /ghcms/safeco/rate-with-reports runs the complete Safeco rate flow:
| Step | Transaction | Purpose | Key Output |
|---|---|---|---|
| 1. CRN Bootstrap | None (DB only) | Load existing quote, create session | Entity refs, initial pricing |
| 2. RC1 + CC | RateCall1 + com.safeco_ReconcileCC | Prior insurance verification (CLUE) | CLUE results, updated entity refs |
| 3. RC1 + ADD | RateCall1 + com.safeco_ReconcileADD | Additional driver discovery | Driver candidates, updated entity refs |
| 4. RC2 | RateCall2 | Final rating with MVR | Premiums, coverages, discounts, payment plans |
See ACORD XML Protocol for detailed transaction documentation.
Session Management
Carrier Quote Sessions
Quote sessions are stored in carrier_quote_sessions (Prisma), keyed by (quoteId, SAFECO, AUTO).
| Field | Purpose |
|---|---|
carrierQuoteNumber | Safeco's CompanysQuoteNumber |
carrierClientId | Safeco's CompanyClientID |
carrierUrl | Safeco portal URL |
metadata.entityRefs | Driver/vehicle/location Safeco IDs |
metadata.initialPricing | CRN pricing (total, PIF, PPM) |
metadata.latestPricing | Most recent Safeco pricing |
metadata.paymentOptions | Payment plan data |
metadata.status | active, completed, or error |
expiresAt | 30-day TTL |
Pricing Resolution Cascade
When the service needs pricing, it checks three sources in order:
- Session field —
extractCrnPricingFromSession(): looks infinal_quotes_datafor Safeco quote withpremium_total__c,premium_total_pif__c,premium_total_ppm__c - Metadata —
buildPricingFromMetadata(): usesinitialPricingfrom session metadata - CRN recovery —
recoverCrnPricing(): re-queries CRN legacy DB as last resort, persists back to session
Payment Plan Synthesis
Safeco doesn't always return usable payment plans. The service has fallback logic:
- Safeco plans with real pricing → use as-is
- Safeco plans with zero pricing → override with CRN pricing (
overridePlansWithCrnPricing) - No Safeco plans → synthesize from CRN pricing:
- Full plan: total premium from CRN
- Monthly plan: down payment + installments estimated from total
- No CRN pricing, has FullTermAmt → synthesize from Safeco's
FullTermAmt(sum ofPersVeh[].FullTermAmt.Amt) - Scaling — when Safeco's FullTermAmt differs from CRN total, pricing is proportionally scaled
Frontend Usage
SafecoRateBox
The SafecoRateBox component (components/safeco-rate-box.tsx) displays pricing and handles rate calls:
| Mode | Hook | Trigger |
|---|---|---|
'initial' | useSafecoRate('initial') | Auto-fires on policy coverages page when no premium |
'update' | useSafecoRate('update') | "Update Price" button after coverage changes |
Summary Page
The Summary page calls executeRateCall(sessionId) (rate-with-reports) before checkout to get final pricing with MVR results.
Backend Handlers
| Handler | Service Method | Purpose |
|---|---|---|
safeco-initial-quote.handler.ts | getExistingCrnQuote() | Load CRN quote, no API call |
safeco-update-quote.handler.ts | executeUpdateRate() | Plain RC1 |
safeco-rate-with-reports.handler.ts | executeQuoteWithReports() | Full 4-step flow |
All handlers in apps/apis/fastlane-api-gateway/src/app/handlers/. They delegate to SafecoV3RateService in libs/apis/carriers/safeco/.
Error Handling
- ACORD status check —
StatusCd !== 0→ throw - Message status —
MsgStatusCdofFail,Error,Rejected→ throw withExtendedStatusdetails - Session error marking — on failure, session metadata is updated with
status: 'error', error message, and stack trace - Knockout detection — rejected quotes or zero premiums trigger navigation to the knockout page (see Knockouts)