AI quoting posture parameters foundation

This draft adds the Dealops 2 data model, defaults, seeding path, resolver, and e2e coverage for good/better/best quote posture parameters.

Author: @pk675 PR: dealops#6672 Linear: DEA-7341 Status: draft Area: Dealops 2 / AI quoting Files: 10 Diff: +429 / -0 Migration: follow-up

What it adds

A new QuotingParameter model stores one posture value per organization and parameter, with room for future user-level and derived values.

How values start

definitions.json is the single defaults file. New orgs are seeded in the existing create-org transaction at default value 5.

How callers consume it

resolvePosture(orgId, userId?) returns every defined parameter with an org baseline plus the rep override when present.

Draft blocker

The Prisma migration is deliberately absent. Until it lands, the new e2e tests fail because the table does not exist in CI.

Prisma schema
Definitions
Seeder
Resolver
Repository
E2E tests

1. Why this exists

Program context

This is T1 of the good/better/best quoting chain: DEA-7341 → DEA-7345.

  • This PR creates the posture storage and read model.
  • Later PRs add APIs, prompt wiring, and 3-quote generation.
Problem being solved

The quote generator needs a durable answer to “what posture should this org or rep use?” before UI and prompt work can safely build on top.

Deal size
Close chance
Opinionated baseline: org posture always comes from the database after seeding. File defaults are used to define and seed the vocabulary, not to silently paper over an unseeded org at resolution time.

2. What changes

New model

packages/prisma/schema/models/v2/quoting-parameter.prisma

Adds QuotingParameter, QuotingParameterScope, and QuotingParameterName.

scopeORG or USER; user-level rows are future-ready.
parameterEnum-backed vocabulary: DEAL_SIZE, CLOSE_CHANCE.
valueConfigured posture value, intended range 1–10.
derivedValueReserved observed value for later outcome-derived posture display.
updatedByUserIdAudit hook for later admin/API updates.
Duplicate protection

Uniqueness is database-enforced for both row shapes.

  • @@unique([organizationId, parameter], where: { scope: "ORG" }) permits only one org baseline per parameter.
  • @@unique([organizationId, scope, userId, parameter]) permits only one user row per parameter.
  • The partial unique handles Postgres NULL behavior for ORG rows, where userId is null.
File / area Change Why reviewers should care
posture/definitions.json Defines DEAL_SIZE and CLOSE_CHANCE, descriptions, and default 5. Adding a future parameter should be one JSON entry plus matching enum/migration work.
posture/definitions.ts Loads JSON from disk and validates with Zod. Deploys fail loudly if definitions are malformed, duplicated, or missing an enum member.
seedDefaultQuotingParameters.ts Creates ORG rows from definitions using createMany({ skipDuplicates: true }). Seeder is idempotent and is intended to be the only creator of ORG rows.
quotingParameterRepository.ts Lists ORG rows and, optionally, one user's USER rows in a single query. Read path is org-scoped and centralized ahead of APIs.
resolvePosture.ts Builds the resolved posture payload: description, org value, rep value. Downstream prompt rendering can consume a stable shape.
organizations.prisma, users.prisma Add relation fields back to QuotingParameter. Enables real FKs to Organization and User.
merge-test/tests.ts Adds e2e coverage for seeding, uniqueness, missing seed behavior, and resolver precedence. Validates the actual DB constraints and resolver behavior, not just unit-level mapping.

3. How it works

1 Definitions load definitions.json declares each parameter and default value.
2 Definitions validate definitions.ts requires valid range, no duplicates, and full enum coverage.
3 Org creation seeds createOrganization calls the seeder inside the existing transaction.
4 Repository reads Query returns ORG rows plus matching USER rows for the requesting rep.
5 Posture resolves Each parameter returns orgValue and repValue.
Definition contract

loadQuotingParameterDefinitions() reads the JSON file next to the module and validates each item.

  • parameter must be a QuotingParameterName.
  • defaultValue must be an integer from 1 to 10.
  • Every enum member must be present exactly once.
Create-org hook

apps/server/src/utils/organizations.ts now seeds quoting parameters next to existing org settings.

await seedDefaultOrgSettings(tx, org.id, { pricingFlowType });
await seedDefaultQuotingParameters(tx, org.id);
Resolver precedence

resolvePosture(orgId, userId?) makes rep-level values additive, not required.

  • ORG row exists → orgValue.
  • USER row exists → repValue from user row.
  • No USER row → repValue = orgValue.
  • Missing ORG row → throw loudly.
Before

No durable quoting posture vocabulary or seeded per-org baseline existed for the good/better/best renderer to read.

  • No QuotingParameter table.
  • No global defaults file.
  • No resolver contract for prompt code.
After

The system has a database-backed org baseline and a stable resolver shape that future APIs and prompt wiring can build on.

  • Org defaults are created transactionally.
  • Uniqueness is enforced in the DB.
  • Rep overrides are represented without another migration.

4. Test coverage

Seeding

Creates one ORG row per definition, at the JSON default, and can be run twice without extra rows.

Constraint

Attempts to insert a second ORG row for DEAL_SIZE reject with a unique constraint error.

Missing seed

resolvePosture rejects when an org has no ORG row, treating it as a provisioning bug.

Precedence

Org edits win over file defaults; USER rows become repValue; missing USER rows mirror orgValue.

Test fixture shape

The e2e tests create TEST-prefixed throwaway organizations and clean up quotingParameter, orgSetting, and organization rows in finally.

5. What it doesn't change

Explicit non-goals in this PR

6. Risks / rollback / open questions

Current draft risk: the migration is not in this PR. The new e2e tests are expected to fail in CI until the follow-up migration creates QuotingParameter, enums, FKs, and partial unique indexes.
Rollback

Before the migration lands, rollback is just reverting code and schema additions. After migration, rollback also needs to drop the new table/enums or leave them unused.

Migration follow-up

The migration needs to backfill existing organizations with ORG rows; otherwise resolvePosture correctly throws for those orgs.

Validation boundary

The 1–10 range is validated in app code and future tRPC schemas, not as a DB CHECK constraint.