feat: Feature 1.2 (NBA stats FastAPI service) + Feature 1.4 (database schema)
Feature 1.2: Python FastAPI microservice wrapping nba_api - GET /stats/season-avg, /stats/last-n, /stats/splits, /players/search - Redis caching (24hr/1hr/6hr/7day), 0.6s rate limiting, PRA derived stat - 27 Python tests passing Feature 1.4: Complete Supabase database schema - 6 tables: users, picks, scan_sessions, bets, outcomes, performance - RLS enabled on all tables with auth.uid() policies - 3 triggers: auto-create user, updated_at, scan count reset - 37 schema validation tests passing - Migration SQL ready, pending manual apply (WSL2 DNS blocker) Total: 92 tests (65 Node.js + 27 Python), all passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from app.services.stats import get_season_avg, get_last_n, get_splits
|
||||
from app.utils.player_map import search_players
|
||||
from app.utils.cache import cache_health
|
||||
|
||||
app = FastAPI(title="BetonBLK NBA Stats Service", version="1.0.0")
|
||||
|
||||
VALID_STAT_TYPES = {
|
||||
"points", "rebounds", "assists", "threes", "blocks",
|
||||
"steals", "pra", "turnovers", "minutes", "games_played",
|
||||
}
|
||||
|
||||
VALID_SPLIT_TYPES = {"home_away", "rest_days", "vs_team"}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok", "cache": "connected" if cache_health() else "disconnected"}
|
||||
|
||||
|
||||
@app.get("/players/search")
|
||||
async def player_search(name: str = Query(..., min_length=2)):
|
||||
results = search_players(name)
|
||||
if not results:
|
||||
raise HTTPException(status_code=404, detail=f"Player not found: {name}")
|
||||
return {"results": results}
|
||||
|
||||
|
||||
@app.get("/stats/season-avg")
|
||||
async def season_avg(
|
||||
player: str = Query(..., min_length=2),
|
||||
stat_type: str = Query(None),
|
||||
season: str = Query(None),
|
||||
):
|
||||
if stat_type and stat_type not in VALID_STAT_TYPES:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid stat_type: {stat_type}")
|
||||
|
||||
try:
|
||||
result = get_season_avg(player, stat_type=stat_type, season=season)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=503, detail="NBA stats service unavailable")
|
||||
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail=f"Player not found: {player}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/stats/last-n")
|
||||
async def last_n(
|
||||
player: str = Query(..., min_length=2),
|
||||
n: int = Query(10, ge=1, le=30),
|
||||
stat_type: str = Query(None),
|
||||
):
|
||||
if stat_type and stat_type not in VALID_STAT_TYPES:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid stat_type: {stat_type}")
|
||||
|
||||
try:
|
||||
result = get_last_n(player, n=n, stat_type=stat_type)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=503, detail="NBA stats service unavailable")
|
||||
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail=f"Player not found: {player}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/stats/splits")
|
||||
async def splits(
|
||||
player: str = Query(..., min_length=2),
|
||||
stat_type: str = Query(...),
|
||||
split_type: str = Query(...),
|
||||
opponent: str = Query(None),
|
||||
):
|
||||
if stat_type not in VALID_STAT_TYPES:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid stat_type: {stat_type}")
|
||||
|
||||
if split_type not in VALID_SPLIT_TYPES:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid split_type: {split_type}")
|
||||
|
||||
if split_type == "vs_team" and not opponent:
|
||||
raise HTTPException(status_code=400, detail="opponent is required when split_type=vs_team")
|
||||
|
||||
try:
|
||||
result = get_splits(player, stat_type, split_type, opponent=opponent)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=503, detail="NBA stats service unavailable")
|
||||
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail=f"Player not found: {player}")
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user