Files

138 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Fetch 2026 March Madness bracket data: matchups from ESPN + rankings from Massey."""
import os
import sys
import requests
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(SCRIPT_DIR, "data")
ESPN_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard"
# First round dates for 2026 tournament
FIRST_ROUND_DATES = ["20260319", "20260320"]
HEADERS = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
}
BRACKET_ORDER = [1, 8, 5, 4, 6, 3, 7, 2] # seed order within a region for bracket pairing
def fetch_matchups():
"""Fetch first-round matchups from ESPN scoreboard API, grouped by region."""
# Collect games with region info
games = [] # list of (region, seed_hi, team_hi, seed_lo, team_lo)
for date in FIRST_ROUND_DATES:
print(f"Fetching ESPN scoreboard for {date}...")
try:
resp = requests.get(
ESPN_SCOREBOARD_URL,
params={"dates": date, "groups": 50, "limit": 365},
headers=HEADERS,
timeout=15,
)
resp.raise_for_status()
except requests.RequestException as e:
print(f" ERROR fetching {date}: {e}", file=sys.stderr)
continue
data = resp.json()
events = data.get("events", [])
print(f" Found {len(events)} events")
for event in events:
comps = event.get("competitions", [])
if not comps:
continue
comp = comps[0]
competitors = comp.get("competitors", [])
if len(competitors) != 2:
continue
# Extract region from notes
region = "Unknown"
for note in comp.get("notes", []):
headline = note.get("headline", "")
for r in ["East", "West", "South", "Midwest"]:
if r in headline:
region = r
break
teams = []
for c in competitors:
team_info = c.get("team", {})
name = team_info.get("shortDisplayName") or team_info.get("displayName", "???")
seed = c.get("curatedRank", {}).get("current", 99)
teams.append((seed, name))
teams.sort(key=lambda t: t[0]) # higher seed (lower number) first
games.append((region, teams[0][0], teams[0][1], teams[1][0], teams[1][1]))
# Group by region, then sort within each region by bracket order
regions = {}
for region, s1, t1, s2, t2 in games:
regions.setdefault(region, []).append((s1, t1, s2, t2))
for region in regions:
regions[region].sort(key=lambda g: BRACKET_ORDER.index(g[0]) if g[0] in BRACKET_ORDER else 99)
# Write region-grouped file
outpath = os.path.join(DATA_DIR, "matchups.csv")
region_order = ["East", "South", "West", "Midwest"]
with open(outpath, "w") as f:
for i, region in enumerate(region_order):
if region not in regions:
continue
if i > 0:
f.write("\n")
f.write(f"{region}\n")
for s1, t1, s2, t2 in regions[region]:
f.write(f"{s1},{t1},{s2},{t2}\n")
total = sum(len(g) for g in regions.values())
print(f"\nWrote {total} matchups in {len(regions)} regions to {outpath}")
# Readable summary
for region in region_order:
if region not in regions:
continue
print(f"\n === {region} ===")
for s1, t1, s2, t2 in regions[region]:
print(f" ({s1:>2}) {t1:<22} vs ({s2:>2}) {t2}")
return regions
def check_rankings():
"""Check that Massey Composite rankings CSV exists in data dir."""
outpath = os.path.join(DATA_DIR, "massey.csv")
if not os.path.exists(outpath):
print("WARNING: massey.csv not found in data/", file=sys.stderr)
print(" Download manually from https://masseyratings.com/cb/compare.htm", file=sys.stderr)
print(" and save as data/massey.csv", file=sys.stderr)
return False
with open(outpath) as f:
header = f.readline().strip()
num_teams = sum(1 for line in f if line.strip())
num_systems = header.count(",") - 5 # subtract Team, Conf, W-L, Delta, CMP, Sort
print(f"Rankings: {num_teams} teams, {num_systems} ranking systems in {outpath}")
return True
def main():
os.makedirs(DATA_DIR, exist_ok=True)
print("=== 2026 March Madness Data Fetcher ===\n")
check_rankings()
print()
fetch_matchups()
if __name__ == "__main__":
main()