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

103
scripts/pull_tfc.py Normal file
View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""
Pull Toronto FC squad and fixtures and store in Portia DB.
Delegates all API calls and DB writes to nike.sync.sync_team_data()
so this script stays thin and the real logic lives in one place.
"""
import sys
from pathlib import Path
# Make sure the project root is on the path when run directly
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from datetime import date
from nike import config, db
from nike.sync import sync_team_data
def main() -> None:
if not config.API_FOOTBALL_KEY:
print("❌ API_FOOTBALL_KEY not set in .env")
sys.exit(1)
print("=" * 60)
print(" NIKE — Toronto FC Data Pull")
print(f" API: {config.API_FOOTBALL_BASE}")
print("=" * 60)
db.create_pool()
try:
results = sync_team_data(
team_search=config.TFC_SEARCH,
seasons=config.SEASONS,
)
finally:
db.close_pool()
# ── Summary ──────────────────────────────────────────
if results["errors"]:
print("\n⚠️ Errors encountered:")
for e in results["errors"]:
print(f"{e}")
print(f"\n✅ Team: {results.get('team', 'unknown')}")
print(f" Players: {results['players']}")
print(f" API calls: {results['api_calls']}")
for season, count in results.get("seasons", {}).items():
print(f" Fixtures {season}: {count}")
if results.get("squad"):
_print_squad(results["squad"])
if results.get("all_fixtures"):
_print_fixtures(results["all_fixtures"])
def _print_squad(squad: list[dict]) -> None:
print("\n" + "=" * 60)
print(" SQUAD")
print("=" * 60)
pos_order = ["Goalkeeper", "Defender", "Midfielder", "Attacker"]
by_pos: dict = {}
for p in squad:
by_pos.setdefault(p.get("position") or "Unknown", []).append(p)
for pos in pos_order + [k for k in by_pos if k not in pos_order]:
if pos not in by_pos:
continue
print(f"\n {pos.upper()}S:")
for p in sorted(by_pos[pos],
key=lambda x: x["number"] if isinstance(x.get("number"), int) else 99):
print(f" #{str(p.get('number', '-')).rjust(2)} {p['name']}")
def _print_fixtures(fixtures: list[dict]) -> None:
print("\n" + "=" * 60)
print(" FIXTURES")
print("=" * 60)
today = date.today().isoformat()
today_f = [f for f in fixtures if f["date"][:10] == today]
completed = [f for f in fixtures if f["status"] in ("FT", "AET", "PEN")]
upcoming = [f for f in fixtures if f["status"] in ("NS", "TBD", "PST")]
if today_f:
print("\n 🔴 TODAY:")
for f in today_f:
score = f" {f['score']}" if f.get("score") else ""
print(f" {f['home']}{score} {f['away']} @ {f['venue']} [{f['status']}]")
if completed:
print("\n RECENT RESULTS (last 5):")
for f in completed[-5:]:
print(f" {f['date'][:10]} {f['home']} {f['score']} {f['away']}")
if upcoming:
print("\n UPCOMING (next 5):")
for f in sorted(upcoming, key=lambda x: x["date"])[:5]:
print(f" {f['date'][:10]} {f['home']} vs {f['away']} [{f.get('round', '')}]")
if __name__ == "__main__":
main()