Submission
What is a Submission?
A Submission is the single container that represents a piece of risk being offered to the carrier for consideration. Everything that happens before a policy is issued lives inside a Submission: data collection, rating, UW review, and bind.
Submissions are never deleted. They move through a linear status chain and end in either issued, declined, or withdrawn.
When is a Submission Created?
A Submission is always created by calling createSubmissionWithQuote(). This function runs atomically and creates three rows in a single transaction:
policy.submissions— the submission headerpolicy.quotes— a draft quote linked to itpolicy.quote_versions— version 1 snapshot (empty,status = 'draft')
// packages/db/src/queries/submissions.ts
const result = await createSubmissionWithQuote(input, quoteNumber)
// Returns: { submissionId, submissionNumber, quoteId, quoteNumber }
You can never have a Submission without a Quote. They are born together.
Transaction Types
The transactionType column controls which intake flow is used.
transactionType | Meaning |
|---|---|
new_business | First-time insured — no prior policy |
new_renewal | Carrier-initiated renewal of an existing policy |
endorsement | Mid-term change to an in-force policy |
For endorsements, three additional columns are populated:
| Column | Purpose |
|---|---|
priorPolicyId | FK to the in-force policy.policies row being changed |
changeEffectiveDate | The date the change takes effect (not the original inception) |
endorsementType | e.g. add_driver, add_vehicle, coverage_change, name_change |
endorsementReason | Free-text audit trail |
Status Lifecycle
| Status | Meaning |
|---|---|
open | Risk data is being collected in the wizard. No premium yet. |
quoted | At least one rating engine call succeeded. writtenPremium is set on the Quote. |
bound | A quote was selected and accepted. A policy.policies row was created. |
issued | The DEC page was generated and delivered to the insured / producer. |
declined | Carrier will not write this risk. Terminal. |
withdrawn | Insured or producer pulled the submission. Terminal. |
The updateSubmissionStatus() query function transitions the status. The app is responsible for enforcing valid state transitions before calling it.
Source Channels
Who or what initiated the submission:
sourceChannel | Initiated by |
|---|---|
producer | Licensed agent/broker portal |
direct | Insured self-service portal |
api | Third-party integration / aggregator |
agent_ai | Internal AI agent (auto-submission) |
What a Submission Owns
submission_parties
All parties named on the submission and their roles. Order 0 is always the named insured.
| Role | Meaning |
|---|---|
named_insured | Primary insured — always required |
additional_insured | Also covered under the policy |
co_insured | Joint named insured |
additional_interest | Has insurable interest but no coverage |
mortgagee | Lender with mortgage on the insured property |
landlord | Property owner / lessor |
lienholder | Creditor with a lien |
loss_payee | Receives claim proceeds directly |
certificate_holder | Receives certificate of insurance only |
Each party links to a policy.parties row (name, contact info, addresses). See the Party page for the party entity.
submission_draft_form_data
Wizard progress snapshots. Each row is a JSON blob for one wizard step (stored as data jsonb). Used to restore the form on navigation and to hydrate the rating engine payload. Never read this column directly in rating code — fetchSubmissionDraftContext() aggregates all steps into a single structured object.
Key Columns Reference
policy.submissions
─────────────────────────────────────────────────────────────────
id uuid PK
submission_number varchar(30) UNIQUE — SUB-{YEAR}-{SEQ8}
tenant_id uuid NOT NULL — row-level tenant isolation
customer_id varchar NOT NULL — cross-system PartyOne reference (no FK)
transaction_type varchar(20) — 'new_business' | 'new_renewal' | 'endorsement'
prior_policy_id uuid — FK policies.id (endorsements only)
prior_policy_number varchar(30) — denormed for display
change_effective_date date — endorsements only
endorsement_type varchar(40) — endorsements only
endorsement_reason text — endorsements only
state_code char(2) NOT NULL — ISO state — drives filed rate selection
source_channel varchar(20) — 'producer' | 'direct' | 'api' | 'agent_ai'
assigned_to uuid — FK users.id (underwriter / CSR)
producer_membership_id uuid — no FK; cross-schema distribution ref
internal_producer_code_id uuid — no FK; cross-schema
producer_code varchar(50) — denormed for DEC page & commission
agency_id uuid — no FK; cross-schema distribution ref
status varchar(20) — 'open' | 'quoted' | 'bound' | 'issued'
| 'declined' | 'withdrawn'
ai_context jsonb — AI agent reasoning only; NOT wizard data
created_at / updated_at timestamptz
─────────────────────────────────────────────────────────────────
Creation Flow
C4 Context Diagram
Query Functions
All submission query functions live in packages/db/src/queries/submissions.ts and are re-exported from @policyone/db.
| Function | Purpose |
|---|---|
createSubmissionWithQuote(input, quoteNumber) | Atomically creates submission + draft quote + v1 snapshot |
fetchSubmissionDetail(id) | Full submission row with parties, current quote, and assigned user |
fetchSubmissionDraftContext(id) | Aggregates all wizard step blobs into one structured context object |
saveSubmissionDraftData(id, stepKey, data) | Upserts a single wizard step snapshot |
searchSubmissions(filter) | Paginated search — status, assignee, agency, producer, customer |
updateSubmissionStatus(id, status) | Transition status — validates tenant context |
updateSubmissionAssignee(id, userId) | Assign to underwriter / CSR |
listSubmissionParties(submissionId) | All parties on a submission ordered by order_num |
upsertSubmissionParty(input) | Add or update a party role on a submission |
removeSubmissionParty(submissionId, partyId) | Remove a party from a submission |
updateSubmissionPartyRole(id, role) | Change the role of an existing submission party |
Design Notes
customerId is not a foreign key. It references the external PartyOne system which lives in a separate service. The policy.parties table holds a local copy of the party details for performance; customerId is the cross-system reference used to look up or sync that local copy.
producerCode is denormalised. It is copied from distribution.producer_agency_memberships.producer_code at submission creation and never updated. This ensures the DEC page always shows the code that was in effect when the risk was submitted, even if the producer's code changes later.
The wizard never writes to submissions.ai_context. The ai_context JSONB column is reserved exclusively for AI agent reasoning metadata (model, version, confidence, rules evaluated). Wizard form data goes to submission_draft_form_data.
One Quote per Submission today. The schema supports multiple quotes (A/B pricing options) via the quotes table FK, but the current wizard only creates one. The bind flow always reads quotes WHERE submission_id = :id ORDER BY created_at DESC LIMIT 1.