Add REST API endpoints for team player statistics

Implemented complete REST API to retrieve NHL player statistics for entire teams:

Application Layer:
- Created DTOs for Player, SkaterStats, GoalieStats, and PlayerWithStatsDTO
- Implemented GetTeamPlayerStatsUseCase that orchestrates data retrieval
- Transforms domain entities to API-friendly DTOs

Presentation Layer:
- Created /api/v1/teams/{team_id}/players endpoint (all players)
- Created /api/v1/teams/{team_id}/players/skaters endpoint (skaters only)
- Created /api/v1/teams/{team_id}/players/goalies endpoint (goalies only)
- Integrated routes into main FastAPI application

Features:
- Retrieves complete roster for any NHL team by abbreviation
- Aggregates current season statistics for each player
- Differentiates between skater and goalie statistics
- Proper error handling with 404 for invalid teams
- Follows CLEAN architecture with dependency injection

Documentation:
- Created comprehensive API_GUIDE.md with usage examples
- Includes setup instructions, endpoint documentation
- Python, JavaScript, and cURL examples
- Complete list of NHL team abbreviations

Testing:
- Added test scripts for both direct adapter testing and REST API testing
- Verified functionality with Toronto Maple Leafs data

The REST API is now ready for integration with fantasy hockey analysis tools.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Simard
2025-11-23 22:44:39 -06:00
parent 28169863df
commit c10fdd594d
11 changed files with 930 additions and 6 deletions

120
test_nhl_api.py Normal file
View File

@@ -0,0 +1,120 @@
"""Manual test script for NHL API integration."""
import asyncio
import sys
from datetime import datetime
# Add src to path for imports
sys.path.insert(0, "/Users/michaelsimard/dev/services/project-kempe-backend")
from src.infrastructure.adapters.nhl import NHLPlayerAdapter, NHLTeamAdapter
async def test_teams():
"""Test team retrieval."""
print("\n" + "=" * 60)
print("TESTING TEAM ADAPTER")
print("=" * 60)
team_adapter = NHLTeamAdapter()
# Test getting all teams
print("\n1. Getting all NHL teams...")
teams = await team_adapter.get_all_teams()
print(f" Found {len(teams)} teams")
if teams:
print("\n First 5 teams:")
for team in teams[:5]:
print(f" - {team.full_name} ({team.abbreviation}) - {team.division}")
# Test getting a specific team Wings
print("\n2. Getting Detroit Red Wings by abbreviation (DET)...")
wings = await team_adapter.get_team_by_id("DET")
if wings:
print(f" ✓ Team: {wings.full_name}")
print(f" City: {wings.city}")
print(f" Division: {wings.division}")
print(f" Conference: {wings.conference}")
else:
print(" ✗ Team not found")
# Test getting teams by division
print("\n3. Getting teams from Atlantic Division...")
atlantic_teams = await team_adapter.get_teams_by_division("Atlantic")
print(f" Found {len(atlantic_teams)} teams in Atlantic Division")
for team in atlantic_teams:
print(f" - {team.full_name}")
async def test_players():
"""Test player retrieval."""
print("\n" + "=" * 60)
print("TESTING PLAYER ADAPTER")
print("=" * 60)
player_adapter = NHLPlayerAdapter()
# Test getting team roster (Toronto Maple Leafs)
print("\n1. Getting Detroit Red Wings roster...")
players = await player_adapter.get_players_by_team("DET")
print(f" Found {len(players)} players on roster")
if players:
print("\n Sample players:")
for player in players[:5]:
print(
f" - #{player.jersey_number or '??'} {player.full_name} ({player.position})"
)
# Get a specific player for stats testing
test_player = players[0]
print(f"\n2. Getting stats for {test_player.full_name} (ID: {test_player.id})...")
if test_player.is_goalie():
stats = await player_adapter.get_goalie_stats(test_player.id)
if stats:
print(f" ✓ Goalie Stats:")
print(f" Games Played: {stats.games_played}")
print(f" Wins: {stats.wins}")
print(f" Save %: {stats.save_percentage:.1f}%")
print(f" GAA: {stats.goals_against_average:.2f}")
else:
print(" ✗ No stats found")
else:
stats = await player_adapter.get_skater_stats(test_player.id)
if stats:
print(f" ✓ Skater Stats:")
print(f" Games Played: {stats.games_played}")
print(f" Goals: {stats.goals}")
print(f" Assists: {stats.assists}")
print(f" Points: {stats.points}")
print(f" Plus/Minus: {stats.plus_minus:+d}")
else:
print(" ✗ No stats found")
async def main():
"""Run all tests."""
print("\n" + "=" * 60)
print("NHL API INTEGRATION TEST")
print(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
try:
await test_teams()
await test_players()
print("\n" + "=" * 60)
print("ALL TESTS COMPLETED")
print("=" * 60 + "\n")
except Exception as e:
print(f"\n✗ ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())