major-domo-database/app/routers_v3/stratplay/crud.py
Cal Corum 86f8495284
All checks were successful
Build Docker Image / build (pull_request) Successful in 2m32s
feat: Add group_by=sbaplayer to batting, pitching, and fielding endpoints
Enables career-total aggregation by real-world player identity (SbaPlayer)
across all seasons. JOINs StratPlay → Player to access Player.sbaplayer FK,
groups by that FK, and excludes players with null sbaplayer. Also refactors
stratplay router from single file into package and adds integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:42:14 -06:00

160 lines
5.3 KiB
Python

import logging
from fastapi import APIRouter, Depends, HTTPException
from ...db_engine import db, StratPlay, StratGame, model_to_dict, chunked
from ...dependencies import (
oauth2_scheme,
valid_token,
PRIVATE_IN_SCHEMA,
handle_db_errors,
)
from .models import PlayModel, PlayList
router = APIRouter()
logger = logging.getLogger("discord_app")
@router.get("/{play_id}")
@handle_db_errors
async def get_one_play(play_id: int):
if StratPlay.get_or_none(StratPlay.id == play_id) is None:
db.close()
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
r_play = model_to_dict(StratPlay.get_by_id(play_id))
db.close()
return r_play
@router.patch("/{play_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def patch_play(
play_id: int, new_play: PlayModel, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logger.warning(f"patch_play - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized")
if StratPlay.get_or_none(StratPlay.id == play_id) is None:
db.close()
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
StratPlay.update(**new_play.dict()).where(StratPlay.id == play_id).execute()
r_play = model_to_dict(StratPlay.get_by_id(play_id))
db.close()
return r_play
@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logger.warning(f"post_plays - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized")
new_plays = []
this_game = StratGame.get_or_none(StratGame.id == p_list.plays[0].game_id)
if this_game is None:
raise HTTPException(
status_code=404, detail=f"Game ID {p_list.plays[0].game_id} not found"
)
for play in p_list.plays:
this_play = play
this_play.inning_half = this_play.inning_half.lower()
top_half = this_play.inning_half == "top"
if this_play.batter_team_id is None and this_play.batter_id is not None:
this_play.batter_team_id = (
this_game.away_team.id if top_half else this_game.home_team.id
)
if this_play.pitcher_team_id is None:
this_play.pitcher_team_id = (
this_game.home_team.id if top_half else this_game.away_team.id
)
if this_play.catcher_id is not None:
this_play.catcher_team_id = (
this_game.home_team.id if top_half else this_game.away_team.id
)
if this_play.defender_id is not None:
this_play.defender_team_id = (
this_game.home_team.id if top_half else this_game.away_team.id
)
if this_play.runner_id is not None:
this_play.runner_team_id = (
this_game.away_team.id if top_half else this_game.home_team.id
)
if this_play.pa == 0:
this_play.batter_final = None
new_plays.append(this_play.dict())
with db.atomic():
for batch in chunked(new_plays, 20):
StratPlay.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_plays)} plays"
@router.delete("/{play_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logger.warning(f"delete_play - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized")
this_play = StratPlay.get_or_none(StratPlay.id == play_id)
if not this_play:
db.close()
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
count = this_play.delete_instance()
db.close()
if count == 1:
return f"Play {play_id} has been deleted"
else:
raise HTTPException(
status_code=500, detail=f"Play {play_id} could not be deleted"
)
@router.delete("/game/{game_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def delete_plays_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logger.warning(f"delete_plays_game - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found")
count = StratPlay.delete().where(StratPlay.game == this_game).execute()
db.close()
if count > 0:
return f"Deleted {count} plays matching Game ID {game_id}"
else:
raise HTTPException(
status_code=500, detail=f"No plays matching Game ID {game_id} were deleted"
)
@router.post("/erun-check", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def post_erun_check(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logger.warning(f"post_erun_check - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized")
all_plays = StratPlay.update(run=1).where(
(StratPlay.e_run == 1) & (StratPlay.run == 0)
)
count = all_plays.execute()
db.close()
return count