# QuitSure Discovery

A short (~5 minute) single-session web funnel for smokers who keep procrastinating quitting.
It delivers a guided, emotional experience and ends by sending the user to QuitSure's existing
web paywall to subscribe. It is live in production at `web.quitsure.app/discovery`.

The funnel itself is client-side only. This repo also ships a thin FastAPI shim (`app.py`) that
serves the built static site and adds a few best-effort, MySQL-backed API routes (personalization,
config, health, clickstream, session summary).

## Architecture

```
Browser ──▶ Apache (/discovery) ──▶ uvicorn app:app
                                      ├─ GET  /api/me          (personalize from ?u=<hash>)
                                      ├─ GET  /api/config      (paywall URL + redirect flag)
                                      ├─ GET  /api/health      (DB probe; 503 when DB down)
                                      ├─ POST /api/track-batch (clickstream, best-effort)
                                      ├─ POST /api/session     (session summary upsert, best-effort)
                                      └─ StaticFiles(web/out)  (the funnel itself)
```

- `app.py`: FastAPI shim. Loads `.env` first, then reads config (including `MOUNT_PREFIX`), then
  mounts the static export LAST so the API routes win.
- `db.py`: all MySQL access (PyMySQL, parameterized, column-whitelisted). Errors are logged and
  swallowed so the funnel never breaks.
- `web/out/`: the built Next.js static export, committed on purpose (see Deploy contract).

The POST routes are unauthenticated by design (public funnel), Pydantic-validated, and rate-limited
per IP (fail-open). They always return `{"ok": true/false}` with HTTP 200 (or 429 when rate-limited),
never a 5xx, so a transient DB issue can never break the funnel.

## Run locally

```bash
pip install -r requirements-dev.txt   # runtime deps + test/lint tooling
uvicorn app:app --reload --port 8081
```

Then open http://localhost:8081/ (or http://localhost:8081/discovery/ if you set
`MOUNT_PREFIX=/discovery` in your local `.env`). Copy `.env.example` to `.env` and fill in the DB
creds and `SUBSCRIPTION_URL` first.

## Tests, lint, type check

```bash
pytest -q          # unit + API tests (DB is mocked, no MySQL needed)
ruff check .       # lint
mypy .             # pragmatic type check
```

CI runs all three on every push/PR (`.github/workflows/ci.yml`), plus a separate job that builds the
funnel to verify it still compiles. Tool config lives in `pyproject.toml` (ruff and mypy target only
the Python files; `web/` is excluded).

## Deploy contract

Production deploys with exactly three steps, nothing more:

```bash
git pull
pip install -r requirements.txt   # runtime deps only
# restart uvicorn
```

- `web/out/` is committed on purpose, so the server needs NO Node and NO build step. Never hand-edit
  it; rebuild from `web/src`.
- `requirements.txt` is the only runtime install source. All lint/type/test tooling lives in
  `requirements-dev.txt` and is never installed on the server.
- The `Dockerfile` is OPTIONAL (local/CI parity only) and is NOT used in production.
- `MOUNT_PREFIX` is empty/absent on the server (Apache strips `/discovery`). Never put `/discovery`
  in the prod `.env`.

Deeper design rationale (payments handoff, tracking, no user creation) lives in `CLAUDE.md` and
`DISCOVERY_BE_BRIEF.md`.
