from fastapi import APIRouter, Depends, HTTPException, Query from typing import List, Optional, Literal import copy import logging import pydantic from ..db_engine import ( db, Decision, StratGame, Player, model_to_dict, chunked, fn, Team, ) from ..dependencies import ( oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors, ) logger = logging.getLogger("discord_app") router = APIRouter(prefix="/api/v3/decisions", tags=["decisions"]) class DecisionModel(pydantic.BaseModel): game_id: int season: int week: int game_num: int pitcher_id: int team_id: int win: int = 0 loss: int = 0 hold: int = 0 is_save: int = 0 is_start: bool = False b_save: int = 0 irunners: int = 0 irunners_scored: int = 0 rest_ip: float = 0 rest_required: int = 0 class DecisionList(pydantic.BaseModel): decisions: List[DecisionModel] class DecisionReturnList(pydantic.BaseModel): count: int decisions: list[DecisionModel] @router.get("") @handle_db_errors async def get_decisions( season: list = Query(default=None), week: list = Query(default=None), game_num: list = Query(default=None), s_type: Literal["regular", "post", "all", None] = None, team_id: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None, win: Optional[int] = None, loss: Optional[int] = None, hold: Optional[int] = None, save: Optional[int] = None, b_save: Optional[int] = None, irunners: list = Query(default=None), irunners_scored: list = Query(default=None), game_id: list = Query(default=None), player_id: list = Query(default=None), limit: Optional[int] = None, short_output: Optional[bool] = False, ): all_dec = Decision.select().order_by( -Decision.season, -Decision.week, -Decision.game_num ) if season is not None: all_dec = all_dec.where(Decision.season << season) if week is not None: all_dec = all_dec.where(Decision.week << week) if game_num is not None: all_dec = all_dec.where(Decision.game_num << game_num) if game_id is not None: all_dec = all_dec.where(Decision.game_id << game_id) if player_id is not None: all_dec = all_dec.where(Decision.pitcher << player_id) # # Need to allow for split-season stats # if team_id is not None: # all_teams = Team.select().where(Team.id << team_id) # all_games = StratGame.select().where( # (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams)) # all_dec = all_dec.where(Decision.game << all_games) # if team_id is not None: # all_players = Player.select().where(Player.team_id << team_id) # all_dec = all_dec.where(Decision.pitcher << all_players) if team_id is not None: s8_teams = [int(x) for x in team_id if int(x) <= 350] if season is not None and 8 in season or s8_teams: all_teams = Team.select().where(Team.id << team_id) all_games = StratGame.select().where( (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams) ) all_dec = all_dec.where(Decision.game << all_games) else: all_teams = Team.select().where(Team.id << team_id) all_dec = all_dec.where(Decision.team << all_teams) if s_type is not None: all_games = StratGame.select().where(StratGame.season_type == s_type) all_dec = all_dec.where(Decision.game << all_games) # if team_id is not None: # all_players = Player.select().where(Player.team_id << team_id) # all_dec = all_dec.where(Decision.pitcher << all_players) if week_start is not None: all_dec = all_dec.where(Decision.week >= week_start) if week_end is not None: all_dec = all_dec.where(Decision.week <= week_end) if win is not None: all_dec = all_dec.where(Decision.win == win) if loss is not None: all_dec = all_dec.where(Decision.loss == loss) if hold is not None: all_dec = all_dec.where(Decision.hold == hold) if save is not None: all_dec = all_dec.where(Decision.save == save) if b_save is not None: all_dec = all_dec.where(Decision.b_save == b_save) if irunners is not None: all_dec = all_dec.where(Decision.irunners << irunners) if irunners_scored is not None: all_dec = all_dec.where(Decision.irunners_scored << irunners_scored) if limit is not None: if limit < 1: limit = 1 all_dec = all_dec.limit(limit) return_dec = { "count": all_dec.count(), "decisions": [model_to_dict(x, recurse=not short_output) for x in all_dec], } return return_dec @router.patch("/{decision_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def patch_decision( decision_id: int, win: Optional[int] = None, loss: Optional[int] = None, hold: Optional[int] = None, save: Optional[int] = None, b_save: Optional[int] = None, irunners: Optional[int] = None, irunners_scored: Optional[int] = None, rest_ip: Optional[int] = None, rest_required: Optional[int] = None, token: str = Depends(oauth2_scheme), ): if not valid_token(token): logger.warning(f"patch_decision - Bad Token: {token}") raise HTTPException(status_code=401, detail="Unauthorized") this_dec = Decision.get_or_none(Decision.id == decision_id) if this_dec is None: raise HTTPException( status_code=404, detail=f"Decision ID {decision_id} not found" ) if win is not None: this_dec.win = win if loss is not None: this_dec.loss = loss if hold is not None: this_dec.hold = hold if save is not None: this_dec.is_save = save if b_save is not None: this_dec.b_save = b_save if irunners is not None: this_dec.irunners = irunners if irunners_scored is not None: this_dec.irunners_scored = irunners_scored if rest_ip is not None: this_dec.rest_ip = rest_ip if rest_required is not None: this_dec.rest_required = rest_required if this_dec.save() == 1: d_result = model_to_dict(this_dec) return d_result else: raise HTTPException( status_code=500, detail=f"Unable to patch decision {decision_id}" ) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)): if not valid_token(token): logger.warning(f"post_decisions - Bad Token: {token}") raise HTTPException(status_code=401, detail="Unauthorized") new_dec = [] for x in dec_list.decisions: if StratGame.get_or_none(StratGame.id == x.game_id) is None: raise HTTPException( status_code=404, detail=f"Game ID {x.game_id} not found" ) if Player.get_or_none(Player.id == x.pitcher_id) is None: raise HTTPException( status_code=404, detail=f"Player ID {x.pitcher_id} not found" ) new_dec.append(x.dict()) with db.atomic(): for batch in chunked(new_dec, 10): Decision.insert_many(batch).on_conflict_ignore().execute() return f"Inserted {len(new_dec)} decisions" @router.delete("/{decision_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): logger.warning(f"delete_decision - Bad Token: {token}") raise HTTPException(status_code=401, detail="Unauthorized") this_dec = Decision.get_or_none(Decision.id == decision_id) if this_dec is None: raise HTTPException( status_code=404, detail=f"Decision ID {decision_id} not found" ) count = this_dec.delete_instance() if count == 1: return f"Decision {decision_id} has been deleted" else: raise HTTPException( status_code=500, detail=f"Decision {decision_id} could not be deleted" ) @router.delete("/game/{game_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): logger.warning(f"delete_decisions_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: raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found") count = Decision.delete().where(Decision.game == this_game).execute() if count > 0: return f"Deleted {count} decisions matching Game ID {game_id}" else: raise HTTPException( status_code=500, detail=f"No decisions matching Game ID {game_id} were deleted", )