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
This commit is contained in:
2026-03-21 18:19:42 +00:00
parent b8689d530a
commit ee8436d5b8
81 changed files with 50251 additions and 176 deletions

View File

@@ -0,0 +1,383 @@
#!/usr/bin/env python3
"""
TheSportsDB API Discovery — walk the API top-down, save raw responses.
Uses V1 free key ('3') for basic structure validation.
With a premium key, also tests V2 endpoints.
Workflow:
1. Search for leagues (MLS, Premier League) → IDs
2. Search for teams (Toronto FC, Arsenal) → IDs
3. Get team schedules (next + previous matches) → event IDs
4. Get match detail, stats, lineup, timeline for a finished match
5. Get squad roster + sample player detail
6. Get standings (V1)
7. Get events by date (V1)
8. Get livescores
Saves every response to docs/api_samples/sportsdb/{step}_{endpoint}.json
"""
import sys
import json
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from nike import config
from nike import sportsdb as api
SAMPLES_DIR = Path(__file__).resolve().parent.parent / "docs" / "api_samples" / "sportsdb"
SAMPLES_DIR.mkdir(parents=True, exist_ok=True)
# Track API calls
_call_count = 0
def save(name: str, data) -> None:
"""Save raw API response to a JSON file."""
global _call_count
_call_count += 1
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 show_keys(data, label="response"):
"""Print top-level structure of a response."""
if isinstance(data, dict):
for k, v in data.items():
if isinstance(v, list):
print(f" {label}.{k}: list ({len(v)} items)")
if v and isinstance(v[0], dict):
print(f" [0] keys: {list(v[0].keys())}")
elif isinstance(v, dict):
print(f" {label}.{k}: dict ({len(v)} keys)")
elif v is None:
print(f" {label}.{k}: null")
else:
print(f" {label}.{k}: {str(v)[:80]}")
elif isinstance(data, list):
print(f" {label}: list ({len(data)} items)")
if data and isinstance(data[0], dict):
print(f" [0] keys: {list(data[0].keys())}")
elif data is None:
print(f" {label}: null")
def extract_items(data: dict, key: str) -> list:
"""Safely extract a list from a response dict."""
items = data.get(key)
return items if isinstance(items, list) else []
def main():
is_premium = config.SPORTSDB_KEY not in ('3', '')
print("=" * 60)
print(" TheSportsDB API Discovery")
print(f" Key: {'Premium' if is_premium else 'Free (V1 only)'}")
print("=" * 60)
# ══════════════════════════════════════════════════════
# STEP 1: Search for leagues (V1 free works for search)
# ══════════════════════════════════════════════════════
print("\n[1/8] Searching for leagues...")
mls_id = None
epl_id = None
if is_premium:
# V2 search
mls = api.search_leagues("MLS")
save("01_v2_search_leagues_mls", mls)
show_keys(mls)
items = extract_items(mls, "search")
if items:
mls_id = items[0].get("idLeague")
print(f" → MLS: {items[0].get('strLeague')} (ID: {mls_id})")
epl = api.search_leagues("Premier League")
save("01_v2_search_leagues_epl", epl)
items = extract_items(epl, "search")
if items:
epl_id = items[0].get("idLeague")
print(f" → EPL: {items[0].get('strLeague')} (ID: {epl_id})")
else:
print(" (V2 search requires premium — using V1 search)")
# Also try V1 search (works on free key)
mls_v1 = api.v1_search_teams("Toronto FC")
save("01_v1_search_teams_tfc", mls_v1)
show_keys(mls_v1)
teams = extract_items(mls_v1, "teams")
tfc_id = None
if teams:
tfc_id = teams[0].get("idTeam")
mls_id = mls_id or teams[0].get("idLeague")
print(f" → Toronto FC: ID={tfc_id}, league={teams[0].get('strLeague')} "
f"(leagueID={teams[0].get('idLeague')})")
ars_v1 = api.v1_search_teams("Arsenal")
save("01_v1_search_teams_arsenal", ars_v1)
teams = extract_items(ars_v1, "teams")
ars_id = None
if teams:
ars_id = teams[0].get("idTeam")
epl_id = epl_id or teams[0].get("idLeague")
print(f" → Arsenal: ID={ars_id}, league={teams[0].get('strLeague')} "
f"(leagueID={teams[0].get('idLeague')})")
# ══════════════════════════════════════════════════════
# STEP 2: Team detail
# ══════════════════════════════════════════════════════
print("\n[2/8] Fetching team details...")
if tfc_id:
tfc_detail = api.v1_lookup_team(int(tfc_id))
save("02_v1_team_detail_tfc", tfc_detail)
teams = extract_items(tfc_detail, "teams")
if teams:
t = teams[0]
print(f"{t.get('strTeam')}: {t.get('strStadium')}, "
f"{t.get('strStadiumLocation')}")
print(f" Keys: {list(t.keys())[:15]}...")
# ══════════════════════════════════════════════════════
# STEP 3: Schedule — next + previous matches
# ══════════════════════════════════════════════════════
print("\n[3/8] Fetching team schedules...")
event_id = None
if tfc_id:
# V1 previous (last 5)
prev = api.v1_previous_team(int(tfc_id))
save("03_v1_previous_tfc", prev)
results = extract_items(prev, "results")
print(f" Previous matches: {len(results)}")
for m in results[:3]:
eid = m.get("idEvent")
home = m.get("strHomeTeam", "?")
away = m.get("strAwayTeam", "?")
hscore = m.get("intHomeScore", "?")
ascore = m.get("intAwayScore", "?")
date = m.get("dateEvent", "?")
print(f" {date} {home} {hscore}-{ascore} {away} (eventID: {eid})")
if not event_id and hscore is not None:
event_id = int(eid)
# V1 next (next 5)
nxt = api.v1_next_team(int(tfc_id))
save("03_v1_next_tfc", nxt)
events = extract_items(nxt, "events")
print(f" Upcoming matches: {len(events)}")
for m in events[:3]:
home = m.get("strHomeTeam", "?")
away = m.get("strAwayTeam", "?")
date = m.get("dateEvent", "?")
print(f" {date} {home} vs {away}")
if is_premium and tfc_id:
# V2 schedule endpoints
prev_v2 = api.schedule_previous_team(int(tfc_id))
save("03_v2_previous_tfc", prev_v2)
show_keys(prev_v2)
next_v2 = api.schedule_next_team(int(tfc_id))
save("03_v2_next_tfc", next_v2)
show_keys(next_v2)
# ══════════════════════════════════════════════════════
# STEP 4: Match detail (V2 lookup for a finished match)
# ══════════════════════════════════════════════════════
print("\n[4/8] Fetching match detail...")
if event_id:
print(f" Using event ID: {event_id}")
# V1 event lookup (works on free)
ev1 = api.v1_event_results(event_id)
save("04_v1_event_detail", ev1)
events = extract_items(ev1, "events")
if events:
e = events[0]
print(f" V1 event keys: {list(e.keys())[:15]}...")
print(f"{e.get('strHomeTeam')} {e.get('intHomeScore')}-"
f"{e.get('intAwayScore')} {e.get('strAwayTeam')}")
print(f" Venue: {e.get('strVenue')}, Referee: {e.get('strOfficial')}")
if is_premium:
# V2 event detail
ev2 = api.lookup_event(event_id)
save("04_v2_event_detail", ev2)
show_keys(ev2)
# V2 stats
stats = api.lookup_event_stats(event_id)
save("04_v2_event_stats", stats)
show_keys(stats)
stat_items = extract_items(stats, "lookup")
if stat_items:
print(f" Stats sample:")
for s in stat_items[:5]:
print(f" {s.get('strStat')}: "
f"Home={s.get('intHome')} Away={s.get('intAway')}")
# V2 timeline
timeline = api.lookup_event_timeline(event_id)
save("04_v2_event_timeline", timeline)
show_keys(timeline)
tl_items = extract_items(timeline, "lookup")
if tl_items:
print(f" Timeline events: {len(tl_items)}")
for t in tl_items[:5]:
print(f" {t.get('intTime')}' {t.get('strTimeline')}: "
f"{t.get('strPlayer')} ({t.get('strTeam')})")
# V2 lineup
lineup = api.lookup_event_lineup(event_id)
save("04_v2_event_lineup", lineup)
show_keys(lineup)
lineup_items = extract_items(lineup, "lookup")
if lineup_items:
print(f" Lineup entries: {len(lineup_items)}")
for p in lineup_items[:3]:
print(f" #{p.get('intSquadNumber')} {p.get('strPlayer')} "
f"({p.get('strPosition')}) "
f"home={p.get('strHome')} sub={p.get('strSubstitute')}")
else:
print(" ⚠ No event ID found — skipping")
# ══════════════════════════════════════════════════════
# STEP 5: Squad roster + player detail
# ══════════════════════════════════════════════════════
print("\n[5/8] Fetching squad & player detail...")
player_id = None
if is_premium and tfc_id:
squad = api.list_players(int(tfc_id))
save("05_v2_squad_tfc", squad)
show_keys(squad)
players = extract_items(squad, "list")
if players:
p = players[0]
player_id = p.get("idPlayer")
print(f" Squad size: {len(players)}")
print(f" Sample player: {p.get('strPlayer')} "
f"(#{p.get('strNumber')}, {p.get('strPosition')})")
print(f" Player keys: {list(p.keys())[:15]}...")
if player_id:
pdetail = api.lookup_player(int(player_id))
save("05_v2_player_detail", pdetail)
show_keys(pdetail)
else:
# V1 player search
players_v1 = api.v1_search_players("Bernardeschi")
save("05_v1_search_player", players_v1)
show_keys(players_v1)
items = extract_items(players_v1, "player")
if items:
player_id = items[0].get("idPlayer")
print(f"{items[0].get('strPlayer')} (ID: {player_id})")
print(f" Keys: {list(items[0].keys())[:15]}...")
# ══════════════════════════════════════════════════════
# STEP 6: Standings (V1)
# ══════════════════════════════════════════════════════
print("\n[6/8] Fetching standings...")
if epl_id:
# EPL should have current season data
standings = api.v1_standings(int(epl_id), "2025-2026")
save("06_v1_standings_epl", standings)
table = extract_items(standings, "table")
print(f" EPL standings: {len(table)} teams")
if table:
print(f" [0] keys: {list(table[0].keys())}")
for row in table[:3]:
print(f" #{row.get('intRank')} {row.get('strTeam')} "
f"P:{row.get('intPlayed')} W:{row.get('intWin')} "
f"D:{row.get('intDraw')} L:{row.get('intLoss')} "
f"Pts:{row.get('intPoints')}")
if mls_id:
standings_mls = api.v1_standings(int(mls_id), "2026")
save("06_v1_standings_mls", standings_mls)
table = extract_items(standings_mls, "table")
print(f" MLS standings: {len(table)} teams")
if table:
for row in table[:3]:
print(f" #{row.get('intRank')} {row.get('strTeam')} "
f"Pts:{row.get('intPoints')}")
# ══════════════════════════════════════════════════════
# STEP 7: Events by date (V1)
# ══════════════════════════════════════════════════════
print("\n[7/8] Fetching events by date...")
events_today = api.v1_events_by_date("2026-03-09")
save("07_v1_events_today", events_today)
ev_list = extract_items(events_today, "events")
print(f" Soccer events today: {len(ev_list)}")
if ev_list:
print(f" [0] keys: {list(ev_list[0].keys())[:15]}...")
for e in ev_list[:3]:
print(f" {e.get('strLeague')}: {e.get('strHomeTeam')} vs "
f"{e.get('strAwayTeam')} ({e.get('strStatus')})")
if mls_id:
mls_events = api.v1_events_by_date_league("2026-03-09", int(mls_id))
save("07_v1_events_today_mls", mls_events)
ev_list = extract_items(mls_events, "events")
print(f" MLS events today: {len(ev_list)}")
for e in ev_list[:5]:
print(f" {e.get('strHomeTeam')} vs {e.get('strAwayTeam')} "
f"({e.get('strStatus')})")
# ══════════════════════════════════════════════════════
# STEP 8: Livescores (V2 premium only)
# ══════════════════════════════════════════════════════
print("\n[8/8] Fetching livescores...")
if is_premium:
live = api.livescores_soccer()
save("08_v2_livescores", live)
show_keys(live)
games = extract_items(live, "livescore")
print(f" Live soccer matches: {len(games)}")
for g in games[:3]:
print(f" {g.get('strHomeTeam')} {g.get('intHomeScore')}-"
f"{g.get('intAwayScore')} {g.get('strAwayTeam')} "
f"({g.get('strStatus')} {g.get('strProgress')}')")
else:
print(" (Livescores require premium key)")
# ══════════════════════════════════════════════════════
# Summary
# ══════════════════════════════════════════════════════
print("\n" + "=" * 60)
files = sorted(SAMPLES_DIR.glob("*.json"))
print(f" Saved {len(files)} response samples to docs/api_samples/sportsdb/")
for f in files:
size_kb = f.stat().st_size / 1024
print(f" {f.name:.<55} {size_kb:.1f} KB")
print("=" * 60)
print(f"\n API calls made: {_call_count}")
print(f" Key type: {'Premium (V1+V2)' if is_premium else 'Free (V1 only)'}")
print(f"\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()