Customer / Quote — standalone architecture
Build just this flow: a customer (or producer — an insurance agent/broker) enters the property & product selection, the system prefills from data sources, scores perils via the cat-model, and emits a quote. It is the only externally-exposed flow, so it gets its own trust boundary (a hard line where outside traffic is checked before anything inside is reached). Advance the maturity stage to grow it.
Stage 1 · MVP. Vercel quote wizard → Cloud Run Quote API → one Cloud SQL (submission · outbox). Manual data entry; a working quote.
Stage 2 · Real product. Add the external prefill + cat-model services (so the customer confirms instead of typing), emit QuoteRequested, and put a WAF at the edge.
Stage 3 · Harden. Cache idempotent lookups (address resolution, property characteristics — never FCRA consumer-report data) in Redis to cut latency and vendor cost; add bot protection (quote-scraping is real).
Stage 4 · Scale. Go multi-region behind a global load balancer + CDN for low latency near customers.
📖 Flow story — what really happens
Every cross-flow arrow is the same async handoff — no flow calls another directly. 1. The sending flow commits its data row plus an outbox row in one transaction. 2. A relay publishes the event to the event bus (Pub/Sub on GCP · EventBridge→SQS on AWS). 3. The bus's push-subscription POSTs it to the receiving flow's Cloud Run API, which processes it idempotently (duplicates ignored, deduped on event_id). Synchronous reads (e.g. getProductVersion) are plain request/response.
- The customer/producer opens the portal — a Next.js app on Vercel behind the WAF; it holds no policy data and talks only to the BFF.
- They enter the address; the BFF makes a synchronous read to Keystone, the prefill service (request/response), to fill year built, construction, roof, sq ft — facts to confirm, not type. (Full prefill integration spec — vendors, costs, storage, refresh criteria.)
- They pick a line + form (line of business + policy form — e.g. HO-3 homeowners); another sync read,
getProductVersion(line, form, asOfDate), returns the immutable product and the quote workbench (the coverage-selection screen) renders its provisions/options. (How product versioning works — worked example.) - The cat-model is called (
POST /peril-scores, sync) for wildfire/flood/wind at that exact address; those scores + the prefilled property data set the coverage defaults — every defaulted provision/option traces back to a prefill field (field → provision mapping). - The customer edits coverages; each change re-evaluates compliance + risk rules live on the screen (nothing saved yet).
- On submit, the Quote API (Cloud Run) writes two rows in one transaction: the first append-only record — a
submissionrow (event-sourced:SubmissionCreated), stamped with the cat-model version + as-of date — and anoutboxrow holdingQuoteRequested. Then it commits. - The outbox relay publishes
QuoteRequested{submission_id, property, coverages, geo} to the event bus. - Underwriting's push-subscription delivers it to the Underwriting API (deduped on
event_id) — the submission now sits in the underwriter's queue.
Interfaces (black-box contract)
| Direction | Contract | Who |
|---|---|---|
| Emits | QuoteRequested {property, coverages, geo} | Underwriting |
| Calls (read) | getProductVersion(), POST /peril-scores, prefill | Product, Cat-model, Prefill (black boxes) |
| Consumes | — (entry point of the lifecycle) | — |