Real-time AI-powered broadcast intelligence for Atlanta Braves radio. Surfaces AI-generated narratives, player analytics, matchup history, and situational intelligence to announcers and producers during live games — with sub-6-second data latency from MLB's live feed to the broadcast booth.
Three-service monorepo deployed on Google Cloud Platform, designed around a core constraint: zero network dependencies during live broadcast for all cached data.
┌──────────────────────────┐
│ Firebase Hosting │
│ (CDN + SPA) │
└────────┬───┬─────────────┘
│ ▲
API │ │ Static Assets
Proxy │ │
▼ │
┌──────────────┐ ┌─────────────────────────┐ ┌───────────────────┐
│ MLB Stats API│◄────►│ Backend Service │◄────►│ Python Service │
│ (GUMBO Feed) │ │ Fastify + TypeScript │ │ FastAPI + Statcast│
└──────────────┘ └──────┬────────┬──────────┘ └───────────────────┘
│ │
┌─────────┘ └─────────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ SQLite │ │ Vertex AI │
│ (WAL mode) │ │ Gemini 2.5 Pro │
└──────────────┘ └──────────────────┘
| Service | Stack | Deploy Target |
|---|---|---|
| Backend | Fastify 5, TypeScript, SQLite, Pino | Cloud Run |
| Frontend | React 18, Vite 6, Tailwind CSS v4 | Firebase Hosting |
| Python | FastAPI, pybaseball, Statcast | Cloud Run |
- Live Game Tracking — 5-second polling of MLB's GUMBO feed with Server-Sent Events pushing state changes to connected clients in real time
- AI Broadcast Narratives — Gemini 2.5 Pro generates context-aware talking points for each at-bat, combining player stats, matchup history, game situation, and broadcaster personality profiles
- Predictive Prefetching — System anticipates data needs at boot, on batter changes (on-deck + in-hole), and on pitcher substitutions, pre-warming caches before announcers need the data
- Statcast Analytics — Deep statistical pipeline via pybaseball: exit velocity, barrel rates, pitch spin rates, platoon splits, and historical batter-vs-pitcher matchups
- Multi-Source Matchup Intelligence — Parallel queries to MLB Stats API and Statcast with graceful partial-failure handling via
Promise.allSettled() - Broadcaster Co-Host Profiles — AI narratives adapt tone and content based on which co-host is on air
- Live Ticker — Real-time MLB scores and NL standings alongside the broadcast dashboard
- Venue-Aware Weather — Dynamic weather data using stadium coordinates, with intelligent fallback for API outages
- Free-Form AI Q&A — Natural language queries about the current game state answered from live context
The system consumes MLB's GUMBO v1.1 live feed, a comprehensive JSON stream updated pitch-by-pitch during games.
MLB GUMBO Feed (5s poll)
│
▼
GUMBO Poller ─── detects state transitions ──┐
│ │
├─► game-state (every poll cycle) │
├─► batter-change (plate appearance) │
├─► pitcher-change (substitution) │
└─► score-change (run scored) │
▼
Event Bus (pub/sub)
│
▼
SSE Stream (/api/events)
│
▼
Browser EventSource
│
▼
React Context → UI Panels
End-to-end latency: ~5.5 seconds from MLB feed update to browser render.
Each at-bat, the system assembles a rich context window for Gemini 2.5 Pro:
- Batter profile — current + prior season stats, splits, tendencies
- Pitcher profile — current + prior season stats, pitch arsenal
- Matchup history — historical at-bats between the two players (MLB Stats + Statcast)
- Game situation — inning, count, runners, score differential
- Broadcaster profile — personality, preferred angles, storytelling style
The AI generates a structured response: a lead talking point and supporting angles, giving announcers a quick-glance narrative they can riff on live.
Announcers can ask free-form questions about the game ("How has Acuna done with runners in scoring position this series?") and get factual answers synthesized from the live context window — no hallucination, sourced only from available data.
The system uses a tiered caching strategy designed to survive the worst-case scenario: an API outage during a live broadcast.
| Tier | Technology | Scope | Purpose |
|---|---|---|---|
| L1 | In-memory TTL cache | Per-instance, volatile | Hot path — sub-millisecond reads for repeat queries within a game |
| L2 | SQLite (WAL mode) | Persistent across restarts | Warm path — survives process crashes, ephemeral by design on Cloud Run |
| L3 | External APIs | Source of truth | Cold path — MLB Stats, Statcast, Vertex AI |
SQLite runs in WAL (Write-Ahead Logging) mode for concurrent read/write performance. The database is intentionally ephemeral on Cloud Run — an architectural decision that trades persistence for zero network database dependencies during live broadcasts.
BOOT TIME
└─► Discover today's game → start poller → prefetch full rosters
(both teams: stats, player info, opposing starter narratives)
Concurrency-limited parallel fetches across all data sources
BATTER CHANGE
└─► Prefetch on-deck + in-hole batter stats (current + prior season)
Pre-generate narrative for on-deck batter vs current pitcher
PITCHER CHANGE
└─► Re-generate narratives for current batter, on-deck, and in-hole
vs the new pitcher (3 parallel AI generations)
SSE CLIENT CONNECT
└─► Auto-recovery: if poller isn't running, discover game + start + prefetch
Prefetch operations are idempotent with in-flight guards preventing duplicate concurrent runs.
| Category | Endpoints | Purpose |
|---|---|---|
| Game | 4 | Live state, today's game discovery, poller control |
| Player Stats | 2 | Batter and pitcher profiles with dual-season data |
| Analytics | 5 | Matchups, Statcast, splits, platoon data |
| AI | 2 | Narrative generation, free-form Q&A |
| Broadcast | 3 | Co-host profiles, umpire tendencies |
| Context | 3 | Weather, ticker (scores + standings), health |
| Real-time | 1 | SSE event stream |
| Batch | 2 | Roster prefetch orchestration |
| Event | Trigger | Purpose |
|---|---|---|
game-state |
Every 5s poll | Full game state sync |
batter-change |
New plate appearance | Current, on-deck, and in-hole batters |
pitcher-change |
Substitution | New pitcher entering the game |
score-change |
Run scored | Updated score with team names |
heartbeat |
Every 15s | Connection keepalive |
- Circuit Breaker — Python's pybaseball integration uses a full state-machine circuit breaker (CLOSED → OPEN → HALF_OPEN) that isolates external library failures from the broadcast
- Graceful Degradation — Matchup queries fan out to MLB Stats API and Statcast simultaneously; partial failures return available data rather than erroring
- Timeout Boundaries — Every external call wrapped with abort signals; AI generation has a hard 5-second ceiling
- Weather Fallback — API failures gracefully degrade to venue-default conditions
- SSE Auto-Recovery — Frontend automatically reconnects on stream interruption; backend auto-discovers games on client connect if poller isn't running
- Prefetch Idempotency — In-flight guards prevent duplicate concurrent prefetch runs on rapid event bursts
- SQLite over cloud databases — Eliminates network latency and single points of failure for cached data during live broadcast. The tradeoff (cold cache on redeploy) is mitigated by aggressive boot-time prefetching
- SSE over WebSockets — Simpler protocol, native browser support, automatic reconnection. The data flow is unidirectional (server → client), making SSE the right fit
- Monorepo with service isolation — Three services share a repo for atomic deploys but maintain strict boundaries. Python service exists solely because pybaseball/Statcast require a Python runtime
- Ephemeral by design — No persistent cloud database means no database ops, no connection pooling, no migration headaches. Each deploy starts clean and pre-warms within seconds
| Service | Test Files | Tests | Status |
|---|---|---|---|
| Backend | 19 | 85 | All passing |
| Frontend | 4 | 10 | All passing |
| Python | 4 | 16 | All passing |
| Total | 27 | 111 | 109 passing, 2 skipped (integration) |
- CI/CD: Automated deploy pipeline on push to
main— builds and deploys all three services in dependency order with health check verification - Workload Identity Federation: Keyless authentication from GitHub Actions to GCP via OIDC — no service account keys stored anywhere
| Metric | Value |
|---|---|
| Services | 3 (TypeScript backend, React frontend, Python analytics) |
| Application code | ~6,600 lines (TypeScript + TSX + Python) |
| Source files | 105+ |
| API endpoints | 22 |
| SSE event types | 5 |
| External data sources | 4 (MLB Stats, GUMBO, Statcast/pybaseball, Open-Meteo) |
| AI model | Gemini 2.5 Pro (Vertex AI) |
| Cache tiers | 3 (memory → SQLite → API) |
| Test suite | 111 tests across 27 files |
| Poll frequency | 5 seconds (live game state) |
| Target latency | <6s end-to-end (feed → browser) |
A real-time broadcast intelligence system built for the speed and reliability demands of live sports radio.