| name | heater-appb-architecture |
| description | Use when starting any task on HEATER's live product — the api/ FastAPI backend (Railway service "celebrated-respect") or web/ Next.js frontend (heater-v1-0-1.vercel.app) — or when unsure where code lives, how a request flows, which database owns what, why CORS/Yahoo-writes/billing seem "missing", or which app (Streamlit vs React) is the real one. Load this first on App B work. |
HEATER App B — System Model
Repo: C:\Users\conno\Code\HEATER_v1.0.1 (GitHub name stays HEATER_v1.0.0). App B is the product: Next.js 16 on Vercel + FastAPI on Railway over shared, UNCHANGED Python engines in src/. App A (Streamlit, app.py + pages/) is a frozen strangler-fig fallback — never build there; it constrains App B only via frozen-reference equivalence tests on shared engines.
Request path
Browser → Vercel Next server → server-side rewrite /api/:path* → ${HEATER_API_BASE}/api/:path* (next.config.ts:3-13) → Railway FastAPI (api/main.py::create_app factory, ~line 31) → thin router (api/routers/) → service (api/services/*_service.py, the ONLY layer importing src/) → engines → SQLite.
CORS is moot. The proxy makes every browser call same-origin. HEATER_API_CORS_ORIGINS need not be set in prod; don't "fix" CORS for browser traffic.
The two databases — never cross them
| DB | Env | Owner | Contents |
|---|
data/draft_tool.db | HEATER_DB_PATH | scheduler is sole writer; API reads | players, projections, rosters, stats — everything engines read |
data/api_state.db | HEATER_API_DB_PATH | API stores (api/stores/) | api_users, api_subscriptions, api_saved_prompts, api_leagues, api_memberships, api_processed_events |
Auth/billing/tenancy state goes in api_state.db via a store (Protocol + InMemory + Sqlite pattern). Writing user state into draft_tool.db, or engine data into api_state.db, is a design violation. Single Railway replica is a hard invariant (in-process writer + SQLite; railway.api.toml numReplicas 1).
Boot & deploy
start-api.sh: seeds Yahoo token on first boot, backgrounds the scheduler loop (HEATER_SCHEDULER_BOOT=1, HEATER_SCHEDULER_IS_OWNER=1), then exec python -m uvicorn api.main:create_app --factory --port $PORT. It is a factory — local runs need uvicorn api.main:create_app --factory --port 8000. Push to master auto-deploys App B; each deploy re-bootstraps data (~10 min) before projections/pool are warm. Healthcheck /healthz; also /livez + /readyz (api/health.py).
Dormancy predicates (features are built + OFF until env set)
| Predicate | Env | Flips on |
|---|
clerk_configured() api/auth.py:166 | CLERK_ISSUER | real auth + 401 read-gate (require_login, require_viewer_context) |
billing_env_configured() api/billing_config.py:13 | STRIPE_SECRET_KEY and STRIPE_PRO_PRICE_ID | require_pro 402 gate + tier AI caps |
relay_enabled() src/token_relay.py:49 | HEATER_TOKEN_RELAY_URL + HEATER_RELAY_KEY | Yahoo token pulls |
| statcast relay gate src/statcast_relay.py:60 | HEATER_STATCAST_RELAY_URL | Savant xwOBA/barrel upserts |
On the live beta, Clerk is ON and Stripe is intentionally UNSET (friends are free). Absent Stripe env is not a bug.
Failure story (why config-file discipline matters)
2026-06-22: App B 502'd (Railway serviceDomain targetPort 8000 vs 8080) and was running Streamlit, not FastAPI — the service built the root Dockerfile until the Railway service setting railwayConfigFile=railway.api.toml pointed it at api/Dockerfile + start-api.sh. Rule: App B's build is defined by railway.api.toml; the root Dockerfile/railway.toml belong to App A.
Non-negotiables
src/ engines stay byte-identical for the live Streamlit app — add behavior in services or NEW src/ modules (see heater-appb-engine-composition).
- Everything additive + env-dormant; never activate owner-gated infra (Postgres/B2.2+, Clerk prod keys, Stripe live) without explicit owner go.
- Yahoo write scope (
fspt-w) is externally unavailable — HEATER is a read-only advisor; live writes 401 by design.
When NOT to use
Streamlit/App A page work (out of scope by decree); pure engine math questions (heater-appb-engine-composition); deep deploy ops (heater-appb-deploy-ops).