major-domo-database/app/routers_v3/teams.py
Cal Corum 56fca1fa03 fix: Fix CSV export, season filtering, and position matching in refactored services
Integration testing revealed three issues with the refactored service layer:

1. CSV Export Format
   - Nested team/sbaplayer dicts were being dumped as strings
   - Now flattens team to abbreviation, sbaplayer to ID
   - Matches original CSV format from pre-refactor code

2. Season=0 Filter
   - season=0 was filtering for WHERE season=0 (returns nothing)
   - Now correctly returns all seasons when season=0 or None
   - Affects 13,266 total players across all seasons

3. Generic Position "P"
   - pos=P returned no results (players have SP/RP/CP, not P)
   - Now expands P to match SP, RP, CP pitcher positions
   - Applied to both DB filtering and Python mock filtering

4. Roster Endpoint Enhancement
   - Added default /teams/{id}/roster endpoint (assumes 'current')
   - Existing /teams/{id}/roster/{which} endpoint unchanged

All changes maintain backward compatibility and pass integration tests.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-04 11:06:58 -06:00

120 lines
3.4 KiB
Python

"""
Team Router - Refactored
Thin HTTP layer using TeamService for business logic.
"""
from fastapi import APIRouter, Query, Response, Depends
from typing import List, Optional, Literal
from ..dependencies import oauth2_scheme, PRIVATE_IN_SCHEMA, handle_db_errors, cache_result
from ..services.base import BaseService
from ..services.team_service import TeamService
router = APIRouter(prefix='/api/v3/teams', tags=['teams'])
@router.get('')
@handle_db_errors
@cache_result(ttl=10*60, key_prefix='teams')
async def get_teams(
season: Optional[int] = None,
owner_id: list = Query(default=None),
manager_id: list = Query(default=None),
team_abbrev: list = Query(default=None),
active_only: Optional[bool] = False,
short_output: Optional[bool] = False,
csv: Optional[bool] = False
):
"""Get teams with filtering."""
result = TeamService.get_teams(
season=season,
owner_id=owner_id if owner_id else None,
manager_id=manager_id if manager_id else None,
team_abbrev=team_abbrev if team_abbrev else None,
active_only=active_only or False,
short_output=short_output or False,
as_csv=csv or False
)
if csv:
return Response(content=result, media_type='text/csv')
return result
@router.get('/{team_id}')
@handle_db_errors
@cache_result(ttl=30*60, key_prefix='team')
async def get_one_team(team_id: int):
"""Get a single team by ID."""
return TeamService.get_team(team_id)
@router.get('/{team_id}/roster')
@handle_db_errors
@cache_result(ttl=30*60, key_prefix='team-roster')
async def get_team_roster_default(
team_id: int,
sort: Optional[str] = None
):
"""Get team roster with IL lists (defaults to current season)."""
return TeamService.get_team_roster(team_id, 'current', sort=sort)
@router.get('/{team_id}/roster/{which}')
@handle_db_errors
@cache_result(ttl=30*60, key_prefix='team-roster')
async def get_team_roster(
team_id: int,
which: Literal['current', 'next'],
sort: Optional[str] = None
):
"""Get team roster with IL lists."""
return TeamService.get_team_roster(team_id, which, sort=sort)
@router.patch('/{team_id}')
async def patch_team(
team_id: int,
token: str = Depends(oauth2_scheme),
manager1_id: Optional[int] = None,
manager2_id: Optional[int] = None,
gmid: Optional[int] = None,
gmid2: Optional[int] = None,
mascot: Optional[str] = None,
stadium: Optional[str] = None,
thumbnail: Optional[str] = None,
color: Optional[str] = None,
abbrev: Optional[str] = None,
sname: Optional[str] = None,
lname: Optional[str] = None,
dice_color: Optional[str] = None,
division_id: Optional[int] = None,
):
"""Patch a team (partial update)."""
# Build dict of provided fields
data = {}
locals_dict = locals()
for key, value in locals_dict.items():
if key not in ('team_id', 'token') and value is not None:
data[key] = value
return TeamService.update_team(team_id, data, token)
@router.post('')
async def post_teams(
team_list: dict,
token: str = Depends(oauth2_scheme)
):
"""Create multiple teams."""
return TeamService.create_teams(team_list.get("teams", []), token)
@router.delete('/{team_id}')
async def delete_team(
team_id: int,
token: str = Depends(oauth2_scheme)
):
"""Delete a team."""
return TeamService.delete_team(team_id, token)