# Beat My Car Quote — Enterprise Architecture

## Locked commercial rules

| Tier | Monthly price (CAD) | Opportunity entitlement | Pricing automation |
|---|---:|---:|---|
| Bronze | $99.99 | Up to 5 per Stripe billing period | Dealer enters each offer |
| Silver | $199.99 | Up to 10 per Stripe billing period | Dealer enters each offer |
| Gold | $299.99 | All matching opportunities | Active inventory prices can generate offers automatically |

- An opportunity is consumed when it is assigned to a Bronze or Silver dealership, whether or not the dealership responds. Duplicate assignment does not consume an additional opportunity.
- Gold is not metered. Only dealerships with an active or trialing subscription are eligible.
- A customer report contains up to the three lowest eligible offers from three different dealerships, so one dealer cannot occupy multiple report positions.
- A report can never be emailed until an administrator explicitly approves that exact report version.
- Customer identity and direct contact information are never exposed in the dealer portal. Dealers see only vehicle specifications, response deadline, and the buyer market needed to price the request.

## Price-comparison rule

Offers are ranked by a comparable pre-tax price in Canadian cents:

`vehicle price + mandatory fees - unconditional discounts`

An offer is eligible only when it is submitted, availability is confirmed, and the quote has not expired. Conditional rebates, trade-in values, optional products, financing payments, and taxes do not reduce the comparable price. The report retains a price snapshot so a later dealer edit cannot change an already-approved report.

## Approval workflow

```mermaid
flowchart LR
  A["Paid customer request"] --> B["Matching job"]
  B --> C["Dealer opportunities"]
  C --> D["Submitted or Gold-generated offers"]
  D --> E["Select lowest 3 eligible prices"]
  E --> F["Draft report version"]
  F --> G["Admin review"]
  G -->|Reject| F
  G -->|Approve exact version| H["Approved"]
  H --> I["Email worker"]
  I --> J["Customer delivered"]
```

The database rejects a direct transition to `sent` unless the preceding state is `approved` and the approval identity and timestamp exist. Sending creates an idempotent email-delivery record. The customer request becomes `delivered` only after the email provider accepts the message.

## Portable production shape

```mermaid
flowchart TB
  CDN["CDN / rate limiting"] --> WEB["Stateless Next.js web instances"]
  WEB --> PG["Managed PostgreSQL"]
  WEB --> OBJ["S3-compatible object storage"]
  WEB --> STRIPE["Stripe Checkout and Billing"]
  PG --> WORKER["Independent background workers"]
  WORKER --> PG
  WORKER --> OBJ
  WORKER --> RESEND["Resend email"]
  STRIPE --> WEB
  RESEND --> WEB
```

The application talks to replaceable interfaces for billing, email, object storage, authentication, and background jobs. The core data is standard PostgreSQL. Gold uploads use CSV as the minimum portable format; the original file is retained in S3-compatible storage and normalized price rows are stored in PostgreSQL.

The first queue implementation is the `background_jobs` table. Workers claim work with `FOR UPDATE SKIP LOCKED`, so multiple workers can safely process matching, imports, report rendering, and email concurrently. A queue adapter can later be replaced by SQS, RabbitMQ, or Redis without changing opportunity or report tables.

## Capacity target

1,000 customer requests per hour is an average of about 0.28 requests per second. The production design should be load-tested at a substantially higher burst rate (initial target: 25 request submissions per second for five minutes) while background workers absorb fan-out. The web request path performs only validation and a short database transaction; dealer matching, Gold inventory comparison, PDF rendering, and email run outside it.

Initial production controls:

- PostgreSQL connection pooling and short transactions.
- Idempotency keys for Stripe events, opportunity matching, report creation, and email sends.
- Horizontally scalable stateless web and worker processes.
- Indexed matching, assignment, offer-ranking, and job-claim queries.
- Retry with exponential backoff and a dead-letter state after the maximum attempts.
- Rate limits on login, imports, quote submissions, and public checkout.
- Structured logs, error monitoring, queue-depth alerts, database backups, and restore drills.
- Encryption in transit and at rest, least-privilege database accounts, hashed session tokens, and an immutable audit trail.

This architecture is designed for the target; capacity is not considered proven until the deployed environment passes load, failover, restore, and security testing.

## Deployment boundaries

The current Spaceship-hosted site can remain the public front end during migration, but shared hosting should not be treated as the final enterprise runtime. Production should use:

1. A managed PostgreSQL database (Supabase, Neon, RDS, Cloud SQL, Azure PostgreSQL, or equivalent).
2. A container-capable web platform that can run multiple Next.js instances.
3. A separately scalable worker service.
4. S3-compatible private object storage for report PDFs and inventory upload originals.
5. A secrets manager and separate development, staging, and production environments.

No provider-specific database features are required by the initial migration other than PostgreSQL's broadly supported `pgcrypto` extension for UUID generation.

## Tables and ownership

- `dealerships`, `dealer_users`, `dealer_memberships`, `dealer_sessions`: dealer directory, organization membership, and sign-in.
- `dealer_subscriptions`: Stripe billing state and atomically metered current-period usage.
- `customer_requests`: private customer and vehicle request data.
- `opportunities`, `opportunity_assignments`: dealer-safe request data and scoped access.
- `inventory_imports`, `inventory_prices`: Gold price-list uploads and normalized active inventory.
- `dealer_offers`: manual and inventory-generated offers using integer cents.
- `report_versions`, `report_selected_offers`: immutable review versions and the approved lowest-three snapshot.
- `email_deliveries`, `webhook_events`, `background_jobs`: reliable external integrations and asynchronous work.
- `audit_events`: administrative, dealer, and automated activity history.

The `dealer_opportunity_view` intentionally omits all customer name, email, phone, notes, and exact postal-code fields.

## Implementation sequence

1. Provision staging PostgreSQL, apply the SQL migrations, and configure pooled and direct migration URLs.
2. Add repository adapters and dual-write new customer requests to JSON and PostgreSQL during validation.
3. Import existing requests and dealer-directory records, reconcile counts, then switch reads to PostgreSQL.
4. Add verified-email password activation, membership management, and the dealer-safe opportunity dashboard.
5. Create Stripe subscription products/prices and synchronize subscription state by signed webhooks.
6. Add manual offers, Gold CSV validation/import, automated matching, and inventory freshness rules.
7. Replace the single-deal report editor with the lowest-three review screen and versioned approval action.
8. Move matching, PDF generation, and email into workers; add monitoring and replay tools.
9. Run privacy/security review, accessibility testing, backup restore testing, and burst/load testing before production cutover.

## Required acceptance tests

- A sixth Bronze or eleventh Silver assignment is rejected under concurrent requests.
- Gold receives every exact eligible match and does not increment usage.
- Past-due, paused, cancelled, and expired subscriptions receive nothing.
- Dealers cannot read unassigned opportunities or any customer PII.
- Withdrawn, expired, unconfirmed, or conditional-only offers cannot rank.
- The report contains no more than three offers in deterministic price order.
- Editing an offer after report creation does not alter the report snapshot.
- No endpoint or worker can send a draft, rejected, or superseded report.
- Replayed Stripe and Resend webhooks do not create duplicate work or email.
- A worker crash releases work for retry without losing or duplicating it.
- The deployment sustains the agreed burst profile and recovers from a database failover.
