A Flask-based API for exploring MLB team analytics, including power rankings, standings, odds, player stats, volatility, consistency, clustering, and advanced statistical models (ACF stability, Granger causality, HMM states).
Currently online at - https://mlb.riddhimandal.com
-
Data ingestion & caching
- Loads pre-saved CSV/JSON files from
data/when available - Falls back to dynamic scraping & computation functions (
sunday_power,get_batting_stats, etc.) - Keeps results in memory for speed
- Loads pre-saved CSV/JSON files from
-
Endpoints for core data
/teams— team metadata (names + codes)/power— weekly power rankings/standings— MLB standings/odds— playoff odds/batting,/pitching,/fielding— player/team performance stats
-
Analytics endpoints
/ranks— combined view of power rankings vs MLB standings/kdes— KDE + histogram of ranking changes/volatility— ranking volatility over time/stability&/consistency— ACF-based measures of rank dynamics/granger— Granger causality (power → MLB standings)/similarity— trajectory similarity between two teams/clusters— k-means clustering of season stats/hmm— hidden Markov model for team performance states
Clone the repository and install dependencies:
git clone <your-repo-url>
cd mlb-api
pip install -r requirements.txtYou’ll also need Google Chrome + chromedriver installed if scraping is used.
python app.pyThe API will start at http://localhost:5000.
Make sure you have a Procfile:
web: gunicorn app:app
Deploy:
git push heroku main- Get power rankings (default 2025):
curl http://localhost:5000/power- Get team metadata:
curl http://localhost:5000/teams?year=2025- Compare rankings for specific teams:
curl "http://localhost:5000/ranks?teams=TOR&teams=NYY&mode=both"- Run Granger causality test:
curl "http://localhost:5000/granger?team=TOR&max_lag=3"- Uses a mix of saved CSVs/JSON in the
data/directory and scraping/stats functions defined in the repo. - Cached in memory per session for efficiency.
- Some endpoints depend on others being loaded first (e.g.,
/ranksrequires/powerand/standings). - If running on Heroku, set
WEB_CONCURRENCY=1to avoid multiple Chrome workers. - Data freshness depends on CSVs and scraping functions.
Base URL (local):
http://localhost:5000/
| Endpoint | Method | Purpose | Notes |
|---|---|---|---|
/ |
GET | Health check | Returns { "ok": true }. |
/teams |
GET | Team metadata (names & codes) | Reads from data/teams_{year}.json and data/tms_{year}.json if present. |
/colors |
GET | Team Colors (RGB values) | Reads from data/team_colors.json if present, else uses built-in defaults. |
/logo |
GET | Team logo (base64-encoded) | Fetches from ESPN and returns as base64 string. |
/power |
GET | Weekly power rankings time series | Caches in memory; CSV fallback data/power_rankings_{year}.csv else sunday_power. |
/standings |
GET | MLB standings time series | Caches in memory; CSV fallback data/standings_{year}.csv else sunday_standings. |
/odds |
GET | Playoff odds time series | Caches in memory; CSV fallback data/odds_{year}.csv else sunday_odds. |
/batting |
GET | Batting stats snapshot/series | CSV fallback data/batting_stats_{year}.csv else get_batting_stats. |
/pitching |
GET | Pitching stats snapshot/series | CSV fallback data/pitching_stats_{year}.csv else get_pitching_stats. |
/fielding |
GET | Fielding stats snapshot/series | CSV fallback data/fielding_stats_{year}.csv else get_fielding_stats. |
/ranks |
GET | Power vs MLB ranks for selected teams | Requires /power & /standings to be loaded first. |
/kdes |
GET | KDE + histogram of Δrank | Requires /power & /standings. |
/volatility |
GET | Rank volatility time series | Requires /power & /standings. |
/stability |
GET | ACF-based stability (Δ Fisher-z) | Requires /power & /standings. |
/consistency |
GET | ACF values (consistency) | Requires /power & /standings. |
/granger |
GET | Granger causality (power → MLB) | Requires /power & /standings. |
/similarity |
GET | Trajectory similarity stats | Requires /power & /standings. |
/clusters |
GET | Season clustering (k-means) | Needs standings, odds, batting, pitching, fielding loaded. |
/hmm |
GET | Hidden Markov Model states | Builds power features, fits HMM for a team. |
-
Method: GET
-
Returns:
{ "ok": true } -
Example:
curl http://localhost:5000/
-
Method: GET
-
Query:
year(int, default2025) -
Notes: If
data/teams_{year}.jsonanddata/tms_{year}.jsonexist, loads those; otherwise returns whatever is cached in memory. -
Returns:
{ "teams": {...}, "tms": {...} } -
Example:
curl "http://localhost:5000/teams?year=2025"
-
Method: GET
-
Query Parameters:
team(string, required) — team code (e.g.,TOR,NYY) -
Returns:
{ "team": <string>, "color": { "association": <string>, "color": [<int>, <int>, <int>] } } -
Notes: Reads from
data/team_colors.jsonif present; otherwise uses built-in defaults. -
Example:
curl "http://localhost:5000/color?team=TOR"
-
Method: GET
-
Query Parameters:
team(string, required) — team code (e.g.,TOR,NYY) -
Returns:
{ "team": <string>, "logo": <string|null>, "url": <string> } -
Notes: Fetches team logo from ESPN and returns as base64-encoded string.
-
Example:
curl "http://localhost:5000/logo?team=TOR"
-
Method: GET
-
Query:
year(int, default2025) -
Logic:
- Serve from in-memory cache if same
YEAR - Else try
data/power_rankings_{year}.csv - Else compute via
sunday_power(year)(also setsTEAMS/TMS)
- Serve from in-memory cache if same
-
Returns:
{ "power": [...], "teams": {...}, "tms": {...} } -
Example:
curl "http://localhost:5000/power?year=2025"
-
Method: GET
-
Query:
year(int, default2025) -
Logic: cache →
data/standings_{year}.csv→sunday_standings(year) -
Returns:
{ "standings": [...] } -
Example:
curl "http://localhost:5000/standings?year=2025"
-
Method: GET
-
Query:
year(int, default2025) -
Logic: cache →
data/odds_{year}.csv→sunday_odds(year) -
Returns:
{ "odds": [...] } -
Example:
curl "http://localhost:5000/odds?year=2025"
-
Method: GET
-
Query:
year(int, default2025) -
Logic: cache →
data/batting_stats_{year}.csv→get_batting_stats(year) -
Returns:
{ "batting": [...] } -
Example:
curl "http://localhost:5000/batting?year=2025"
-
Method: GET
-
Query:
year(int, default2025) -
Logic: cache →
data/pitching_stats_{year}.csv→get_pitching_stats(year) -
Returns:
{ "pitching": [...] } -
Example:
curl "http://localhost:5000/pitching?year=2025"
-
Method: GET
-
Query:
year(int, default2025) -
Logic: cache →
data/fielding_stats_{year}.csv→get_fielding_stats(year) -
Returns:
{ "fielding": [...] } -
Example:
curl "http://localhost:5000/fielding?year=2025"
-
Method: GET
-
Requires:
/powerand/standingsloaded first (in memory) -
Query:
teams(repeatable) — team codes (e.g.,teams=TOR&teams=NYY)mode— one ofpower,mlb,diff,both(defaultboth)
-
Returns:
{ "ranks": [...] }(tidy table for plotting/compare) -
Example:
curl "http://localhost:5000/ranks?teams=TOR&teams=NYY&mode=both"
-
Method: GET
-
Requires:
/power&/standings -
Query:
teams(repeatable) — team codes (optional; if omitted, logic in your builder decides scope)source—powerormlb(defaultpower)
-
Returns:
{ "kde_data": [...], "hist_data": [...], "peaks": {...}, "bandwidth": <float> } -
Example:
curl "http://localhost:5000/kdes?teams=TOR&source=power"
-
Method: GET
-
Requires:
/power&/standings -
Query:
teams(repeatable) — team codessource—powerormlb(defaultpower)
-
Returns:
{ "volatility_data": [...] } -
Example:
curl "http://localhost:5000/volatility?teams=TOR&source=mlb"
-
Method: GET
-
Requires:
/power&/standings -
Query:
team— single team code (e.g.,TOR)source—powerormlb(defaultpower)max_lag— int, default4
-
Returns:
{ "stability_data": [...] } -
Example:
curl "http://localhost:5000/stability?team=TOR&source=power&max_lag=4"
-
Method: GET
-
Requires:
/power&/standings -
Query:
team— single team codesource—powerormlb(defaultpower)max_lag— int, default4
-
Returns:
{ "consistency_data": [...] }(raw ACF values) -
Example:
curl "http://localhost:5000/consistency?team=TOR&source=mlb&max_lag=3"
-
Method: GET
-
Requires:
/power&/standings -
Query:
team— single team codemax_lag— int, default4
-
Returns:
{ "granger_data": [...], "stats": {...} } -
Example:
curl "http://localhost:5000/granger?team=TOR&max_lag=4"
-
Method: GET
-
Requires:
/power&/standings -
Query:
team_a— team code Ateam_b— team code Bsource—powerormlb(defaultpower)
-
Returns:
{ "similarity_stats": {...} } -
Example:
curl "http://localhost:5000/similarity?team_a=TOR&team_b=NYY&source=power"
-
Method: GET
-
Requires:
/standings,/odds,/batting,/pitching,/fielding(all loaded) -
Query:
k— number of clusters (int, default6)
-
Returns:
{ "clusters": [...] }whereteamsis an array per cluster -
Example:
curl "http://localhost:5000/clusters?k=6"
-
Method: GET
-
Requires: (implicitly uses current
POWER,TEAMS,TMS; builds features internally) -
Query:
team— team code (required)
-
Returns:
{ "states": [...], "stats": { "P": [[...]], "P_labels": ["Good","Mediocre","Bad"], "pi": [...], "means": [[...]], "mean_cols": ["level_dev","chg_z","mom3_z"], "init": "quant", "n_used": 0 } } -
Example:
curl "http://localhost:5000/hmm?team=TOR"
- Load the foundational datasets first (e.g., call
/powerand/standings) so downstream analytics endpoints have their inputs warmed in memory. Many endpoints return400if prerequisites aren’t loaded. - CSV/JSON files in
data/are preferred when present; otherwise the API triggers scraping/compute functions defined in your code. - Everything is GET for now; results are JSON and generally return tidy records suitable for plotting/dataframes.