Files
nike/scripts/discover_api.py
Robert Helewka ee8436d5b8 feat: implement MCP server and dashboard for football data platform
Add complete Nike football data platform with:
- FastMCP server exposing football data tools over HTTP
- RapidAPI client for free-api-live-football-data integration
- Bootstrap web dashboard with live match/standings views
- REST API endpoints for dashboard consumption
- Docker support with multi-stage build
- Comprehensive README with architecture docs
- Minimal .gitignore replacing verbose Python template
2026-03-21 18:19:42 +00:00

328 lines
13 KiB
Python

#!/usr/bin/env python3
"""
API Discovery Script — walk the RapidAPI top-down, save raw responses.
Workflow:
1. Search for leagues (MLS, Premier League) → get league IDs
2. Search for teams (Toronto FC, Arsenal) → get team IDs
3. Get league matches → find recent past + upcoming event IDs
4. Get match detail, score, stats, location for a finished match
5. Get match highlights (goals, cards, events)
6. Get lineups for a finished match
7. Get squad roster + sample player detail
8. Get standings
Saves every response to docs/api_samples/{step}_{endpoint}.json
Cost: ~18 API calls
"""
import sys
import json
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from nike import rapidapi as rapi
SAMPLES_DIR = Path(__file__).resolve().parent.parent / "docs" / "api_samples"
SAMPLES_DIR.mkdir(parents=True, exist_ok=True)
def save(name: str, data) -> None:
"""Save raw API response to a JSON file."""
path = SAMPLES_DIR / f"{name}.json"
with open(path, "w") as f:
json.dump(data, f, indent=2, default=str)
size_kb = path.stat().st_size / 1024
print(f" ✓ Saved {path.name} ({size_kb:.1f} KB)")
def extract_id(data, label="item") -> int | None:
"""Try to pull an ID from the first search result."""
resp = data.get("response") if isinstance(data, dict) else None
# Handle nested response structures
items = []
if isinstance(resp, dict):
for key in ("suggestions", "teams", "leagues", "players", "matches"):
if key in resp and isinstance(resp[key], list) and resp[key]:
items = resp[key]
break
if not items:
items = [resp]
elif isinstance(resp, list):
items = resp
else:
# Try alternate envelope keys
for key in ("data", "result"):
val = data.get(key)
if isinstance(val, list) and val:
items = val
break
for item in items:
if isinstance(item, dict):
eid = item.get("id") or item.get("primaryId")
if eid:
name = item.get("name") or item.get("title") or "?"
print(f"{label}: {name} (ID: {eid})")
return int(eid)
print(f" ⚠ Could not extract ID from {label} response")
# Dump top-level keys for debugging
if isinstance(data, dict):
print(f" Top-level keys: {list(data.keys())}")
if isinstance(resp, dict):
print(f" response keys: {list(resp.keys())}")
return None
def find_event_ids(data, limit=2):
"""Extract event IDs from a matches/fixtures response."""
resp = data.get("response") if isinstance(data, dict) else None
events = {"past": [], "upcoming": []}
items = []
if isinstance(resp, dict):
for key in ("matches", "events", "fixtures", "allMatches"):
if key in resp and isinstance(resp[key], list):
items = resp[key]
break
if not items:
# Flatten if resp itself contains sub-lists
for key, val in resp.items():
if isinstance(val, list) and val and isinstance(val[0], dict):
items = val
break
elif isinstance(resp, list):
items = resp
else:
for key in ("data", "result"):
val = data.get(key)
if isinstance(val, list):
items = val
break
for item in items:
if not isinstance(item, dict):
continue
eid = item.get("id") or item.get("eventId") or item.get("primaryId")
if not eid:
continue
# Detect finished vs upcoming
status = item.get("status", {})
if isinstance(status, dict):
finished = (status.get("finished", False) or
status.get("short") == "FT" or
status.get("type") == "finished")
elif isinstance(status, str):
finished = status.lower() in ("ft", "aet", "pen", "finished")
else:
finished = False
bucket = "past" if finished else "upcoming"
events[bucket].append(int(eid))
return {k: v[:limit] for k, v in events.items()}
def main():
print("=" * 60)
print(" Nike API Discovery — Mapping Response Structures")
print("=" * 60)
# ══════════════════════════════════════════════════════
# STEP 1: League discovery
# ══════════════════════════════════════════════════════
print("\n[1/8] Searching for leagues...")
mls = rapi.search_leagues("MLS")
save("01_search_leagues_mls", mls)
mls_id = extract_id(mls, "MLS")
epl = rapi.search_leagues("Premier League")
save("01_search_leagues_epl", epl)
epl_id = extract_id(epl, "Premier League")
popular = rapi.get_popular_leagues()
save("01_popular_leagues", popular)
# ══════════════════════════════════════════════════════
# STEP 2: Team discovery
# ══════════════════════════════════════════════════════
print("\n[2/8] Searching for teams...")
tfc = rapi.search_teams("Toronto FC")
save("02_search_teams_tfc", tfc)
tfc_id = extract_id(tfc, "Toronto FC")
ars = rapi.search_teams("Arsenal")
save("02_search_teams_arsenal", ars)
ars_id = extract_id(ars, "Arsenal")
# ══════════════════════════════════════════════════════
# STEP 3: League matches (fixtures)
# ══════════════════════════════════════════════════════
print("\n[3/8] Fetching league matches...")
mls_events = {"past": [], "upcoming": []}
if mls_id:
mls_matches = rapi.get_league_matches(mls_id)
save("03_league_matches_mls", mls_matches)
mls_events = find_event_ids(mls_matches)
print(f" → Events found: {len(mls_events['past'])} past, "
f"{len(mls_events['upcoming'])} upcoming")
if not mls_events["past"] and not mls_events["upcoming"]:
# Dump structure hints to help debug
resp = mls_matches.get("response")
if isinstance(resp, dict):
print(f" response keys: {list(resp.keys())}")
for k, v in resp.items():
if isinstance(v, list) and v:
print(f" response.{k}[0] keys: "
f"{list(v[0].keys()) if isinstance(v[0], dict) else type(v[0])}")
elif isinstance(resp, list) and resp:
print(f" response[0] keys: "
f"{list(resp[0].keys()) if isinstance(resp[0], dict) else type(resp[0])}")
else:
print(" ⚠ No MLS ID, skipping")
# ══════════════════════════════════════════════════════
# STEP 4: Match detail (finished match)
# ══════════════════════════════════════════════════════
print("\n[4/8] Fetching match detail...")
event_id = None
if mls_events["past"]:
event_id = mls_events["past"][0]
elif mls_events["upcoming"]:
event_id = mls_events["upcoming"][0]
if event_id:
print(f" Using event ID: {event_id}")
detail = rapi.get_match_detail(event_id)
save("04_match_detail", detail)
score = rapi.get_match_score(event_id)
save("04_match_score", score)
status = rapi.get_match_status(event_id)
save("04_match_status", status)
location = rapi.get_match_location(event_id)
save("04_match_location", location)
else:
print(" ⚠ No event ID found — skipping match detail")
# ══════════════════════════════════════════════════════
# STEP 5: Match stats + highlights (goals, cards, events)
# ══════════════════════════════════════════════════════
print("\n[5/8] Fetching match stats & highlights...")
if event_id:
stats = rapi.get_match_stats(event_id)
save("05_match_stats", stats)
highlights = rapi.get_match_highlights(event_id)
save("05_match_highlights", highlights)
referee = rapi.get_match_referee(event_id)
save("05_match_referee", referee)
else:
print(" ⚠ Skipping (no event ID)")
# ══════════════════════════════════════════════════════
# STEP 6: Lineups
# ══════════════════════════════════════════════════════
print("\n[6/8] Fetching lineups...")
if event_id:
home_lineup = rapi.get_home_lineup(event_id)
save("06_lineup_home", home_lineup)
away_lineup = rapi.get_away_lineup(event_id)
save("06_lineup_away", away_lineup)
else:
print(" ⚠ Skipping (no event ID)")
# ══════════════════════════════════════════════════════
# STEP 7: Squad & player detail
# ══════════════════════════════════════════════════════
print("\n[7/8] Fetching squad & player detail...")
player_id = None
if tfc_id:
squad = rapi.get_squad(tfc_id)
save("07_squad_tfc", squad)
# Extract a player ID from the squad response
resp = squad.get("response") if isinstance(squad, dict) else None
if isinstance(resp, list) and resp:
for p in resp:
if isinstance(p, dict):
pid = p.get("id") or p.get("primaryId")
if pid:
player_id = int(pid)
print(f" → Sample player: {p.get('name', '?')} (ID: {player_id})")
break
elif isinstance(resp, dict):
# May be nested under squad/players/roster
for key in ("squad", "players", "roster", "members"):
if key in resp and isinstance(resp[key], list):
for p in resp[key]:
if isinstance(p, dict):
pid = p.get("id") or p.get("primaryId")
if pid:
player_id = int(pid)
print(f" → Sample player: {p.get('name', '?')} "
f"(ID: {player_id})")
break
break
if not player_id:
print(f" response keys: {list(resp.keys())}")
if player_id:
player = rapi.get_player_detail(player_id)
save("07_player_detail", player)
else:
print(" ⚠ Could not find a player ID in squad response")
else:
print(" ⚠ No TFC ID, skipping")
# ══════════════════════════════════════════════════════
# STEP 8: Standings
# ══════════════════════════════════════════════════════
print("\n[8/8] Fetching standings...")
if mls_id:
standings = rapi.get_standings(mls_id)
save("08_standings_mls", standings)
# ══════════════════════════════════════════════════════
# Summary
# ══════════════════════════════════════════════════════
print("\n" + "=" * 60)
files = sorted(SAMPLES_DIR.glob("*.json"))
print(f" Saved {len(files)} response samples to docs/api_samples/")
for f in files:
size_kb = f.stat().st_size / 1024
print(f" {f.name:.<50} {size_kb:.1f} KB")
print("=" * 60)
print("\n Discovered IDs:")
print(f" MLS league ID : {mls_id}")
print(f" EPL league ID : {epl_id}")
print(f" Toronto FC team ID : {tfc_id}")
print(f" Arsenal team ID : {ars_id}")
if event_id:
print(f" Sample event ID : {event_id}")
if player_id:
print(f" Sample player ID : {player_id}")
print()
if __name__ == "__main__":
main()