For: DevOps Engineer Generated: 2026-03-05 Version: 801566a (main)
Braves Booth Intelligence is a real-time AI-powered broadcast operations dashboard for the Atlanta Braves radio booth. It surfaces player stats, AI-generated narratives, matchup history, and situational intelligence to announcers and producers during live MLB games — with sub-6-second data latency from MLB's live feed to the broadcast booth.
The system serves a single, high-stakes use case: Ben Ingram and his co-hosts open a browser tab before first pitch and get pre-populated, LLM-enhanced talking points for every at-bat. The dashboard updates automatically as the game progresses — batter changes, pitcher substitutions, score updates — without any manual intervention.
The tech stack is a three-service monorepo (Fastify backend, React frontend, FastAPI Python service) deployed on Google Cloud Platform. The core architectural constraint is zero network dependencies during live broadcast for all cached data — SQLite over Firestore, pre-generation over live LLM calls, graceful degradation over hard failures.
Current status: Production-deployed at braves-booth.web.app with CI/CD automation. Two known bugs (co-host panel blank due to missing profile data, data staleness due to no lineup-phase awareness) and several enhancement opportunities documented. Estimated monthly cost: $3-8/month on Cloud Run.
| Environment | Status | Uptime Target | Release Cadence |
|---|---|---|---|
| Production (Cloud Run + Firebase) | Active | Best-effort (broadcast hours) | Push-to-main auto-deploy |
| Local Dev (Docker Compose) | Active | N/A | Continuous |
| Staging | Not implemented | — | — |
| Category | Technology | Version | Purpose |
|---|---|---|---|
| Frontend | React + Vite + Tailwind CSS | 18.3 / 6.0 / 4.2 | SPA dashboard |
| Backend | Fastify + TypeScript | 5.2 / 5.7 | API server, SSE, orchestration |
| Python | FastAPI + pybaseball | 0.115+ / 2.2.7 | Statcast analytics pipeline |
| Database | SQLite (better-sqlite3) | 12.6 | Local persistence (WAL mode) |
| Cache | node-cache | 5.1 | In-memory TTL cache |
| AI | Vertex AI (Gemini 2.5 Pro) | 1.10 | Narrative generation |
| Hosting | Firebase Hosting | — | CDN + SPA routing |
| Compute | Cloud Run | — | Backend + Python services |
| CI/CD | GitHub Actions | — | Test + deploy pipeline |
| Registry | Artifact Registry | — | Docker images |
| Auth | Workload Identity Federation | — | GitHub → GCP keyless auth |
| Category | Score | Notes |
|---|---|---|
| Architecture | 9/10 | Clean separation, event-driven, three-tier cache |
| Code Quality | 9/10 | Strict TypeScript, no any types, zero TODOs |
| Test Coverage | 6/10 | Backend 76 tests (good), Frontend 10 tests (thin), Python 16 tests |
| Operations | 5/10 | No monitoring, no alerting, no IaC, no staging |
| Security | 8/10 | No secrets in code, parameterized SQL, input validation, WIF auth |
| Documentation | 8/10 | 11 indexed docs, comprehensive CLAUDE.md, good README |
| Resilience | 7/10 | Circuit breaker, graceful degradation, but no retry/backoff on MLB API |
┌─ BROWSER ────────────────────────────────────────────────────────────────┐
│ React 18 + Vite 6 + Tailwind v4 │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ GameContext (SSE → state provider) │ │
│ │ ├── TopNav (co-host selector, mode toggle, clock) │ │
│ │ ├── GameStateBar (score, inning, count, bases) │ │
│ │ ├── DashboardGrid (3-column) │ │
│ │ │ ├── BatterCard (stats, splits, narrative, back-of-card) │ │
│ │ │ ├── PitcherCard (tonight line, pitch mix, velo, K stats) │ │
│ │ │ └── RightSidebar │ │
│ │ │ ├── SituationalPanel (weather, umpire, park factors) │ │
│ │ │ ├── OnDeckPanel (on-deck + in-hole) │ │
│ │ │ ├── CohostPanel (broadcaster profile) │ │
│ │ │ └── BullpenPanel (defense mode) │ │
│ │ ├── Ticker (scrolling league scores) │ │
│ │ └── QueryBar (Cmd+K AI Q&A) │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ SSE │ REST │
└──────────┼────────────────────────┼──────────────────────────────────────┘
│ │
▼ ▼
┌─ BACKEND (Fastify 5 + TypeScript, Cloud Run) ───────────────────────────┐
│ │
│ GUMBO Poller ──► Event Bus ──► SSE Route ──► Connected Clients │
│ (5s poll) (EventEmit) (/api/events) │
│ │
│ Game State Prefetch Engine AI Service (Vertex AI) │
│ (in-memory) (pregame.ts) (Gemini 2.5 Pro) │
│ │
│ ┌────────────────── Caching Layer ──────────────────────┐ │
│ │ L1: node-cache (in-memory, per-key TTL 60s-24h) │ │
│ │ L2: SQLite (WAL mode, narrative_log + player_stats) │ │
│ │ L3: API fetch + AI generation (on-demand) │ │
│ └───────────────────────────────────────────────────────┘ │
└────────┬─────────────────────────────────┬───────────────────────────────┘
│ │
▼ ▼
┌─ MLB Stats API ──────┐ ┌─ PYTHON SERVICE (FastAPI, Cloud Run) ─────┐
│ statsapi.mlb.com │ │ Circuit Breaker (3 fail → 60s cooldown) │
│ ├── Live Feed (GUMBO)│ │ TTL Caches (30min statcast, 1hr matchup) │
│ ├── Player Stats │ │ pybaseball → Statcast analytics │
│ ├── Schedule │ │ Endpoints: │
│ ├── Standings │ │ ├── /statcast/batter/:id │
│ └── Boxscore │ │ ├── /statcast/pitcher/:id │
├──────────────────────┤ │ ├── /splits/:id/:type │
│ Open-Meteo (weather) │ │ ├── /matchup/:batterId/:pitcherId │
├──────────────────────┤ │ └── /batch/prefetch │
│ Vertex AI (Gemini) │ └───────────────────────────────────────────┘
└──────────────────────┘
| Layer | Technology | Version | Purpose | Config |
|---|---|---|---|---|
| Frontend | React | 18.3.1 | Component UI | — |
| Frontend | Vite | 6.0.0 | Build + HMR | vite.config.ts |
| Frontend | Tailwind CSS | 4.2.1 | Utility CSS | globals.css |
| Frontend | TypeScript | 5.7 | Type safety | tsconfig.json (strict) |
| Backend | Fastify | 5.2.0 | HTTP + SSE server | — |
| Backend | @google-cloud/vertexai | 1.10.0 | Gemini 2.5 Pro | GCP_PROJECT_ID env |
| Backend | better-sqlite3 | 12.6.2 | Embedded database | DB_PATH env |
| Backend | node-cache | 5.1.2 | In-memory TTL cache | cache.ts TTL constants |
| Backend | Pino | 9.6.0 | Structured logging | LOG_LEVEL env |
| Backend | TypeScript | 5.7 | Type safety | tsconfig.json (strict) |
| Python | FastAPI | 0.115+ | Statcast API | — |
| Python | pybaseball | 2.2.7+ | Baseball Reference scraper | — |
| Python | cachetools | 5.3+ | TTL caches | cache.py |
| Python | httpx | 0.28+ | Async HTTP client | — |
| Infra | Docker Compose | — | Local dev orchestration | docker-compose.yml |
| Infra | Cloud Run | — | Production compute | deploy.yml |
| Infra | Firebase Hosting | — | CDN + SPA + API proxy | firebase.json |
| Infra | Artifact Registry | — | Container images | us-east4 |
| Infra | GitHub Actions | — | CI/CD | ci.yml + deploy.yml |
MLB GameFeed API (5s poll)
→ GUMBO Poller (parse + diff)
→ Event Bus (batter-change, pitcher-change, score-change, game-state)
→ SSE Route (/api/events) → Browser EventSource
→ useGameState hook → GameContext → All Dashboard Panels
Prefetch Pipeline (boot + batter/pitcher changes):
→ Roster extraction from boxscore
→ Parallel stat fetch (concurrency=5): MLB API + Python statcast
→ AI narrative generation (Gemini 2.5 Pro)
→ Three-tier cache (memory → SQLite → serve)
braves/
├── .github/workflows/
│ ├── ci.yml # Lint + typecheck + test (all 3 services)
│ └── deploy.yml # Python → Backend → Frontend deploy
├── 000-docs/ # 12 indexed architecture/design docs
│ ├── 001-PP-PROD-*.md # PRD v1.0
│ ├── 002-AT-ARCH-*.md # Technical infrastructure spec
│ ├── 003-AT-DSGN-*.md # Design system
│ ├── 004-RL-RSRC-*.md # Research brief
│ ├── 005-WA-WFLW-*.md # Workflow automation
│ ├── 006-RL-RSRC-*.md # Deep research findings
│ ├── 007-AT-ARCH-*.md # Worldmonitor baseline audit
│ ├── 008-PM-PLAN-*.md # Git workflow
│ ├── 009-TQ-TEST-*.md # Testing strategy
│ ├── 010-AT-ARCH-*.md # Architect review (7 critical fixes)
│ ├── 011-AT-DSGN-*.md # Frontend gap analysis
│ └── 012-AA-AUDT-*.md # This document
├── backend/
│ ├── src/
│ │ ├── server.ts # Fastify bootstrap + prefetch hooks
│ │ ├── db.ts # SQLite init (4 tables, WAL mode)
│ │ ├── lib/ # config, cache, http-client, logger
│ │ ├── services/ # gumbo-poller, game-state, event-bus,
│ │ │ # pregame, ai, mlb-stats, matchup,
│ │ │ # player-db, cohost, umpire
│ │ └── routes/ # 12 route files (SSE, batter, pitcher,
│ │ # narrative, query, game, matchup,
│ │ # ticker, weather, umpire, cohost, health)
│ ├── data/ # Umpire defaults JSON
│ ├── scripts/ # find-game.ts, test-endpoints.ts
│ ├── Dockerfile # Multi-stage Node 22 build
│ ├── package.json # 8 runtime + 7 dev dependencies
│ ├── tsconfig.json # Strict mode, ES2022, ESNext modules
│ ├── eslint.config.js # Flat config, typescript-eslint
│ └── .env.example # All env vars documented
├── frontend/
│ ├── src/
│ │ ├── components/ # 16 components (layout, panels, shared)
│ │ ├── hooks/ # useGameState, useFetch, usePolling
│ │ ├── context/ # GameContext (SSE state provider)
│ │ ├── lib/ # api.ts (all backend calls + normalizers)
│ │ ├── types/ # api.ts, events.ts
│ │ └── styles/ # globals.css (design system)
│ ├── Dockerfile # Node 22 dev-mode container
│ ├── package.json # 3 runtime + 10 dev dependencies
│ ├── tsconfig.json # Strict, noUncheckedIndexedAccess
│ ├── vite.config.ts # React + Tailwind plugins, API proxy
│ └── eslint.config.js # Flat config, react-hooks plugin
├── python/
│ ├── app/
│ │ ├── main.py # FastAPI app, 5 routers
│ │ ├── routes/ # health, statcast, splits, matchup, batch
│ │ ├── services/ # statcast (pybaseball wrapper), prefetch
│ │ └── lib/ # circuit_breaker, cache
│ ├── tests/ # 4 test files, 16 tests
│ ├── scripts/ # prefetch.py, test-statcast.py
│ ├── Dockerfile # Python 3.12-slim
│ ├── requirements.txt # 5 runtime dependencies
│ └── pyproject.toml # Ruff + pytest config
├── docker-compose.yml # 3 services + booth-data volume
├── firebase.json # Hosting + API rewrite to Cloud Run
├── .firebaserc # Project: braves-booth
├── Makefile # 20+ targets (dev, test, lint, deploy)
├── team-config.json # Braves team config (id, stadium, colors)
├── CLAUDE.md # AI assistant guidance
└── README.md # Quick start + architecture overview
- backend/src/services/: Core business logic — GUMBO poller, prefetch orchestrator, AI narrative engine, MLB Stats API client, dual-source matchup resolver. 9 service files, all with clean separation of concerns.
- backend/src/routes/: 12 HTTP route files. All async with try/catch error handling. Parameterized SQL queries throughout (no injection risk).
- frontend/src/components/panels/: 6 dashboard panels. BatterCard is largest (1,140+ lines). Each panel manages its own data fetching via hooks.
- python/app/lib/: Circuit breaker (CLOSED→OPEN→HALF_OPEN state machine) and TTL caches (statcast 30min, splits/matchup 1hr).
Prerequisites:
- Docker Desktop (or Docker Engine + Compose)
- Node.js 22+ (for running individual services)
- Python 3.12+ (for Python service outside Docker)
- GCP credentials:
gcloud auth application-default login(for Vertex AI)
Setup:
git clone <repo>
cd braves
cp backend/.env.example backend/.env
make dev # Docker Compose up --build (all 3 services)Verification:
curl http://localhost:3001/api/health # Backend health
curl http://localhost:8001/health # Python health
open http://localhost:5173 # Frontend dashboard
make test-sse # Test SSE stream
make find-game # Find today's gamePkIndividual services (without Docker):
make dev-backend # cd backend && npm run dev (tsx watch, port 3001)
make dev-frontend # cd frontend && npm run dev (vite, port 5173)
make dev-python # uvicorn app.main:app --reload --port 8001Pre-flight checklist:
- All CI checks pass (lint, typecheck, test for all 3 services)
- No
.envfiles staged -
team-config.jsonupdated if team config changed - GCP service account permissions verified
Execution:
Automatic on push to main via .github/workflows/deploy.yml:
- Python service → Build Docker image → Push to Artifact Registry → Deploy to Cloud Run (
braves-booth-python, 1GB/1CPU, 0-2 instances) - Backend service → Copy team-config.json into build context → Build → Push → Deploy to Cloud Run (
braves-booth-backend, 512MB/1CPU, 0-2 instances,--no-cpu-throttling,--session-affinity, 1hr timeout) - Frontend → Vite build with VITE_SSE_URL → Copy to
public/→ Deploy to Firebase Hosting - Health check →
curl https://braves-booth.web.app/api/health
Rollback protocol:
# List Cloud Run revisions
gcloud run revisions list --service braves-booth-backend --region us-east4
gcloud run revisions list --service braves-booth-python --region us-east4
# Rollback to previous revision
gcloud run services update-traffic braves-booth-backend \
--to-revisions=<previous-revision>=100 --region us-east4
# Firebase Hosting rollback
firebase hosting:channel:list # or revert via Firebase Console| Aspect | Current State | Recommendation |
|---|---|---|
| Health checks | Backend + Python /health endpoints |
Add Cloud Run uptime checks |
| Logging | Pino structured JSON (backend), stdout (Python) | Cloud Logging already captures; add log-based alerting |
| Metrics | None | Add Cloud Run metrics dashboard (latency, errors, instances) |
| Error tracking | None | Add Sentry or Cloud Error Reporting |
| Dashboards | None | Create Cloud Monitoring dashboard |
| Alerting | None | Cloud Monitoring alerts on 5xx rate, latency P95, instance count |
| Severity | Definition | Response Time | Playbook |
|---|---|---|---|
| P0 | Dashboard down during live broadcast | Immediate | Check Cloud Run logs, restart service, rollback if needed |
| P1 | SSE disconnections during game | 5 min | Check Cloud Run instance health, verify --no-cpu-throttling |
| P2 | Stale data / missing narratives | 15 min | Check Vertex AI quota, Python circuit breaker state, cache TTLs |
| P3 | Non-game-day issues | Next business day | Standard triage |
| Role | Resource | Purpose | Auth Method |
|---|---|---|---|
bb-backend-runtime |
Cloud Run backend | Runtime SA for Vertex AI access | GCP SA |
bb-deploy |
Cloud Run + Artifact Registry | CI/CD deployment | WIF (GitHub Actions) |
| GitHub Actions | GCP via WIF | Keyless auth for deploys | Workload Identity Federation |
| Developer | Local | Vertex AI via ADC | gcloud auth application-default login |
| Secret | Storage | Rotation |
|---|---|---|
| GCP credentials | Workload Identity Federation (no keys) | N/A (keyless) |
| GitHub secrets (WIF_PROVIDER, WIF_SERVICE_ACCOUNT) | GitHub encrypted secrets | Manual |
| No API keys for MLB Stats API | N/A (public API) | N/A |
| No database passwords | N/A (SQLite, no auth) | N/A |
- No hardcoded secrets in codebase (verified: grep for API keys, passwords, tokens — zero results)
- Parameterized SQL throughout (no SQL injection vectors)
- Input validation on all numeric IDs (parseInt + isNaN guard)
- CORS properly configurable (permissive in dev, strict in production)
- No
eval(),exec(), orFunction()in any service - No unsafe deserialization patterns
- AbortSignal timeouts on all external HTTP calls (3-5s)
- Python Dockerfile runs as root (recommendation: add
USER nobody)
| Resource | Cost | Notes |
|---|---|---|
| Cloud Run (Backend) | $1-3/mo | ~1,100 req/game x 162 games, min instances=0 |
| Cloud Run (Python) | $1-3/mo | Prefetch bursts, idle between games |
| Firebase Hosting | $0 | Free tier (< 10 GB/mo bandwidth) |
| Artifact Registry | $0.10/mo | Image storage |
| Vertex AI (Gemini) | $1-2/mo | ~20 narratives/game, pre-generated |
| Total | $3-8/mo |
| Metric | Target | Current |
|---|---|---|
| MLB feed poll → SSE push | < 6s | ~5s (5s poll interval) |
| Batter change → narrative ready | < 3s | 1.5-2.5s (prefetched) |
| Dashboard cold load | < 2s | ~1.5s (Firebase CDN) |
| AI narrative generation | < 5s | 2-4s (Gemini 2.5 Pro) |
| Python statcast fetch | < 10s | 3-8s (pybaseball, circuit-broken) |
| Key | TTL | Justification |
|---|---|---|
| Schedule (today's games) | 60s | Frequent checks during game |
| Player stats | 5 min | Season stats don't change fast |
| Standings | 5 min | Updated between games |
| Matchup (H2H) | 1 hour | Historical data, stable |
| Narrative | 5 min (memory) / persistent (SQLite) | Re-serve within game |
| Umpire | 24 hours | Doesn't change |
| Weather | 5 min | Updated frequently enough |
| Statcast (Python) | 30 min | Pre-game data, stable |
| Splits (Python) | 1 hour | Historical, stable |
- Real-time pipeline: GUMBO poller → EventEmitter → SSE streaming works reliably with 5s latency
- Three-tier caching: Memory → SQLite → API/AI generation prevents redundant fetches
- Prefetch system: Boot-time + reactive prefetch (on-deck, pitcher-change) keeps data warm
- AI narratives: Gemini 2.5 Pro generates quality radio-ready talking points in 2-4s
- Dual-source matchup: MLB Stats API + Python statcast in parallel with graceful partial failure
- Circuit breaker: Python service protected from pybaseball hangs/failures
- CI/CD: Automated testing + deployment on every push to main
- Code quality: Strict TypeScript (no
any), zero TODO/FIXME, comprehensive error handling - Backend test suite: 76 tests covering routes, services, state management, AI parsing
- Security: No secrets in code, WIF auth, parameterized SQL, input validation
- Documentation: 11 indexed docs + comprehensive CLAUDE.md + README
- Co-host panel broken: Missing
backend/data/cohost-profiles/*.jsonfiles — directory doesn't exist - Data staleness: No lineup-phase awareness — system fetches once at boot, never re-fetches when lineups are posted (~90 min before first pitch)
- Frontend test coverage thin: Only 10 tests across 4 files (20% component coverage). BatterCard (1,140 lines), PitcherCard, useGameState all untested
- No error boundary: Single failing React component crashes entire dashboard
- No monitoring/alerting: No Sentry, no Cloud Monitoring alerts, no dashboards
- No Infrastructure-as-Code: Cloud Run config is imperative CLI in deploy.yml, not reproducible
- No staging environment: Code goes straight from local → production
- Python Dockerfile runs as root: Missing
USERdirective - Placeholder UI panels: BullpenPanel shows "Data loading...", umpire section is placeholder
- No retry/backoff on MLB API: GUMBO poller catches errors but doesn't retry — waits 5s for next poll
- pybaseball no explicit timeout: Thread executor calls to pybaseball can hang indefinitely
-
[Critical] Fix co-host panel — Create
backend/data/cohost-profiles/with broadcaster JSON files. Impact: Feature F8 (Co-Host Intelligence) completely non-functional. Fix: 30 minutes. -
[Critical] Fix data staleness — Add lineup-watch poller that re-triggers prefetch when lineups are confirmed. Impact: Announcers see stale/missing data at first pitch. Fix: 1-2 days.
-
[High] Add React error boundary — Single component failure shouldn't crash the dashboard during a live broadcast. Impact: Runtime resilience. Fix: 2 hours.
-
[High] Add Cloud Monitoring alerts — 5xx error rate, latency P95, instance scaling. Impact: No visibility into production health. Fix: Half day.
-
[Medium] Increase frontend test coverage — Prioritize BatterCard, PitcherCard, useGameState. Impact: Regression risk on core components. Fix: 2-3 days.
-
[Medium] Add pybaseball timeout — Wrap thread executor calls with explicit timeout to prevent indefinite hangs. Impact: Worker thread exhaustion during live game. Fix: 1 hour.
-
[Low] Add staging environment — Firebase preview channel + separate Cloud Run services. Impact: No pre-production validation. Fix: Half day.
| Capability | Command | Notes |
|---|---|---|
| Start all services | make dev |
Docker Compose, all 3 services |
| Start backend only | make dev-backend |
tsx watch, port 3001 |
| Start frontend only | make dev-frontend |
Vite, port 5173 |
| Start Python only | make dev-python |
uvicorn --reload, port 8001 |
| Run all tests | make test |
Frontend + Backend + Python |
| Run backend tests | make test-backend |
vitest |
| Run frontend tests | make test-frontend |
vitest |
| Run Python tests | make test-python |
pytest |
| Run single backend test | cd backend && npx vitest run src/routes/batter.test.ts |
— |
| Run single frontend test | cd frontend && npx vitest run src/components/Panel.test.tsx |
— |
| Run single Python test | cd python && python -m pytest tests/test_statcast.py -v |
— |
| Lint all | make lint |
ESLint + Ruff |
| Typecheck all | make typecheck |
tsc (frontend + backend) |
| Find today's game | make find-game |
Prints gamePk |
| Test SSE stream | make test-sse |
curl to /api/health |
| Test API endpoints | make test-endpoints |
Backend script |
| Prefetch game data | make prefetch GAME_PK=12345 |
Python prefetch script |
| Deploy preview | make deploy-preview |
Frontend-only to Firebase |
| Full clean | make clean |
Docker down -v, rm node_modules |
| Check backend health | curl localhost:3001/api/health |
— |
| Check Python health | curl localhost:8001/health |
— |
| View Cloud Run logs | gcloud run logs read --service braves-booth-backend --region us-east4 |
— |
| List Cloud Run revisions | gcloud run revisions list --service braves-booth-backend --region us-east4 |
— |
| Resource | URL |
|---|---|
| Production | https://braves-booth.web.app |
| Backend API | https://braves-booth.web.app/api/health |
| GCP Console | https://console.cloud.google.com/run?project=braves-booth |
| Artifact Registry | https://console.cloud.google.com/artifacts?project=braves-booth |
| Firebase Console | https://console.firebase.google.com/project/braves-booth |
| GitHub Repo | (private repo) |
| CI/CD | GitHub Actions tab |
| Showcase Gist | https://gist.github.com/jeremylongshore/07e15be57e39b73c0b6f486bbfad1b8a |
| Endpoint | Method | Purpose |
|---|---|---|
/api/events |
GET (SSE) | Real-time game state stream |
/api/game/live |
GET | Current game state + poller status |
/api/game/today |
GET | Today's game discovery |
/api/game/start-polling |
POST | Manual poller start |
/api/game/stop-polling |
POST | Stop polling |
/api/batter/:id/stats |
GET | Batter stats (SQLite-backed) |
/api/pitcher/:id/stats |
GET | Pitcher stats (SQLite-backed) |
/api/matchup/:batterId/:pitcherId |
GET | Head-to-head (dual-source) |
/api/narrative |
POST | AI narrative (3-tier cache) |
/api/query |
POST | Free-form AI Q&A |
/api/ticker |
GET | Scores + standings |
/api/weather |
GET | Venue weather |
/api/umpire/:id |
GET | Umpire data |
/api/cohost |
GET | List broadcaster profiles |
/api/cohost/:id |
GET | Single broadcaster profile |
/api/health |
GET | Health check |
- Clone repo, run
make dev, verify all 3 services start - Run
make test— all 102 tests pass - Run
make lint+make typecheck— clean - Access https://braves-booth.web.app — verify production is live
-
gcloud auth application-default login— Vertex AI access - Review
000-docs/010-AT-ARCH-architect-review.md— 7 critical decisions - Review
000-docs/001-PP-PROD-*.md— PRD and feature map (F1-F9) - Understand SSE flow: GUMBO → Event Bus → SSE Route → Frontend
- Understand caching: node-cache TTLs → SQLite persistence → API/AI generation
- Trigger a manual prefetch:
make prefetch GAME_PK=<gamePk> - Review Cloud Run config in deploy.yml (timeout, session-affinity, cpu-throttling)
Goal: Fix known bugs, add critical resilience.
- Fix co-host panel — Create broadcaster profile JSON files, verify panel renders
- Fix data staleness — Add lineup-watch poller with game-phase state machine
- Add React error boundary — Wrap dashboard in error boundary with graceful fallback
- Add pybaseball timeout — Explicit 30s timeout on thread executor calls
- Add Cloud Monitoring dashboard — Cloud Run metrics (latency, errors, instances)
Measurable outcome: Dashboard shows correct co-host data, pre-game data refreshes when lineups are posted, no single-component crashes.
Goal: Production hardening and test coverage.
- Frontend test coverage to 60% — BatterCard, PitcherCard, useGameState, OnDeckPanel
- Add staging environment — Firebase preview channel + separate Cloud Run services
- Add Cloud Monitoring alerts — 5xx rate > 1%, latency P95 > 10s, zero instances
- Add Sentry error tracking — Frontend + backend
- Season bootstrap endpoint —
POST /api/admin/bootstrap-seasonfor 162-game pre-population - Python Dockerfile hardening — Non-root user, health check, multi-stage build
Measurable outcome: 60%+ frontend test coverage, staging environment for pre-production testing, alerting on production issues.
Goal: Full pre-population pipeline, operational maturity.
- Complete pre-game intelligence pipeline — Intel cards pre-generated for all lineup batters
- Daily morning check job — Cloud Scheduler → weather + status verification
- Infrastructure-as-Code — Terraform for Cloud Run + Firebase + IAM
- Bullpen panel implementation — Real-time reliever tracking from boxscore API
- Player news integration — Search API for recent 30-day player news
- Performance dashboard — Cache hit rates, AI generation latency, prefetch timing
Measurable outcome: Announcer opens dashboard before first pitch and everything is already populated. Zero LLM calls during broadcast (except pitcher changes/pinch hitters).
| Term | Definition |
|---|---|
| GUMBO | MLB's real-time game data feed (Game Update Management for Baseball Operations) |
| SSE | Server-Sent Events — one-way server-to-client streaming over HTTP |
| gamePk | MLB's unique game identifier (integer) |
| Intel Card | Pre-generated player intelligence package (stats + bio + narrative + matchup) |
| Circuit Breaker | Pattern that stops calling a failing service after N consecutive failures |
| WAL | Write-Ahead Logging — SQLite journal mode for concurrent reads during writes |
| Prefetch | Proactively fetching data before it's needed (on-deck/in-hole batters) |
| WIF | Workload Identity Federation — keyless auth between GitHub Actions and GCP |
CREATE TABLE cohost_profiles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
profile_json TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE narrative_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
game_date TEXT NOT NULL,
game_pk INTEGER NOT NULL,
batter_id INTEGER NOT NULL,
pitcher_id INTEGER NOT NULL,
input_payload TEXT NOT NULL,
output_json TEXT NOT NULL,
latency_ms INTEGER,
created_at INTEGER NOT NULL
);
CREATE TABLE player_stats (
player_id INTEGER NOT NULL,
season TEXT NOT NULL,
player_type TEXT NOT NULL,
name TEXT, team TEXT, position TEXT,
number INTEGER, bats TEXT, throws TEXT,
stats_json TEXT NOT NULL,
splits_json TEXT, last_season_json TEXT,
updated_at INTEGER NOT NULL,
PRIMARY KEY (player_id, season, player_type)
);
CREATE TABLE preferences (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);# Backend
PORT=3001 # Server port
HOST=0.0.0.0 # Bind address
NODE_ENV=development # dev/production
LOG_LEVEL=info # Pino log level
DB_PATH=./booth.db # SQLite file path
TEAM_CONFIG_PATH=../team-config.json # Team config file
MLB_STATS_API_BASE=https://statsapi.mlb.com/api/v1
MLB_GAME_FEED_BASE=https://statsapi.mlb.com/api/v1.1
PYBASEBALL_SERVICE_URL=http://localhost:8001 # Python service URL
CORS_ORIGIN= # Production: https://braves-booth.web.app
GCP_PROJECT_ID=braves-booth # Vertex AI project
GCP_LOCATION=us-east4 # Vertex AI region
# Frontend (build-time)
VITE_BACKEND_URL=http://localhost:3001 # Dev proxy target
VITE_SSE_URL= # Production: Cloud Run URL
# Python
# No env vars — config is code-level| Service | Test Files | Test Count | Framework | Status |
|---|---|---|---|---|
| Backend | 19 files | 76 tests | Vitest | All pass |
| Frontend | 4 files | 10 tests | Vitest + Testing Library | All pass |
| Python | 4 files | 16 tests | pytest | All pass |
| Total | 27 files | 102 tests | All pass |
Backend (package.json):
@fastify/cors: ^11.2.0 @google-cloud/vertexai: ^1.10.0
better-sqlite3: ^12.6.2 dotenv: ^17.3.1
fastify: ^5.2.0 node-cache: ^5.1.2
pino: ^9.6.0 pino-pretty: ^13.0.0
typescript: ~5.7.0 vitest: ^4.0.0
Frontend (package.json):
react: ^18.3.1 react-dom: ^18.3.1
tailwindcss: ^4.2.1 @tailwindcss/vite: ^4.2.1
vite: ^6.0.0 vitest: ^4.0.18
typescript: ~5.7.0
Python (requirements.txt):
fastapi>=0.115.0,<1.0 uvicorn[standard]>=0.34.0,<1.0
pybaseball>=2.2.7,<3.0 cachetools>=5.3.0,<6.0
httpx>=0.28.0,<1.0