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:

  1. policy.submissions — the submission header
  2. policy.quotes — a draft quote linked to it
  3. policy.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.

transactionTypeMeaning
new_businessFirst-time insured — no prior policy
new_renewalCarrier-initiated renewal of an existing policy
endorsementMid-term change to an in-force policy

For endorsements, three additional columns are populated:

ColumnPurpose
priorPolicyIdFK to the in-force policy.policies row being changed
changeEffectiveDateThe date the change takes effect (not the original inception)
endorsementTypee.g. add_driver, add_vehicle, coverage_change, name_change
endorsementReasonFree-text audit trail

Status Lifecycle

Rendering diagram…
StatusMeaning
openRisk data is being collected in the wizard. No premium yet.
quotedAt least one rating engine call succeeded. writtenPremium is set on the Quote.
boundA quote was selected and accepted. A policy.policies row was created.
issuedThe DEC page was generated and delivered to the insured / producer.
declinedCarrier will not write this risk. Terminal.
withdrawnInsured 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:

sourceChannelInitiated by
producerLicensed agent/broker portal
directInsured self-service portal
apiThird-party integration / aggregator
agent_aiInternal AI agent (auto-submission)

What a Submission Owns

Rendering diagram…

submission_parties

All parties named on the submission and their roles. Order 0 is always the named insured.

RoleMeaning
named_insuredPrimary insured — always required
additional_insuredAlso covered under the policy
co_insuredJoint named insured
additional_interestHas insurable interest but no coverage
mortgageeLender with mortgage on the insured property
landlordProperty owner / lessor
lienholderCreditor with a lien
loss_payeeReceives claim proceeds directly
certificate_holderReceives 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

Rendering diagram…

C4 Context Diagram

Rendering diagram…

Query Functions

All submission query functions live in packages/db/src/queries/submissions.ts and are re-exported from @policyone/db.

FunctionPurpose
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.