FastAPI backend service for MLB stats and AI-powered analysis. Works alongside the mlb-remix frontend.
mlb-remix (Remix frontend) → mlb-api (this service) → MLB Stats API
→ Anthropic API
→ Neon Postgres (cache)
- Game Summaries: AI-generated recaps with headlines, key moments, and player highlights
- Player Stats: Basic stats plus advanced sabermetrics (wOBA, wRC+, OPS+, FIP, etc.)
- Scouting Reports: AI-generated player analysis
- Matchup Analysis: Batter vs pitcher predictions
- Caching: Two-tier caching (in-memory + Postgres) to reduce latency and API costs
- Python 3.11+ is required
- On macOS, check your version with
python3 --version
The system Python on macOS is often outdated. We recommend using pyenv to manage Python versions:
# Install pyenv via Homebrew
brew install pyenv
# Install Python 3.12 (compiles from source, avoids library conflicts)
pyenv install 3.12.3
# Use pyenv's Python to create your venv (see below)# Clone and enter directory
cd mlb-api
# Create virtual environment using pyenv's Python
~/.pyenv/versions/3.12.3/bin/python -m venv venv
# Activate the virtual environment
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# If you get SSL certificate errors (common with corporate VPNs), see
# "SSL certificate errors" under Troubleshooting below. Do not use
# --trusted-host; it disables verification instead of fixing trust.
# Set up environment variables
cp .env.example .env
# Edit .env with your actual values
# Run the development server
uvicorn app.main:app --reload
# Open http://localhost:8000/docs for interactive API documentationpython -m venv venv fails with ensurepip error
Your Python installation may have library conflicts. Use pyenv instead (see Prerequisites above).
SSL certificate errors during pip install
This means a TLS-inspecting proxy (corporate VPN) is re-signing traffic with a
root CA that your OS trusts but certifi — the bundle pip ships with — does not.
Do not use --trusted-host. That flag turns verification off and exposes the
install to package tampering. Instead, hand pip a bundle that includes your
organisation's root:
# Export the roots your Mac already trusts, and append them to certifi's bundle
security find-certificate -a -p /Library/Keychains/System.keychain > /tmp/a.pem
security find-certificate -a -p \
/System/Library/Keychains/SystemRootCertificates.keychain > /tmp/b.pem
cat "$(python -m certifi)" /tmp/a.pem /tmp/b.pem > /tmp/combined-ca.pem
pip install --cert /tmp/combined-ca.pem -r requirements.txtApplication traffic needs no such workaround: the MLB client verifies against
the OS trust store via truststore, so it already trusts the proxy root.
uvicorn: command not found
Make sure you've activated the virtual environment: source venv/bin/activate
| Method | Endpoint | Description |
|---|---|---|
| GET | /games/{game_id}/boxscore |
Raw boxscore data |
| GET | /games/{game_id}/summary |
AI-generated game summary |
| GET | /games/schedule |
Game schedule by date |
| Method | Endpoint | Description |
|---|---|---|
| GET | /players/{player_id} |
Player bio information |
| GET | /players/{player_id}/stats |
Season stats with sabermetrics |
| GET | /players/{player_id}/scouting-report |
AI-generated scouting report |
| Method | Endpoint | Description |
|---|---|---|
| GET | /matchups/{batter_id}/vs/{pitcher_id} |
Batter vs pitcher analysis |
| Method | Endpoint | Description |
|---|---|---|
| POST | /analysis/custom |
Custom AI analysis with context |
| Method | Endpoint | Description |
|---|---|---|
| GET | /search |
Fuzzy search across players and teams |
Query parameters: q (required) and limit (default 10).
Unlike every other endpoint, this one does not read from the MLB Stats API —
that API has no name search, so results come from player_index and
team_index in Postgres, matched with pg_trgm. Those tables are populated
by scripts/sync_player_index.py, which runs weekly from
.github/workflows/sync-player-index.yml. A freshly deployed database has
the tables but no rows until that workflow runs at least once.
// app/routes/game.$gameId.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node";
const API_URL = process.env.MLB_API_URL || "http://localhost:8000";
export async function loader({ params }: LoaderFunctionArgs) {
const response = await fetch(`${API_URL}/games/${params.gameId}/summary`);
if (!response.ok) {
throw new Response("Game not found", { status: 404 });
}
const summary = await response.json();
return json({ summary });
}This project uses Neon Postgres for caching. Tables are created automatically on startup.
cached_responses: Stores API responses and AI-generated content with TTLai_generations: Tracks AI generation metrics for analytics
The service uses a two-tier caching strategy:
Fast, per-instance caching for high-frequency MLB API data. Implemented in app/services/memory_cache.py.
| Cache | TTL | Max Size | Use Case |
|---|---|---|---|
| Game Feed | 10 sec | 500 | Live game data (scores, plays) |
| Game Content | 90 sec | 200 | Highlights and videos |
| Schedule | 2 min | 50 | Daily game schedules |
Trade-offs:
- Fast (nanoseconds vs milliseconds)
- Not persistent (lost on restart)
- Per-instance (not shared across workers)
Persistent caching for expensive-to-compute data. Implemented in app/services/cache_service.py.
Use for:
- AI-generated content (summaries, scouting reports)
- Data that should survive restarts
- Shared state across multiple workers
The service calculates advanced stats not provided by the MLB API:
- wOBA (Weighted On-Base Average): Values each hit type by run value
- wRC+ (Weighted Runs Created Plus): Run production normalized to 100
- OPS+: OPS adjusted for park/league (100 = average)
- ISO (Isolated Power): SLG - AVG, measures raw power
- BABIP: Batting average on balls in play (luck indicator)
- FIP (Fielding Independent Pitching): What ERA "should" be
- ERA+: ERA adjusted for park/league (higher = better)
- K/9, BB/9, HR/9: Rate stats per 9 innings
# Run tests
pytest
# Run with auto-reload
uvicorn app.main:app --reload
# Check types (optional)
pip install mypy
mypy app/| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Neon Postgres connection string |
ANTHROPIC_API_KEY |
Yes | Anthropic API key for Claude |
MLB_STATS_API_BASE_URL |
No | MLB API base URL (default: https://statsapi.mlb.com/api/v1) |
DEBUG |
No | Enable debug mode (default: false) |
CACHE_TTL_SECONDS |
No | Default cache TTL (default: 300) |
Note: For
DATABASE_URL, just paste the connection string from Neon as-is. The app automatically converts it for asyncpg compatibility (handlessslmode,channel_binding, and other parameters).
mlb-api/
├── app/
│ ├── main.py # FastAPI app entry point
│ ├── config.py # Environment configuration
│ ├── database.py # Postgres connection
│ ├── routers/
│ │ ├── games.py # Game endpoints
│ │ ├── players.py # Player endpoints
│ │ ├── matchups.py # Matchup endpoints
│ │ └── analysis.py # Analysis endpoints
│ ├── services/
│ │ ├── mlb_client.py # MLB Stats API wrapper
│ │ ├── ai_service.py # Anthropic integration
│ │ ├── sabermetrics.py # Stat calculations
│ │ ├── cache_service.py # Postgres caching
│ │ └── memory_cache.py # In-memory TTL caching
│ └── models/
│ ├── game.py # Game Pydantic models
│ ├── player.py # Player Pydantic models
│ ├── analysis.py # Analysis models
│ └── cache.py # SQLAlchemy ORM models
├── requirements.txt
├── .env.example
└── README.md