/plays and /decisions
Added /plays validators for scorecard, first pass at /decisions
This commit is contained in:
parent
5718ce1af9
commit
40df835d98
@ -1851,8 +1851,8 @@ class StratGame(BaseModel):
|
||||
class StratPlay(BaseModel):
|
||||
game = ForeignKeyField(StratGame)
|
||||
play_num = IntegerField()
|
||||
batter = ForeignKeyField(Player)
|
||||
batter_team = ForeignKeyField(Team)
|
||||
batter = ForeignKeyField(Player, null=True)
|
||||
batter_team = ForeignKeyField(Team, null=True)
|
||||
pitcher = ForeignKeyField(Player)
|
||||
pitcher_team = ForeignKeyField(Team)
|
||||
on_base_code = CharField()
|
||||
@ -1862,7 +1862,7 @@ class StratPlay(BaseModel):
|
||||
starting_outs = IntegerField()
|
||||
away_score = IntegerField()
|
||||
home_score = IntegerField()
|
||||
batter_pos = CharField()
|
||||
batter_pos = CharField(null=True)
|
||||
|
||||
# These <base>_final fields track the base this runner advances to post-play (None) if out
|
||||
on_first = ForeignKeyField(Player, null=True)
|
||||
@ -1915,6 +1915,24 @@ class StratPlay(BaseModel):
|
||||
is_new_inning = BooleanField(default=False)
|
||||
|
||||
|
||||
class Decision(BaseModel):
|
||||
game = ForeignKeyField(StratGame)
|
||||
season = IntegerField()
|
||||
week = IntegerField()
|
||||
game_num = IntegerField()
|
||||
pitcher = ForeignKeyField(Player)
|
||||
win = IntegerField()
|
||||
loss = IntegerField()
|
||||
hold = IntegerField()
|
||||
is_save = IntegerField()
|
||||
b_save = IntegerField()
|
||||
irunners = IntegerField()
|
||||
irunners_scored = IntegerField()
|
||||
rest_ip = IntegerField()
|
||||
rest_required = IntegerField()
|
||||
is_start = BooleanField(default=False)
|
||||
|
||||
|
||||
# class Streak(BaseModel):
|
||||
# player = ForeignKeyField(Player)
|
||||
# streak_type = CharField()
|
||||
@ -1940,6 +1958,6 @@ class StratPlay(BaseModel):
|
||||
db.create_tables([
|
||||
Current, Division, Manager, Team, Result, Player, Schedule, Transaction, BattingStat, PitchingStat, Standings,
|
||||
BattingCareer, PitchingCareer, FieldingCareer, Manager, Award, DiceRoll, DraftList, Keeper, StratGame, StratPlay,
|
||||
Injury
|
||||
Injury, Decision
|
||||
])
|
||||
db.close()
|
||||
|
||||
@ -8,7 +8,7 @@ from fastapi import Depends, FastAPI, Request
|
||||
|
||||
from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \
|
||||
pitchingstats, fieldingstats, draftpicks, draftlist, managers, awards, draftdata, keepers, stratgame, stratplay, \
|
||||
injuries
|
||||
injuries, decisions
|
||||
|
||||
date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
|
||||
log_level = logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN'
|
||||
@ -23,6 +23,9 @@ app = FastAPI(
|
||||
)
|
||||
|
||||
|
||||
logging.info(f'Starting up now...')
|
||||
|
||||
|
||||
app.include_router(current.router)
|
||||
app.include_router(players.router)
|
||||
app.include_router(results.router)
|
||||
@ -42,7 +45,9 @@ app.include_router(keepers.router)
|
||||
app.include_router(stratgame.router)
|
||||
app.include_router(stratplay.router)
|
||||
app.include_router(injuries.router)
|
||||
app.include_router(decisions.router)
|
||||
|
||||
logging.info(f'Loaded all routers.')
|
||||
|
||||
# @app.get("/docs", include_in_schema=False)
|
||||
# async def get_docs(req: Request):
|
||||
|
||||
199
app/routers_v3/decisions.py
Normal file
199
app/routers_v3/decisions.py
Normal file
@ -0,0 +1,199 @@
|
||||
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
|
||||
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
||||
|
||||
logging.basicConfig(
|
||||
filename=LOG_DATA['filename'],
|
||||
format=LOG_DATA['format'],
|
||||
level=LOG_DATA['log_level']
|
||||
)
|
||||
|
||||
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
|
||||
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: int = 0
|
||||
rest_required: int = 0
|
||||
|
||||
|
||||
class DecisionList(pydantic.BaseModel):
|
||||
decisions: List[DecisionModel]
|
||||
|
||||
|
||||
@router.get('')
|
||||
async def get_decisions(
|
||||
season: list = Query(default=None), week: list = Query(default=None), game_num: list = Query(default=None),
|
||||
season_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),
|
||||
short_output: Optional[bool] = False):
|
||||
all_dec = Decision.select()
|
||||
|
||||
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 season_type is not None:
|
||||
all_games = StratGame.select().where(StratGame.season_type == season_type)
|
||||
all_dec = all_dec.where(Decision.game << all_games)
|
||||
if season_type 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)
|
||||
|
||||
return_dec = {
|
||||
'count': all_dec.count(),
|
||||
'decisions': [model_to_dict(x, recurse=not short_output) for x in all_dec]
|
||||
}
|
||||
db.close()
|
||||
return return_dec
|
||||
|
||||
|
||||
@router.patch('/{decision_id}')
|
||||
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):
|
||||
logging.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:
|
||||
db.close()
|
||||
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)
|
||||
db.close()
|
||||
return d_result
|
||||
else:
|
||||
db.close()
|
||||
raise HTTPException(status_code=500, detail=f'Unable to patch decision {decision_id}')
|
||||
|
||||
|
||||
@router.post('')
|
||||
async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)):
|
||||
if not valid_token(token):
|
||||
logging.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_replace().execute()
|
||||
db.close()
|
||||
|
||||
return f'Inserted {len(new_dec)} decisions'
|
||||
|
||||
|
||||
@router.delete('/{decision_id')
|
||||
async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)):
|
||||
if not valid_token(token):
|
||||
logging.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:
|
||||
db.close()
|
||||
raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found')
|
||||
|
||||
count = this_dec.delete_instance()
|
||||
db.close()
|
||||
|
||||
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}')
|
||||
async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)):
|
||||
if not valid_token(token):
|
||||
logging.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:
|
||||
db.close()
|
||||
raise HTTPException(status_code=404, detail=f'Game ID {game_id} not found')
|
||||
|
||||
count = Decision.delete().where(Decision.game == this_game).execute()
|
||||
db.close()
|
||||
|
||||
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')
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from typing import List, Optional, Literal
|
||||
import copy
|
||||
import logging
|
||||
import pydantic
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
from ..db_engine import db, StratPlay, StratGame, Team, model_to_dict, chunked, fn
|
||||
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
||||
@ -21,21 +21,21 @@ router = APIRouter(
|
||||
POS_LIST = Literal['C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'P', 'DH', 'PH', 'PR']
|
||||
|
||||
|
||||
class PlayModel(pydantic.BaseModel):
|
||||
class PlayModel(BaseModel):
|
||||
game_id: int
|
||||
play_num: int
|
||||
batter_id: int
|
||||
batter_id: int = None
|
||||
batter_team_id: int = None
|
||||
pitcher_id: int
|
||||
pitcher_team_id: int = None
|
||||
on_base_code: str
|
||||
inning_half: Literal['top', 'bot']
|
||||
inning_half: Literal['top', 'bot', 'Top', 'Bot']
|
||||
inning_num: int
|
||||
batting_order: int
|
||||
starting_outs: int
|
||||
away_score: int
|
||||
home_score: int
|
||||
batter_pos: POS_LIST
|
||||
batter_pos: POS_LIST = None
|
||||
|
||||
on_first_id: int = None
|
||||
on_first_final: int = None
|
||||
@ -85,8 +85,32 @@ class PlayModel(pydantic.BaseModel):
|
||||
is_tied: bool = False
|
||||
is_new_inning: bool = False
|
||||
|
||||
@validator('on_first_final')
|
||||
def no_final_if_no_runner_one(cls, v, values):
|
||||
if values['on_first_id'] is None:
|
||||
return None
|
||||
return v
|
||||
|
||||
class PlayList(pydantic.BaseModel):
|
||||
@validator('on_second_final')
|
||||
def no_final_if_no_runner_two(cls, v, values):
|
||||
if values['on_second_id'] is None:
|
||||
return None
|
||||
return v
|
||||
|
||||
@validator('on_third_final')
|
||||
def no_final_if_no_runner_three(cls, v, values):
|
||||
if values['on_third_id'] is None:
|
||||
return None
|
||||
return v
|
||||
|
||||
@validator('batter_final')
|
||||
def no_final_if_no_batter(cls, v, values):
|
||||
if values['batter_id'] is None:
|
||||
return None
|
||||
return v
|
||||
|
||||
|
||||
class PlayList(BaseModel):
|
||||
plays: List[PlayModel]
|
||||
|
||||
|
||||
@ -179,9 +203,10 @@ async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
|
||||
|
||||
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:
|
||||
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
|
||||
@ -191,6 +216,9 @@ async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
|
||||
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())
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user