| name | heater-appb-debugging |
| description | Use when App B misbehaves and the cause isn't obvious — works locally but not on Railway/Vercel (or vice versa), a live page renders empty/zero/wrong data, an endpoint 500s or times out, or tests see different data than the app. Run this playbook before changing code. |
App B Debugging Playbook — live vs local divergence first
Most "bugs" in App B are environment/data divergence, not logic. Establish WHICH world is broken before touching code.
Step 0 — know the worlds
| Axis | Local dev | Railway App B |
|---|
| Yahoo league tables | usually EMPTY (rosters/matchups) → FA/team/matchup surfaces degrade honestly | live via token relay |
| FanGraphs | reachable | 403 (datacenter IP) → Marcel-fallback projections |
| Statcast xwOBA | via local fetch | only via Statcast relay env |
| DB | your 26MB data/draft_tool.db | container DB, REBUILT by bootstrap each deploy (~10 min warm-up) |
| git worktrees / CI | fresh EMPTY draft_tool.db — a query returning 0 rows there is expected, not a bug | — |
So: "value=0 / empty board on live only" → suspect data source (403, relay, bootstrap still warming), not code. "Works live, empty locally" → your Yahoo tables are empty; either accept the honest-empty or sync once via try_reconnect_yahoo().sync_to_db().
Step 1 — read the state, not the symptom
python -c "from src.database import get_refresh_log_snapshot; import json; print(json.dumps(get_refresh_log_snapshot(), indent=2))" — per-source status/tier/age. partial/cached/skipped are honest states, not failures.
python scripts/diag_live_accuracy.py (local): identity corruption, probables, projection volumes.
python scripts/diag_matchup_truth.py --api-base https://heater-v1-0-1.vercel.app --token <clerk-jwt> (live vs Yahoo truth). Get the JWT from the signed-in browser console: await window.Clerk.session.getToken().
- Railway logs via the GraphQL helper (see heater-appb-deploy-ops). Note: App B's
api.* INFO logs are hard to read there — prefer local repro or debug-via-response (add a reason/detail field to the contract; that's how the WS4 empty-trades class was made greppable).
Step 2 — reproduce at the right layer
- Run the API locally against your real DB:
uvicorn api.main:create_app --factory --port 8000 (it's a FACTORY — plain api.main:app fails) + the heater-web-live preview config, NEXT_PUBLIC_HEATER_LIVE=1.
- Endpoint-level: hit the service method directly in a REPL with the real pool — fake-service tests can't see engine-shape mismatches (the daily-mode "" matchup / {} urgency bugs were invisible to green synthetic tests).
- Frontend: usePageData dev override
?state=loading|error|empty isolates render vs data.
Step 3 — check the known bug classes before inventing a new one
ET-date skew (evening-ET wrongness → src/et_today.py); NaN leakage ("nan" badges); key-casing/unit mismatches at the engine seam (heater-appb-engine-composition); identity joins by name (heater-appb-player-identity); dormancy predicate flipped (401s = CLERK_ISSUER set?); Known Design Choices (heater-appb-known-non-bugs) — confirm a match and move on.
Failure story — fix the data model, not the symptom
Matchup rank columns showed scrubs/"Test Pitcher" outranking stars. The tempting fix was tweaking rank math; the actual root was ranking over the full ~9,888-row pool (minor leaguers + fixtures). Fix: player_rankings.fantasy_relevant_pool gate (85a340c), math untouched. Corollary: the live "FA value=0" (M-1) root was FanGraphs 403, fixed by the Marcel fallback — not by touching valuation. Ask "what data did this code actually receive?" before "what's wrong with this code?"
When NOT to apply
Clean test failures with a clear diff — just fix them (heater-appb-testing-ci for harness quirks). True deploy/infra outages (502s, env) → heater-appb-deploy-ops.