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,50 @@
|
||||
from nba_api.stats.static import players
|
||||
from app.utils.cache import cache_get, cache_set
|
||||
from app.config import PLAYER_SEARCH_TTL
|
||||
|
||||
|
||||
def normalize_name(name):
|
||||
return name.strip().lower()
|
||||
|
||||
|
||||
def search_players(name):
|
||||
cache_key = f"nba:player:{normalize_name(name)}"
|
||||
cached = cache_get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
all_players = players.get_players()
|
||||
search_lower = normalize_name(name)
|
||||
|
||||
matches = []
|
||||
for p in all_players:
|
||||
full_name = p["full_name"].lower()
|
||||
if search_lower in full_name:
|
||||
matches.append({
|
||||
"player_id": p["id"],
|
||||
"full_name": p["full_name"],
|
||||
"is_active": p["is_active"],
|
||||
})
|
||||
|
||||
cache_set(cache_key, matches, PLAYER_SEARCH_TTL)
|
||||
return matches
|
||||
|
||||
|
||||
def resolve_player(name):
|
||||
"""Resolve a player name to a single active player. Returns (player_id, full_name) or raises."""
|
||||
matches = search_players(name)
|
||||
active = [m for m in matches if m["is_active"]]
|
||||
|
||||
if not active:
|
||||
if matches:
|
||||
# Return first inactive match as fallback
|
||||
return matches[0]["player_id"], matches[0]["full_name"]
|
||||
return None, None
|
||||
|
||||
# Prefer exact match
|
||||
search_lower = normalize_name(name)
|
||||
for m in active:
|
||||
if m["full_name"].lower() == search_lower:
|
||||
return m["player_id"], m["full_name"]
|
||||
|
||||
return active[0]["player_id"], active[0]["full_name"]
|
||||
Reference in New Issue
Block a user