major-domo-database/app/db_engine.py
Cal Corum 126ce53951
Some checks failed
Build Docker Image / build (pull_request) Failing after 20s
fix: Sort games chronologically in standings recalculation for accurate streaks
The standings recalculate function was processing games in arbitrary database
order, causing win/loss streaks to be calculated incorrectly. Added explicit
ordering by week and game_num (ascending) to ensure games are processed
chronologically.

This fixes inconsistent streak values that were reported due to the streak
logic depending on processing games in the correct temporal sequence.

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

2745 lines
99 KiB
Python

import copy
import datetime
import logging
import math
import os
from typing import Literal, List, Optional
from pandas import DataFrame
from peewee import *
from peewee import ModelSelect
from playhouse.shortcuts import model_to_dict
# Database configuration - supports both SQLite and PostgreSQL
DATABASE_TYPE = os.environ.get('DATABASE_TYPE', 'sqlite')
if DATABASE_TYPE.lower() == 'postgresql':
from playhouse.pool import PooledPostgresqlDatabase
db = PooledPostgresqlDatabase(
os.environ.get('POSTGRES_DB', 'sba_master'),
user=os.environ.get('POSTGRES_USER', 'sba_admin'),
password=os.environ.get('POSTGRES_PASSWORD', 'sba_dev_password_2024'),
host=os.environ.get('POSTGRES_HOST', 'sba_postgres'),
port=int(os.environ.get('POSTGRES_PORT', '5432')),
max_connections=20,
stale_timeout=300, # 5 minutes
timeout=0,
autoconnect=True,
autorollback=True # Automatically rollback failed transactions
)
else:
# Default SQLite configuration
db = SqliteDatabase(
'storage/sba_master.db',
pragmas={
'journal_mode': 'wal',
'cache_size': -1 * 64000,
'synchronous': 0
}
)
date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
logger = logging.getLogger('discord_app')
"""
Per season updates:
Result: regular_season & post_season - set season length
update_standings - confirm division alignments and records
Standings: recalculate - e_number function, set season length
- wildcard section, set league abbrevs
"""
WEEK_NUMS = {
'regular': {
}
}
def model_csv_headers(this_obj, exclude=None) -> List:
data = model_to_dict(this_obj, recurse=False, exclude=exclude)
return [x for x in data.keys()]
def model_to_csv(this_obj, exclude=None) -> List:
data = model_to_dict(this_obj, recurse=False, exclude=exclude)
return [x for x in data.values()]
def query_to_csv(all_items: ModelSelect, exclude=None):
if all_items.count() == 0:
data_list = [['No data found']]
else:
data_list = [model_csv_headers(all_items[0], exclude=exclude)]
for x in all_items:
data_list.append(model_to_csv(x, exclude=exclude))
return DataFrame(data_list).to_csv(header=False, index=False)
def complex_data_to_csv(complex_data: List):
if len(complex_data) == 0:
data_list = [['No data found']]
else:
data_list = [[x for x in complex_data[0].keys()]]
for line in complex_data:
logger.info(f'line: {line}')
this_row = []
for key in line:
logger.info(f'key: {key}')
if line[key] is None:
this_row.append('')
elif isinstance(line[key], dict):
if 'name' in line[key]:
this_row.append(line[key]['name'])
elif 'abbrev' in line[key]:
this_row.append(line[key]['abbrev'])
else:
this_row.append(line[key]['id'])
elif isinstance(line[key], int) and line[key] > 100000000:
this_row.append(f"'{line[key]}")
elif isinstance(line[key], str) and ',' in line[key]:
this_row.append(line[key].replace(",", "-_-"))
else:
this_row.append(line[key])
data_list.append(this_row)
return DataFrame(data_list).to_csv(header=False, index=False)
def per_season_weeks(season: int, s_type: Literal['regular', 'post', 'total']):
if season == 1:
if s_type == 'regular':
return {'start': 1, 'end': 20}
elif s_type == 'post':
return {'start': 21, 'end': 22}
else:
return {'start': 1, 'end': 22}
elif season in [3, 4, 5, 6, 7]:
if s_type == 'regular':
return {'start': 1, 'end': 22}
elif s_type == 'post':
return {'start': 23, 'end': 25}
else:
return {'start': 1, 'end': 25}
# Season 2, 8, and beyond
else:
if s_type == 'regular':
return {'start': 1, 'end': 18}
elif s_type == 'post':
return {'start': 19, 'end': 21}
else:
return {'start': 1, 'end': 21}
def win_pct(this_team_stan):
if this_team_stan.wins + this_team_stan.losses == 0:
return 0
else:
return (this_team_stan.wins / (this_team_stan.wins + this_team_stan.losses)) + \
(this_team_stan.run_diff * .000001)
def games_back(leader, chaser):
return ((leader.wins - chaser.wins) + (chaser.losses - leader.losses)) / 2
def e_number(leader, chaser):
e_num = 73 - leader.wins - chaser.losses
return e_num if e_num > 0 else 0
class BaseModel(Model):
class Meta:
database = db
class Current(BaseModel):
week = IntegerField(default=0)
freeze = BooleanField(default=True)
season = IntegerField()
transcount = IntegerField(default=0)
bstatcount = IntegerField(default=0, null=True)
pstatcount = IntegerField(default=0, null=True)
bet_week = IntegerField(default=0, null=True)
trade_deadline = IntegerField()
pick_trade_start = IntegerField()
pick_trade_end = IntegerField()
playoffs_begin = IntegerField()
injury_count = IntegerField(null=True)
@staticmethod
def latest():
latest_current = Current.select().order_by(-Current.id).get()
return latest_current
class Division(BaseModel):
division_name = CharField()
division_abbrev = CharField()
league_name = CharField(null=True)
league_abbrev = CharField(null=True)
season = IntegerField(default=0)
def abbrev(self):
league_short = self.league_abbrev + ' ' if self.league_abbrev else ''
return f'{league_short}{self.division_abbrev}'
def short_name(self):
league_short = self.league_abbrev + ' ' if self.league_abbrev else ''
return f'{league_short}{self.division_name}'
def full_name(self):
league_long = self.league_name + ' ' if self.league_name else ''
return f'{league_long}{self.division_name}'
def sort_division(self, season):
div_query = Standings.select_season(season).where(Standings.team.division == self)
div_teams = [team_stan for team_stan in div_query]
div_teams.sort(key=lambda team: win_pct(team), reverse=True)
# Assign div_gb and e_num
for x in range(len(div_teams)):
# # Used for two playoff teams per divsion
# # Special calculations for the division leader
# if x == 0:
# div_teams[0].div_gb = -games_back(div_teams[0], div_teams[2])
# div_teams[0].div_e_num = None
# div_teams[0].wc_gb = None
# div_teams[0].wc_e_num = None
# elif x == 1:
# div_teams[1].div_gb = 0
# div_teams[1].div_e_num = None
# div_teams[1].wc_gb = None
# div_teams[1].wc_e_num = None
# else:
# div_teams[x].div_gb = games_back(div_teams[1], div_teams[x])
# div_teams[x].div_e_num = e_number(div_teams[1], div_teams[x])
# Used for one playoff team per division
if x == 0:
div_teams[0].div_gb = None
div_teams[0].div_e_num = None
div_teams[0].wc_gb = None
div_teams[0].wc_e_num = None
else:
div_teams[x].div_gb = games_back(div_teams[0], div_teams[x])
div_teams[x].div_e_num = e_number(div_teams[0], div_teams[x])
div_teams[x].wc_gb = 99
div_teams[x].wc_e_num = 99
div_teams[x].save()
@staticmethod
def sort_wildcard(season, league_abbrev):
divisions = Division.select().where(Division.league_abbrev == league_abbrev)
teams_query = Standings.select_season(season).where(
Standings.wc_gb.is_null(False) & (Standings.team.division << divisions)
)
league_teams = [team_stan for team_stan in teams_query]
league_teams.sort(key=lambda team: win_pct(team), reverse=True)
for x in range(len(league_teams)):
# Special calculations for two wildcard teams
if x < 4:
league_teams[x].wc_gb = -games_back(league_teams[x], league_teams[4])
league_teams[x].wc_e_num = None
# elif x == 3:
# league_teams[x].wc_gb = 0
# league_teams[x].wc_e_num = None
else:
league_teams[x].wc_gb = games_back(league_teams[3], league_teams[x])
league_teams[x].wc_e_num = e_number(league_teams[3], league_teams[x])
league_teams[x].save()
class Manager(BaseModel):
name = CharField(unique=True)
image = CharField(null=True)
headline = CharField(null=True)
bio = CharField(null=True)
class Team(BaseModel):
abbrev = CharField()
sname = CharField()
lname = CharField()
manager_legacy = CharField(null=True)
division_legacy = CharField(null=True)
gmid = CharField(max_length=20, null=True) # Discord snowflake IDs as strings
gmid2 = CharField(max_length=20, null=True) # Discord snowflake IDs as strings
manager1 = ForeignKeyField(Manager, null=True)
manager2 = ForeignKeyField(Manager, null=True)
division = ForeignKeyField(Division, null=True)
mascot = CharField(null=True)
stadium = CharField(null=True)
gsheet = CharField(null=True)
thumbnail = CharField(null=True)
color = CharField(null=True)
dice_color = CharField(null=True)
season = IntegerField()
auto_draft = BooleanField(null=True)
salary_cap = FloatField(null=True)
@staticmethod
def select_season(num):
return Team.select().where(Team.season == num)
@staticmethod
def get_by_owner(gmid, season):
team = Team.get_or_none(Team.gmid == gmid, Team.season == season)
if not team:
team = Team.get_or_none(Team.gmid2 == gmid, Team.season == season)
if not team:
return None
return team
@staticmethod
def get_season(name_or_abbrev, season):
team = Team.get_or_none(fn.Upper(Team.abbrev) == name_or_abbrev.upper(), Team.season == season)
if not team:
team = Team.get_or_none(fn.Lower(Team.sname) == name_or_abbrev.lower(), Team.season == season)
if not team:
team = Team.get_or_none(fn.Lower(Team.lname) == name_or_abbrev.lower(), Team.season == season)
return team
def get_record(self, week):
wins = Result.select_season(Current.latest().season).where(
(((Result.hometeam == self) & (Result.homescore > Result.awayscore)) |
((Result.awayteam == self) & (Result.awayscore > Result.homescore))) & (Result.week <= week)
)
losses = Result.select_season(Current.latest().season).where(
(((Result.awayteam == self) & (Result.homescore > Result.awayscore)) |
((Result.hometeam == self) & (Result.awayscore > Result.homescore))) & (Result.week <= week)
)
if wins.count() + losses.count() > 0:
pct = wins.count() / (wins.count() + losses.count())
else:
pct = 0
return {'w': wins.count(), 'l': losses.count(), 'pct': pct}
def get_gms(self):
if self.gmid2:
return [self.gmid, self.gmid2]
else:
return [self.gmid]
def get_this_week(self):
active_team = Player.select_season(self.season).where(Player.team == self).order_by(Player.wara)
active_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []}
combo_pitchers = 0
for guy in active_team:
active_roster['WARa'] += guy.wara
active_roster['players'].append(guy)
guy_pos = guy.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
try:
for pos in guy_pos:
active_roster[pos] += 1
except KeyError:
# This happens for season 1 without player positions listed
pass
if combo_pitchers > 0:
if active_roster['SP'] < 5:
if 5 - active_roster['SP'] <= combo_pitchers:
delta = 5 - active_roster['SP']
else:
delta = combo_pitchers
active_roster['SP'] += delta
combo_pitchers -= delta
if combo_pitchers > 0:
active_roster['RP'] += combo_pitchers
short_il = Player.select_season(self.season).join(Team).where(Player.team.abbrev == f'{self.abbrev}IL')
short_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []}
combo_pitchers = 0
for guy in short_il:
short_roster['WARa'] += guy.wara
short_roster['players'].append(guy)
guy_pos = guy.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
for pos in guy_pos:
short_roster[pos] += 1
if combo_pitchers > 0:
if short_roster['SP'] < 5:
if 5 - short_roster['SP'] <= combo_pitchers:
delta = 5 - short_roster['SP']
else:
delta = combo_pitchers
short_roster['SP'] += delta
combo_pitchers -= delta
if combo_pitchers > 0:
short_roster['RP'] += combo_pitchers
long_il = Player.select_season(self.season).join(Team).where(Player.team.abbrev == f'{self.abbrev}MiL')
long_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []}
combo_pitchers = 0
for guy in long_il:
long_roster['WARa'] += guy.wara
long_roster['players'].append(guy)
guy_pos = guy.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
for pos in guy_pos:
long_roster[pos] += 1
if combo_pitchers > 0:
if long_roster['SP'] < 5:
if 5 - long_roster['SP'] <= combo_pitchers:
delta = 5 - long_roster['SP']
else:
delta = combo_pitchers
long_roster['SP'] += delta
combo_pitchers -= delta
if combo_pitchers > 0:
long_roster['RP'] += combo_pitchers
return {'active': active_roster, 'shortil': short_roster, 'longil': long_roster}
def get_next_week(self):
current = Current.latest()
active_team = Player.select_season(current.season).where(Player.team == self)
active_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []}
combo_pitchers = 0
for guy in active_team:
active_roster['WARa'] += guy.wara
active_roster['players'].append(guy)
guy_pos = guy.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
for pos in guy_pos:
active_roster[pos] += 1
all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == self) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0)
)
all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == self) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0)
)
for move in all_drops:
guy_pos = move.player.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers -= 1
else:
for pos in guy_pos:
active_roster[pos] -= 1
# print(f'dropping {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa')
active_roster['WARa'] -= move.player.wara
try:
active_roster['players'].remove(move.player)
except:
print(f'I could not drop {move.player.name}')
for move in all_adds:
guy_pos = move.player.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
for pos in guy_pos:
active_roster[pos] += 1
# print(f'adding {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa')
active_roster['WARa'] += move.player.wara
active_roster['players'].append(move.player)
if combo_pitchers > 0:
if active_roster['SP'] < 5:
if 5 - active_roster['SP'] <= combo_pitchers:
delta = 5 - active_roster['SP']
else:
delta = combo_pitchers
active_roster['SP'] += delta
combo_pitchers -= delta
if combo_pitchers > 0:
active_roster['RP'] += combo_pitchers
short_il = Player.select_season(current.season).join(Team).where(Player.team.abbrev == f'{self.abbrev}SIL')
short_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []}
combo_pitchers = 0
for guy in short_il:
short_roster['WARa'] += guy.wara
short_roster['players'].append(guy)
guy_pos = guy.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
for pos in guy_pos:
short_roster[pos] += 1
sil_team = Team.get_season(f'{self.abbrev}SIL', current.season)
all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == sil_team) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0)
)
all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == sil_team) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0)
)
for move in all_drops:
guy_pos = move.player.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers -= 1
else:
for pos in guy_pos:
short_roster[pos] -= 1
short_roster['WARa'] -= move.player.wara
# print(f'SIL dropping {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa')
try:
short_roster['players'].remove(move.player)
except:
print(f'I could not drop {move.player.name}')
for move in all_adds:
guy_pos = move.player.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
for pos in guy_pos:
short_roster[pos] += 1
# print(f'SIL adding {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa')
short_roster['WARa'] += move.player.wara
short_roster['players'].append(move.player)
if combo_pitchers > 0:
if short_roster['SP'] < 5:
if 5 - short_roster['SP'] <= combo_pitchers:
delta = 5 - short_roster['SP']
else:
delta = combo_pitchers
short_roster['SP'] += delta
combo_pitchers -= delta
if combo_pitchers > 0:
short_roster['RP'] += combo_pitchers
long_il = Player.select_season(current.season).join(Team).where(Player.team.abbrev == f'{self.abbrev}MiL')
long_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []}
combo_pitchers = 0
for guy in long_il:
long_roster['WARa'] += guy.wara
long_roster['players'].append(guy)
guy_pos = guy.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
for pos in guy_pos:
long_roster[pos] += 1
lil_team = Team.get_season(f'{self.abbrev}LIL', current.season)
all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == lil_team) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0)
)
all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == lil_team) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0)
)
for move in all_drops:
guy_pos = move.player.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers -= 1
else:
for pos in guy_pos:
long_roster[pos] -= 1
long_roster['WARa'] -= move.player.wara
# print(f'LIL dropping {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa')
try:
long_roster['players'].remove(move.player)
except:
print(f'I could not drop {move.player.name}')
for move in all_adds:
guy_pos = move.player.get_positions()
if 'SP' in guy_pos and 'RP' in guy_pos:
combo_pitchers += 1
else:
for pos in guy_pos:
long_roster[pos] += 1
# print(f'LIL adding {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa')
long_roster['WARa'] += move.player.wara
long_roster['players'].append(move.player)
if combo_pitchers > 0:
if long_roster['SP'] < 5:
if 5 - long_roster['SP'] <= combo_pitchers:
delta = 5 - long_roster['SP']
else:
delta = combo_pitchers
long_roster['SP'] += delta
combo_pitchers -= delta
if combo_pitchers > 0:
long_roster['RP'] += combo_pitchers
return {'active': active_roster, 'shortil': short_roster, 'longil': long_roster}
def run_pythag_last8(self):
team_stan = Standings.get_or_none(Standings.team == self)
runs_scored, runs_allowed = 0, 0
away_games = StratGame.select(
fn.SUM(StratGame.away_score).alias('r_scored'),
fn.SUM(StratGame.home_score).alias('r_allowed')
).where((StratGame.away_team == self) & StratGame.game_num.is_null(False))
if away_games.count() > 0:
runs_scored += away_games[0].r_scored
runs_allowed += away_games[0].r_allowed
home_games = StratGame.select(
fn.SUM(StratGame.home_score).alias('r_scored'),
fn.SUM(StratGame.away_score).alias('r_allowed')
).where((StratGame.home_team == self) & StratGame.game_num.is_null(False))
if home_games.count() > 0:
runs_scored += home_games[0].r_scored
runs_allowed += home_games[0].r_allowed
if runs_allowed == 0:
pythag_win_pct = 1
elif runs_scored == 0:
pythag_win_pct = 0
else:
pythag_win_pct = runs_scored ** 1.83 / ((runs_scored ** 1.83) + (runs_allowed ** 1.83))
games_played = team_stan.wins + team_stan.losses
team_stan.pythag_wins = round(games_played * pythag_win_pct)
team_stan.pythag_losses = games_played - team_stan.pythag_wins
last_games = StratGame.select().where(
((StratGame.home_team == self) | (StratGame.away_team == self)) & (StratGame.game_num.is_null(False))
).order_by(
-StratGame.season, -StratGame.week, -StratGame.game_num
).limit(8)
for game in last_games:
if game.home_score > game.away_score:
if game.home_team == self:
team_stan.last8_wins += 1
else:
team_stan.last8_losses += 1
else:
if game.home_team == self:
team_stan.last8_losses += 1
else:
team_stan.last8_wins += 1
return team_stan.save()
class Result(BaseModel):
week = IntegerField()
game = IntegerField()
awayteam = ForeignKeyField(Team)
hometeam = ForeignKeyField(Team)
awayscore = IntegerField()
homescore = IntegerField()
season = IntegerField()
scorecard_url = CharField(null=True)
@staticmethod
def regular_season(num):
if num == 1:
return Result.select().where((Result.season == 1) & (Result.week < 21))
elif num == 2:
return Result.select().where((Result.season == 2) & (Result.week < 19))
elif num == 3 or num == 4:
return Result.select().where((Result.season == num) & (Result.week < 23))
else:
return None
@staticmethod
def post_season(num):
if num == 1:
return Result.select().where((Result.season == 1) & (Result.week >= 21))
elif num == 2:
return Result.select().where((Result.season == 2) & (Result.week >= 19))
elif num == 3 or num == 4:
return Result.select().where((Result.season == num) & (Result.week >= 23))
else:
return None
@staticmethod
def select_season(num):
return Result.select().where(Result.season == num)
# def update_standings(self):
# away_stan = Standings.get_season(self.awayteam)
# home_stan = Standings.get_season(self.hometeam)
# away_div = Division.get_by_id(self.awayteam.division.id)
# home_div = Division.get_by_id(self.hometeam.division.id)
#
# if self.homescore > self.awayscore:
# # - generic w/l & home/away w/l
# home_stan.wins += 1
# home_stan.home_wins += 1
# away_stan.losses += 1
# away_stan.away_losses += 1
#
# # - update streak wl and num
# if home_stan.streak_wl == 'w':
# home_stan.streak_num += 1
# else:
# home_stan.streak_wl = 'w'
# home_stan.streak_num = 1
#
# if away_stan.streak_wl == 'l':
# away_stan.streak_num += 1
# else:
# away_stan.streak_wl = 'l'
# away_stan.streak_num = 1
#
# # - if 1-run, tally accordingly
# if self.homescore == self.awayscore + 1:
# home_stan.one_run_wins += 1
# away_stan.one_run_losses += 1
#
# # Used for one league with 3 divisions
# # - update record v division
# # if away_div.division_abbrev == 'BE':
# # home_stan.div1_wins += 1
# # elif away_div.division_abbrev == 'DO':
# # home_stan.div2_wins += 1
# # else:
# # home_stan.div3_wins += 1
# #
# # if home_div.division_abbrev == 'BE':
# # away_stan.div1_losses += 1
# # elif home_div.division_abbrev == 'DO':
# # away_stan.div2_losses += 1
# # else:
# # away_stan.div3_losses += 1
#
# # Used for two league plus divisions
# if away_div.league_abbrev == 'AL':
# if away_div.division_abbrev == 'E':
# home_stan.div1_wins += 1
# else:
# home_stan.div2_wins += 1
# else:
# if away_div.division_abbrev == 'E':
# home_stan.div3_wins += 1
# else:
# home_stan.div4_wins += 1
#
# if home_div.league_abbrev == 'AL':
# if home_div.division_abbrev == 'E':
# away_stan.div1_losses += 1
# else:
# away_stan.div2_losses += 1
# else:
# if home_div.division_abbrev == 'E':
# away_stan.div3_losses += 1
# else:
# away_stan.div4_losses += 1
#
# # - adjust run_diff
# home_stan.run_diff += self.homescore - self.awayscore
# away_stan.run_diff -= self.homescore - self.awayscore
# else:
# # - generic w/l & home/away w/l
# home_stan.losses += 1
# home_stan.home_losses += 1
# away_stan.wins += 1
# away_stan.away_wins += 1
#
# # - update streak wl and num
# if home_stan.streak_wl == 'l':
# home_stan.streak_num += 1
# else:
# home_stan.streak_wl = 'l'
# home_stan.streak_num = 1
#
# if away_stan.streak_wl == 'w':
# away_stan.streak_num += 1
# else:
# away_stan.streak_wl = 'w'
# away_stan.streak_num = 1
#
# # - if 1-run, tally accordingly
# if self.awayscore == self.homescore + 1:
# home_stan.one_run_losses += 1
# away_stan.one_run_wins += 1
#
# # Used for one league with 3 divisions
# # - update record v division
# # if away_div.division_abbrev == 'BE':
# # home_stan.div1_losses += 1
# # elif away_div.division_abbrev == 'DO':
# # home_stan.div2_losses += 1
# # else:
# # home_stan.div3_losses += 1
# #
# # if home_div.division_abbrev == 'BE':
# # away_stan.div1_wins += 1
# # elif home_div.division_abbrev == 'DO':
# # away_stan.div2_wins += 1
# # else:
# # away_stan.div3_wins += 1
#
# # Used for two league plus divisions
# if away_div.league_abbrev == 'AL':
# if away_div.division_abbrev == 'E':
# home_stan.div1_losses += 1
# else:
# home_stan.div2_losses += 1
# else:
# if away_div.division_abbrev == 'E':
# home_stan.div3_losses += 1
# else:
# home_stan.div4_losses += 1
#
# if home_div.league_abbrev == 'AL':
# if home_div.division_abbrev == 'E':
# away_stan.div1_wins += 1
# else:
# away_stan.div2_wins += 1
# else:
# if home_div.division_abbrev == 'E':
# away_stan.div3_wins += 1
# else:
# away_stan.div4_wins += 1
#
# # - adjust run_diff
# home_stan.run_diff -= self.awayscore - self.homescore
# away_stan.run_diff += self.awayscore - self.homescore
#
# home_stan.save()
# away_stan.save()
class SbaPlayer(BaseModel):
first_name = CharField()
last_name = CharField()
key_fangraphs = IntegerField(null=True)
key_bbref = CharField(null=True)
key_retro = CharField(null=True)
key_mlbam = IntegerField(null=True)
class Player(BaseModel):
name = CharField(max_length=500)
wara = FloatField()
image = CharField(max_length=1000)
image2 = CharField(max_length=1000, null=True)
team = ForeignKeyField(Team)
season = IntegerField()
pitcher_injury = IntegerField(null=True)
pos_1 = CharField(max_length=5)
pos_2 = CharField(max_length=5, null=True)
pos_3 = CharField(max_length=5, null=True)
pos_4 = CharField(max_length=5, null=True)
pos_5 = CharField(max_length=5, null=True)
pos_6 = CharField(max_length=5, null=True)
pos_7 = CharField(max_length=5, null=True)
pos_8 = CharField(max_length=5, null=True)
last_game = CharField(max_length=20, null=True)
last_game2 = CharField(max_length=20, null=True)
il_return = CharField(max_length=20, null=True)
demotion_week = IntegerField(null=True)
headshot = CharField(max_length=500, null=True)
vanity_card = CharField(max_length=500, null=True)
strat_code = CharField(max_length=100, null=True)
bbref_id = CharField(max_length=50, null=True)
injury_rating = CharField(max_length=50, null=True)
sbaplayer = ForeignKeyField(SbaPlayer, null=True)
@staticmethod
def select_season(num):
return Player.select().where(Player.season == num)
@staticmethod
def get_season(name, num):
player = None
try:
player = Player.get(fn.Lower(Player.name) == name.lower(), Player.season == num)
except Exception as e:
print(f'**Error** (db_engine player): {e}')
finally:
return player
def get_positions(self):
"""
Params: None
Return: List of positions (ex ['1b', '3b'])
"""
pos_list = []
if self.pos_1:
pos_list.append(self.pos_1)
if self.pos_2:
pos_list.append(self.pos_2)
if self.pos_3:
pos_list.append(self.pos_3)
if self.pos_4:
pos_list.append(self.pos_4)
if self.pos_5:
pos_list.append(self.pos_5)
if self.pos_6:
pos_list.append(self.pos_6)
if self.pos_7:
pos_list.append(self.pos_7)
if self.pos_8:
pos_list.append(self.pos_8)
return pos_list
class Schedule(BaseModel):
week = IntegerField()
awayteam = ForeignKeyField(Team)
hometeam = ForeignKeyField(Team)
gamecount = IntegerField()
season = IntegerField()
@staticmethod
def select_season(season):
return Schedule.select().where(Schedule.season == season)
class Transaction(BaseModel):
week = IntegerField()
player = ForeignKeyField(Player)
oldteam = ForeignKeyField(Team)
newteam = ForeignKeyField(Team)
season = IntegerField()
moveid = CharField(max_length=50)
cancelled = BooleanField(default=False)
frozen = BooleanField(default=False)
@staticmethod
def select_season(num):
return Transaction.select().where(Transaction.season == num)
class BattingStat(BaseModel):
player = ForeignKeyField(Player)
team = ForeignKeyField(Team)
pos = CharField()
pa = IntegerField()
ab = IntegerField()
run = IntegerField()
hit = IntegerField()
rbi = IntegerField()
double = IntegerField()
triple = IntegerField()
hr = IntegerField()
bb = IntegerField()
so = IntegerField()
hbp = IntegerField()
sac = IntegerField()
ibb = IntegerField()
gidp = IntegerField()
sb = IntegerField()
cs = IntegerField()
bphr = IntegerField()
bpfo = IntegerField()
bp1b = IntegerField()
bplo = IntegerField()
xba = IntegerField()
xbt = IntegerField()
xch = IntegerField()
xhit = IntegerField()
error = IntegerField()
pb = IntegerField()
sbc = IntegerField()
csc = IntegerField()
roba = IntegerField()
robs = IntegerField()
raa = IntegerField()
rto = IntegerField()
week = IntegerField()
game = IntegerField()
season = IntegerField()
@staticmethod
def combined_season(season):
"""
Params: season, integer (season number), optional
Return: ModelSelect object for <num> season
"""
return BattingStat.select().where(BattingStat.season == season)
@staticmethod
def regular_season(season):
"""
Params: num, integer (season number)
Return: ModelSelect object for <num> season's regular season
"""
if season == 1:
return BattingStat.select().where((BattingStat.season == 1) & (BattingStat.week < 21))\
.order_by(BattingStat.week)
elif season == 2:
return BattingStat.select().where((BattingStat.season == 2) & (BattingStat.week < 19))\
.order_by(BattingStat.week)
elif season > 2:
return BattingStat.select().where((BattingStat.season == season) & (BattingStat.week < 23))\
.order_by(BattingStat.week)
else:
return None
@staticmethod
def post_season(season):
"""
Params: num, integer (season number)
Return: ModelSelect object for <num> season's post season
"""
if season == 1:
return BattingStat.select().where((BattingStat.season == 1) & (BattingStat.week >= 21))
elif season == 2:
return BattingStat.select().where((BattingStat.season == 2) & (BattingStat.week >= 19))
elif season > 2:
return BattingStat.select().where((BattingStat.season == season) & (BattingStat.week >= 23))
else:
return None
@staticmethod
def team_season(team, season):
b_stats = BattingStat.regular_season(season).join(Player).select(
fn.SUM(BattingStat.pa).alias('pas'),
fn.SUM(BattingStat.ab).alias('abs'),
fn.SUM(BattingStat.run).alias('runs'),
fn.SUM(BattingStat.hit).alias('hits'),
fn.SUM(BattingStat.rbi).alias('rbis'),
fn.SUM(BattingStat.double).alias('doubles'),
fn.SUM(BattingStat.triple).alias('triples'),
fn.SUM(BattingStat.hr).alias('hrs'),
fn.SUM(BattingStat.bb).alias('bbs'),
fn.SUM(BattingStat.so).alias('sos'),
fn.SUM(BattingStat.hbp).alias('hbps'),
fn.SUM(BattingStat.sac).alias('sacs'),
fn.SUM(BattingStat.ibb).alias('ibbs'),
fn.SUM(BattingStat.gidp).alias('gidps'),
fn.SUM(BattingStat.sb).alias('sbs'),
fn.SUM(BattingStat.cs).alias('css'),
fn.SUM(BattingStat.bphr).alias('bphr'),
fn.SUM(BattingStat.bpfo).alias('bpfo'),
fn.SUM(BattingStat.bp1b).alias('bp1b'),
fn.SUM(BattingStat.bplo).alias('bplo'),
# fn.SUM(BattingStat.xba).alias('xba'),
# fn.SUM(BattingStat.xbt).alias('xbt'),
fn.COUNT(BattingStat.game).alias('games'),
).where(BattingStat.team == team)
total = {
'game': b_stats[0].games if b_stats[0].games else 0,
'pa': b_stats[0].pas if b_stats[0].pas else 0,
'ab': b_stats[0].abs if b_stats[0].abs else 0,
'run': b_stats[0].runs if b_stats[0].runs else 0,
'hit': b_stats[0].hits if b_stats[0].hits else 0,
'rbi': b_stats[0].rbis if b_stats[0].rbis else 0,
'double': b_stats[0].doubles if b_stats[0].doubles else 0,
'triple': b_stats[0].triples if b_stats[0].triples else 0,
'hr': b_stats[0].hrs if b_stats[0].hrs else 0,
'bb': b_stats[0].bbs if b_stats[0].bbs else 0,
'so': b_stats[0].sos if b_stats[0].sos else 0,
'hbp': b_stats[0].hbps if b_stats[0].hbps else 0,
'sac': b_stats[0].sacs if b_stats[0].sacs else 0,
'ibb': b_stats[0].ibbs if b_stats[0].ibbs else 0,
'gidp': b_stats[0].gidps if b_stats[0].gidps else 0,
'sb': b_stats[0].sbs if b_stats[0].sbs else 0,
'cs': b_stats[0].css if b_stats[0].css else 0,
'ba': 0,
'obp': 0,
'slg': 0,
'woba': 0,
'kpct': 0,
'bphr': b_stats[0].bphr if b_stats[0].bphr else 0,
'bpfo': b_stats[0].bpfo if b_stats[0].bpfo else 0,
'bp1b': b_stats[0].bp1b if b_stats[0].bp1b else 0,
'bplo': b_stats[0].bplo if b_stats[0].bplo else 0,
# 'xba': b_stats[0].xba if b_stats[0].xba else 0,
# 'xbt': b_stats[0].xbt if b_stats[0].xbt else 0,
}
if b_stats[0].abs:
total['ba'] = b_stats[0].hits / b_stats[0].abs
total['obp'] = (
(b_stats[0].bbs + b_stats[0].hits + b_stats[0].hbps + b_stats[0].ibbs) / b_stats[0].pas
)
total['slg'] = (
((b_stats[0].hrs * 4) + (b_stats[0].triples * 3) + (b_stats[0].doubles * 2) +
(b_stats[0].hits - b_stats[0].hrs - b_stats[0].triples - b_stats[0].doubles)) / b_stats[0].abs
)
total['woba'] = (
((b_stats[0].bbs * .69) + (b_stats[0].hbps * .722) + (b_stats[0].doubles * 1.271) +
(b_stats[0].triples * 1.616) + (b_stats[0].hrs * 2.101) +
((b_stats[0].hits - b_stats[0].hrs - b_stats[0].triples - b_stats[0].doubles) * .888)) /
(b_stats[0].pas - b_stats[0].ibbs)
)
total['kpct'] = (total['so'] * 100) / total['ab']
total_innings = PitchingStat.regular_season(season).join(Player).select(
fn.SUM(PitchingStat.ip).alias('ips'),
).where(PitchingStat.player.team == team)
total['rper9'] = (total['run'] * 9) / total_innings[0].ips
return total
@staticmethod
def team_fielding_season(team, season):
f_stats = BattingStat.regular_season(season).select(
fn.SUM(BattingStat.xch).alias('xchs'),
fn.SUM(BattingStat.xhit).alias('xhits'),
fn.SUM(BattingStat.error).alias('errors'),
# fn.SUM(BattingStat.roba).alias('roba'),
# fn.SUM(BattingStat.robs).alias('robs'),
# fn.SUM(BattingStat.raa).alias('raa'),
# fn.SUM(BattingStat.rto).alias('rto'),
fn.SUM(BattingStat.pb).alias('pbs'),
fn.SUM(BattingStat.sbc).alias('sbas'),
fn.SUM(BattingStat.csc).alias('cscs'),
fn.COUNT(BattingStat.game).alias('games'),
).where(BattingStat.team == team)
total = {
'game': f_stats[0].games if f_stats[0].games else 0,
'xch': f_stats[0].xchs if f_stats[0].xchs else 0,
'xhit': f_stats[0].xhits if f_stats[0].xhits else 0,
'error': f_stats[0].errors if f_stats[0].errors else 0,
# 'roba': f_stats[0].roba if f_stats[0].roba else 0,
# 'robs': f_stats[0].robs if f_stats[0].robs else 0,
# 'raa': f_stats[0].raa if f_stats[0].raa else 0,
# 'rto': f_stats[0].rto if f_stats[0].rto else 0,
'pb': f_stats[0].pbs if f_stats[0].pbs else 0,
'sbc': f_stats[0].sbas if f_stats[0].sbas else 0,
'csc': f_stats[0].cscs if f_stats[0].cscs else 0,
'wfpct': 0,
'cspct': 0,
}
if total['xch'] > 0:
total['wfpct'] = (total['xch'] - (total['error'] * .5) - (total['xhit'] * .75)) / (total['xch'])
if total['sbc'] > 0:
total['cspct'] = (total['csc'] / total['sbc']) * 100
return total
class PitchingStat(BaseModel):
player = ForeignKeyField(Player)
team = ForeignKeyField(Team)
ip = FloatField()
hit = FloatField()
run = FloatField()
erun = FloatField()
so = FloatField()
bb = FloatField()
hbp = FloatField()
wp = FloatField()
balk = FloatField()
hr = FloatField()
ir = FloatField()
irs = FloatField()
gs = FloatField()
win = FloatField()
loss = FloatField()
hold = FloatField()
sv = FloatField()
bsv = FloatField()
week = IntegerField()
game = IntegerField()
season = IntegerField()
@staticmethod
def select_season(season):
return PitchingStat.select().where(PitchingStat.season == season)
@staticmethod
def regular_season(season):
if season == 1:
return PitchingStat.select().where((PitchingStat.season == 1) & (PitchingStat.week < 21))\
.order_by(PitchingStat.week)
elif season == 2:
return PitchingStat.select().where((PitchingStat.season == 2) & (PitchingStat.week < 19))\
.order_by(PitchingStat.week)
elif season > 2:
return PitchingStat.select().where((PitchingStat.season == season) & (PitchingStat.week < 23))\
.order_by(PitchingStat.week)
else:
return None
@staticmethod
def post_season(season):
if season == 1:
return PitchingStat.select().where((PitchingStat.season == 1) & (PitchingStat.week >= 21))\
.order_by(PitchingStat.week)
elif season == 2:
return PitchingStat.select().where((PitchingStat.season == 2) & (PitchingStat.week >= 19))\
.order_by(PitchingStat.week)
elif season > 2:
return PitchingStat.select().where((PitchingStat.season == season) & (PitchingStat.week >= 23))\
.order_by(PitchingStat.week)
else:
return None
@staticmethod
def team_season(team, season):
p_stats = PitchingStat.regular_season(season).select(
fn.SUM(PitchingStat.ip).alias('ips'),
fn.SUM(PitchingStat.hit).alias('hits'),
fn.SUM(PitchingStat.run).alias('runs'),
fn.SUM(PitchingStat.erun).alias('eruns'),
fn.SUM(PitchingStat.so).alias('sos'),
fn.SUM(PitchingStat.bb).alias('bbs'),
fn.SUM(PitchingStat.hbp).alias('hbps'),
fn.SUM(PitchingStat.wp).alias('wps'),
fn.SUM(PitchingStat.ir).alias('ir'),
fn.SUM(PitchingStat.irs).alias('irs'),
fn.SUM(PitchingStat.balk).alias('balks'),
fn.SUM(PitchingStat.hr).alias('hrs'),
fn.COUNT(PitchingStat.game).alias('games'),
fn.SUM(PitchingStat.gs).alias('gss'),
fn.SUM(PitchingStat.win).alias('wins'),
fn.SUM(PitchingStat.loss).alias('losses'),
fn.SUM(PitchingStat.hold).alias('holds'),
fn.SUM(PitchingStat.sv).alias('saves'),
fn.SUM(PitchingStat.bsv).alias('bsaves'),
).where(PitchingStat.team == team)
total = {
'ip': p_stats[0].ips if p_stats[0].ips else 0,
'hit': int(p_stats[0].hits) if p_stats[0].hits else 0,
'run': int(p_stats[0].runs) if p_stats[0].runs else 0,
'erun': int(p_stats[0].eruns) if p_stats[0].eruns else 0,
'so': int(p_stats[0].sos) if p_stats[0].sos else 0,
'bb': int(p_stats[0].bbs) if p_stats[0].bbs else 0,
'hbp': int(p_stats[0].hbps) if p_stats[0].hbps else 0,
'wp': int(p_stats[0].wps) if p_stats[0].wps else 0,
'balk': int(p_stats[0].balks) if p_stats[0].balks else 0,
'hr': int(p_stats[0].hrs) if p_stats[0].hrs else 0,
'game': int(p_stats[0].games) if p_stats[0].games else 0,
'gs': int(p_stats[0].gss) if p_stats[0].gss else 0,
'win': int(p_stats[0].wins) if p_stats[0].wins else 0,
'loss': int(p_stats[0].losses) if p_stats[0].losses else 0,
'hold': int(p_stats[0].holds) if p_stats[0].holds else 0,
'sv': int(p_stats[0].saves) if p_stats[0].saves else 0,
'bsv': int(p_stats[0].bsaves) if p_stats[0].bsaves else 0,
'wl%': 0,
'era': 0,
'whip': 0,
'ir': int(p_stats[0].ir) if p_stats[0].ir else 0,
'irs': int(p_stats[0].irs) if p_stats[0].irs else 0,
}
if total['ip']:
total['era'] = (total['erun'] * 9) / total['ip']
total['whip'] = (total['bb'] + total['hit']) / total['ip']
if total['win'] + total['loss'] > 0:
total['wl%'] = total['win'] / (total['win'] + total['loss'])
return total
class Standings(BaseModel):
team = ForeignKeyField(Team)
wins = IntegerField(default=0)
losses = IntegerField(default=0)
run_diff = IntegerField(default=0)
div_gb = FloatField(default=0.0, null=True)
div_e_num = IntegerField(default=0, null=True)
wc_gb = FloatField(default=99.0, null=True)
wc_e_num = IntegerField(default=99, null=True)
home_wins = IntegerField(default=0)
home_losses = IntegerField(default=0)
away_wins = IntegerField(default=0)
away_losses = IntegerField(default=0)
last8_wins = IntegerField(default=0)
last8_losses = IntegerField(default=0)
streak_wl = CharField(default='w')
streak_num = IntegerField(default=0)
one_run_wins = IntegerField(default=0)
one_run_losses = IntegerField(default=0)
pythag_wins = IntegerField(default=0)
pythag_losses = IntegerField(default=0)
div1_wins = IntegerField(default=0)
div1_losses = IntegerField(default=0)
div2_wins = IntegerField(default=0)
div2_losses = IntegerField(default=0)
div3_wins = IntegerField(default=0)
div3_losses = IntegerField(default=0)
div4_wins = IntegerField(default=0)
div4_losses = IntegerField(default=0)
@staticmethod
def select_season(season):
return Standings.select().join(Team).where(Standings.team.season == season)
@staticmethod
def get_season(team):
return Standings.get_or_none(Standings.team == team)
@staticmethod
def recalculate(season, full_wipe=True):
all_teams = Team.select_season(season).where(Team.division.is_null(False))
if full_wipe:
# Wipe existing data
s_teams = Team.select().where(Team.season == season)
Standings.delete().where(Standings.team << s_teams).execute()
# delete_lines = Standings.select_season(season)
# for line in delete_lines:
# line.delete_instance()
# Recreate current season Standings objects
create_teams = [Standings(team=team) for team in all_teams]
with db.atomic():
Standings.bulk_create(create_teams)
# Iterate through each individual result
# for game in Result.select_season(season).where(Result.week <= 22):
for game in StratGame.select().where(
(StratGame.season == season) & (StratGame.week <= 18) & (StratGame.game_num.is_null(False))
).order_by(StratGame.week, StratGame.game_num):
# tally win and loss for each standings object
game.update_standings()
# Set pythag record and iterate through last 8 games for last8 record
for team in all_teams:
team.run_pythag_last8()
# Pull each division at a time and sort by win pct
for division in Division.select().where(Division.season == season):
division.sort_division(season)
# Pull each league (filter by not null wc_gb) and sort by win pct
# # For one league:
Division.sort_wildcard(season, 'SBa')
# For two leagues
# Division.sort_wildcard(season, 'AL')
# Division.sort_wildcard(season, 'NL')
class BattingCareer(BaseModel):
name = CharField()
pa = FloatField(default=0)
ab = FloatField(default=0)
run = FloatField(default=0)
hit = FloatField(default=0)
rbi = FloatField(default=0)
double = FloatField(default=0)
triple = FloatField(default=0)
hr = FloatField(default=0)
bb = FloatField(default=0)
so = FloatField(default=0)
hbp = FloatField(default=0)
sac = FloatField(default=0)
ibb = FloatField(default=0)
gidp = FloatField(default=0)
sb = FloatField(default=0)
cs = FloatField(default=0)
bphr = FloatField(default=0)
bpfo = FloatField(default=0)
bp1b = FloatField(default=0)
bplo = FloatField(default=0)
xba = FloatField(default=0)
xbt = FloatField(default=0)
game = FloatField(default=0)
@staticmethod
def recalculate():
# Wipe existing data
delete_lines = BattingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment
for this_season in BattingSeason.select().where(BattingSeason.season_type == 'Regular'):
this_career = BattingCareer.get_or_none(BattingCareer.name == this_season.player.name)
if not this_career:
this_career = BattingCareer(name=this_season.player.name)
this_career.save()
this_career.pa += this_season.pa
this_career.ab += this_season.ab
this_career.run += this_season.run
this_career.hit += this_season.hit
this_career.rbi += this_season.rbi
this_career.double += this_season.double
this_career.triple += this_season.triple
this_career.hr += this_season.hr
this_career.bb += this_season.bb
this_career.so += this_season.so
this_career.hbp += this_season.hbp
this_career.sac += this_season.sac
this_career.ibb += this_season.ibb
this_career.gidp += this_season.gidp
this_career.sb += this_season.sb
this_career.cs += this_season.cs
this_career.bphr += this_season.bphr
this_career.bpfo += this_season.bpfo
this_career.bp1b += this_season.bp1b
this_career.bplo += this_season.bplo
this_career.xba += this_season.xba
this_career.xbt += this_season.xbt
this_career.save()
class PitchingCareer(BaseModel):
name = CharField()
ip = FloatField(default=0)
hit = FloatField(default=0)
run = FloatField(default=0)
erun = FloatField(default=0)
so = FloatField(default=0)
bb = FloatField(default=0)
hbp = FloatField(default=0)
wp = FloatField(default=0)
balk = FloatField(default=0)
hr = FloatField(default=0)
ir = FloatField(default=0)
irs = FloatField(default=0)
gs = FloatField(default=0)
win = FloatField(default=0)
loss = FloatField(default=0)
hold = FloatField(default=0)
sv = FloatField(default=0)
bsv = FloatField(default=0)
game = FloatField(default=0)
@staticmethod
def recalculate():
# Wipe existing data
delete_lines = PitchingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment
for this_season in PitchingSeason.select().where(PitchingSeason.season_type == 'Regular'):
this_career = PitchingCareer.get_or_none(PitchingCareer.name == this_season.player.name)
if not this_career:
this_career = PitchingCareer(name=this_season.player.name)
this_career.save()
this_career.ip += this_season.ip
this_career.hit += this_season.hit
this_career.run += this_season.run
this_career.erun += this_season.erun
this_career.so += this_season.so
this_career.bb += this_season.bb
this_career.hbp += this_season.hbp
this_career.wp += this_season.wp
this_career.balk += this_season.balk
this_career.hr += this_season.hr
this_career.ir += this_season.ir
this_career.irs += this_season.irs
this_career.gs += this_season.gs
this_career.win += this_season.win
this_career.loss += this_season.loss
this_career.hold += this_season.hold
this_career.sv += this_season.sv
this_career.bsv += this_season.bsv
this_career.save()
class FieldingCareer(BaseModel):
name = CharField()
pos = CharField()
xch = IntegerField(default=0)
xhit = IntegerField(default=0)
error = IntegerField(default=0)
pb = IntegerField(default=0)
sbc = IntegerField(default=0)
csc = IntegerField(default=0)
roba = IntegerField(default=0)
robs = IntegerField(default=0)
raa = IntegerField(default=0)
rto = IntegerField(default=0)
game = IntegerField(default=0)
@staticmethod
def recalculate():
# Wipe existing data
delete_lines = FieldingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment
for this_season in FieldingSeason.select().where(FieldingSeason.season_type == 'Regular'):
this_career = FieldingCareer.get_or_none(
FieldingCareer.name == this_season.player.name, FieldingCareer.pos == this_season.pos
)
if not this_career:
this_career = FieldingCareer(name=this_season.player.name, pos=this_season.pos)
this_career.save()
this_career.xch += this_season.xch
this_career.xhit += this_season.xhit
this_career.error += this_season.error
this_career.pb += this_season.pb
this_career.sbc += this_season.sbc
this_career.csc += this_season.csc
this_career.roba += this_season.roba
this_career.robs += this_season.robs
this_career.raa += this_season.raa
this_career.rto += this_season.rto
this_career.save()
class BattingSeason(BaseModel):
player = ForeignKeyField(Player)
season = IntegerField()
season_type = CharField(default='Regular')
career = ForeignKeyField(BattingCareer, null=True)
pa = FloatField(default=0)
ab = FloatField(default=0)
run = FloatField(default=0)
hit = FloatField(default=0)
rbi = FloatField(default=0)
double = FloatField(default=0)
triple = FloatField(default=0)
hr = FloatField(default=0)
bb = FloatField(default=0)
so = FloatField(default=0)
hbp = FloatField(default=0)
sac = FloatField(default=0)
ibb = FloatField(default=0)
gidp = FloatField(default=0)
sb = FloatField(default=0)
cs = FloatField(default=0)
bphr = FloatField(default=0)
bpfo = FloatField(default=0)
bp1b = FloatField(default=0)
bplo = FloatField(default=0)
xba = FloatField(default=0)
xbt = FloatField(default=0)
game = FloatField(default=0)
@staticmethod
def select_season(season):
return BattingSeason.select().where(BattingSeason.season == season)
# @staticmethod
# def recalculate(season, manager_id):
# # Wipe existing data
# delete_lines = BattingSeason.select_season(season)
# for line in delete_lines:
# line.delete_instance()
#
# # For each battingstat, find season or create new and increment
# for line in BattingStat.select().where(
# (BattingStat.season == season) & (BattingStat.player.team.manager1 == manager_id)
# ):
# if line.season == 1:
# s_type = 'Regular' if line.week < 21 else 'Post'
# elif line.season == 2:
# s_type = 'Regular' if line.week < 19 else 'Post'
# else:
# s_type = 'Regular' if line.week < 23 else 'Post'
#
# this_season = BattingSeason.get_or_none(player=line.player, season_type=s_type)
# if not this_season:
# this_season = BattingSeason(player=line.player, season_type=s_type, season=line.season)
# this_season.save()
#
# this_season.pa += line.pa
# this_season.ab += line.ab
# this_season.run += line.run
# this_season.hit += line.hit
# this_season.rbi += line.rbi
# this_season.double += line.double
# this_season.triple += line.triple
# this_season.hr += line.hr
# this_season.bb += line.bb
# this_season.so += line.so
# this_season.hbp += line.hbp
# this_season.sac += line.sac
# this_season.ibb += line.ibb
# this_season.gidp += line.gidp
# this_season.sb += line.sb
# this_season.cs += line.cs
# this_season.save()
def recalculate(self):
self.pa = 0
self.ab = 0
self.run = 0
self.hit = 0
self.rbi = 0
self.double = 0
self.triple = 0
self.hr = 0
self.bb = 0
self.so = 0
self.hbp = 0
self.sac = 0
self.ibb = 0
self.gidp = 0
self.sb = 0
self.cs = 0
self.bphr = 0
self.bpfo = 0
self.bp1b = 0
self.bplo = 0
self.xba = 0
self.xbt = 0
self.game = 0
if self.season_type == 'Regular':
all_stats = BattingStat.regular_season(self.season).where(BattingStat.player == self.player)
else:
all_stats = BattingStat.post_season(self.season).where(BattingStat.player == self.player)
for line in all_stats:
self.pa += line.pa
self.ab += line.ab
self.run += line.run
self.hit += line.hit
self.rbi += line.rbi
self.double += line.double
self.triple += line.triple
self.hr += line.hr
self.bb += line.bb
self.so += line.so
self.hbp += line.hbp
self.sac += line.sac
self.ibb += line.ibb
self.gidp += line.gidp
self.sb += line.sb
self.cs += line.cs
self.bphr += line.bphr
self.bpfo += line.bpfo
self.bp1b += line.bp1b
self.bplo += line.bplo
self.xba += line.xba
self.xbt += line.xbt
self.game += 1
self.save()
return all_stats.count()
class PitchingSeason(BaseModel):
player = ForeignKeyField(Player)
season = IntegerField()
season_type = CharField(default='Regular')
career = ForeignKeyField(PitchingCareer, null=True)
ip = FloatField(default=0)
hit = FloatField(default=0)
run = FloatField(default=0)
erun = FloatField(default=0)
so = FloatField(default=0)
bb = FloatField(default=0)
hbp = FloatField(default=0)
wp = FloatField(default=0)
balk = FloatField(default=0)
hr = FloatField(default=0)
ir = FloatField(default=0)
irs = FloatField(default=0)
gs = FloatField(default=0)
win = FloatField(default=0)
loss = FloatField(default=0)
hold = FloatField(default=0)
sv = FloatField(default=0)
bsv = FloatField(default=0)
game = FloatField(default=0)
@staticmethod
def select_season(season):
return PitchingSeason.select().where(PitchingSeason.season == season)
# @staticmethod
# def recalculate(season, manager_id):
# # Wipe existing data
# delete_lines = PitchingSeason.select_season(season)
# for line in delete_lines:
# line.delete_instance()
#
# # For each pitchingstat, find season or create new and increment
# for line in PitchingStat.select().where(
# (PitchingStat.season == season) & (PitchingStat.player.team.manager1 == manager_id)
# ):
# if line.season == 1:
# s_type = 'Regular' if line.week < 21 else 'Post'
# elif line.season == 2:
# s_type = 'Regular' if line.week < 19 else 'Post'
# else:
# s_type = 'Regular' if line.week < 23 else 'Post'
#
# this_season = PitchingSeason.get_or_none(player=line.player, season_type=s_type)
# if not this_season:
# this_season = PitchingSeason(player=line.player, season_type=s_type, season=line.season)
# this_season.save()
#
# this_season.ip += line.ip
# this_season.hit += line.hit
# this_season.run += line.run
# this_season.erun += line.erun
# this_season.so += line.so
# this_season.bb += line.bb
# this_season.hbp += line.hbp
# this_season.wp += line.wp
# this_season.balk += line.balk
# this_season.hr += line.hr
# this_season.gs += line.gs
# this_season.win += line.win
# this_season.loss += line.loss
# this_season.hold += line.hold
# this_season.sv += line.sv
# this_season.bsv += line.bsv
# this_season.game += 1
# this_season.save()
def recalculate(self):
self.ip = 0
self.hit = 0
self.run = 0
self.erun = 0
self.so = 0
self.bb = 0
self.hbp = 0
self.wp = 0
self.balk = 0
self.hr = 0
self.ir = 0
self.irs = 0
self.gs = 0
self.win = 0
self.loss = 0
self.hold = 0
self.sv = 0
self.bsv = 0
self.game = 0
if self.season_type == 'Regular':
all_stats = PitchingStat.regular_season(self.season).where(PitchingStat.player == self.player)
else:
all_stats = PitchingStat.post_season(self.season).where(PitchingStat.player == self.player)
for line in all_stats:
self.ip += line.ip
self.hit += line.hit
self.run += line.run
self.erun += line.erun
self.so += line.so
self.bb += line.bb
self.hbp += line.hbp
self.wp += line.wp
self.balk += line.balk
self.hr += line.hr
self.ir += line.ir
self.irs += line.irs
self.gs += line.gs
self.win += line.win
self.loss += line.loss
self.hold += line.hold
self.sv += line.sv
self.bsv += line.bsv
self.game += 1
self.save()
return all_stats.count()
class FieldingSeason(BaseModel):
player = ForeignKeyField(Player)
season = IntegerField()
season_type = CharField(default='Regular')
pos = CharField()
career = ForeignKeyField(FieldingCareer, null=True)
xch = IntegerField(default=0)
xhit = IntegerField(default=0)
error = IntegerField(default=0)
pb = IntegerField(default=0)
sbc = IntegerField(default=0)
csc = IntegerField(default=0)
roba = IntegerField(default=0)
robs = IntegerField(default=0)
raa = IntegerField(default=0)
rto = IntegerField(default=0)
game = IntegerField(default=0)
@staticmethod
def select_season(season):
return FieldingSeason.select().where(FieldingSeason.season == season)
# @staticmethod
# def recalculate(season, manager_id):
# # Wipe existing data
# delete_lines = FieldingSeason.select()
# for line in delete_lines:
# line.delete_instance()
#
# # players = Player.select_season(season).where(Player.team)
#
# # For each battingstat, find season or create new and increment
# for line in BattingStat.select().join(Player).join(Team).where(
# (BattingStat.season == season) & (BattingStat.player.team.manager1 == manager_id)
# ):
# if line.season == 1:
# s_type = 'Regular' if line.week < 21 else 'Post'
# elif line.season == 2:
# s_type = 'Regular' if line.week < 19 else 'Post'
# else:
# s_type = 'Regular' if line.week < 23 else 'Post'
#
# this_season = BattingSeason.get_or_none(player=line.player, season_type=s_type, pos=line.pos)
# if not this_season:
# this_season = BattingSeason(player=line.player, season_type=s_type, pos=line.pos, season=line.season)
# this_season.save()
#
# this_season.xch += line.xch
# this_season.xhit += line.xhit
# this_season.error += line.error
# this_season.pb += line.pb
# this_season.sbc += line.sbc
# this_season.csc += line.csc
# this_season.game += 1
# this_season.save()
def recalculate(self):
self.xch = 0
self.xhit = 0
self.error = 0
self.pb = 0
self.sbc = 0
self.csc = 0
self.roba = 0
self.robs = 0
self.raa = 0
self.rto = 0
self.game = 0
if self.season_type == 'Regular':
all_stats = BattingStat.regular_season(self.season).where(
(BattingStat.player == self.player) & (BattingStat.pos == self.pos)
)
else:
all_stats = BattingStat.post_season(self.season).where(
(BattingStat.player == self.player) & (BattingStat.pos == self.pos)
)
for line in all_stats:
self.xch += line.xch
self.xhit += line.xhit
self.error += line.error
self.pb += line.pb
self.sbc += line.sbc
self.csc += line.csc
self.roba += line.roba
self.robs += line.robs
self.raa += line.raa
self.rto += line.rto
self.game += 1
self.save()
return all_stats.count()
class DraftPick(BaseModel):
overall = IntegerField(null=True)
round = IntegerField()
origowner = ForeignKeyField(Team)
owner = ForeignKeyField(Team)
season = IntegerField()
player = ForeignKeyField(Player, null=True)
@staticmethod
def select_season(num):
return DraftPick.select().where(DraftPick.season == num)
@staticmethod
def get_season(team, rd, num):
return DraftPick.get(DraftPick.season == num, DraftPick.origowner == team, DraftPick.round == rd)
class DraftData(BaseModel):
currentpick = IntegerField()
timer = BooleanField()
pick_deadline = DateTimeField(null=True)
result_channel = CharField(max_length=20, null=True) # Discord channel ID as string
ping_channel = CharField(max_length=20, null=True) # Discord channel ID as string
pick_minutes = IntegerField(null=True)
class Award(BaseModel):
name = CharField()
season = IntegerField()
timing = CharField(default="In-Season")
manager1 = ForeignKeyField(Manager, null=True)
manager2 = ForeignKeyField(Manager, null=True)
player = ForeignKeyField(Player, null=True)
team = ForeignKeyField(Team, null=True)
image = CharField(null=True)
class DiceRoll(BaseModel):
season = IntegerField(default=12) # Will be updated to current season when needed
week = IntegerField(default=1) # Will be updated to current week when needed
team = ForeignKeyField(Team, null=True)
roller = CharField(max_length=20)
dsix = IntegerField(null=True)
twodsix = IntegerField(null=True)
threedsix = IntegerField(null=True)
dtwenty = IntegerField(null=True)
class DraftList(BaseModel):
season = IntegerField()
team = ForeignKeyField(Team)
rank = IntegerField()
player = ForeignKeyField(Player)
class Keeper(BaseModel):
season = IntegerField()
team = ForeignKeyField(Team)
player = ForeignKeyField(Player)
class Injury(BaseModel):
season = IntegerField()
player = ForeignKeyField(Player)
total_games = IntegerField()
start_week = IntegerField()
start_game = IntegerField()
end_week = IntegerField()
end_game = IntegerField()
is_active = BooleanField(default=True)
class StratGame(BaseModel):
season = IntegerField()
week = IntegerField()
game_num = IntegerField(null=True)
season_type = CharField(default='regular')
away_team = ForeignKeyField(Team)
home_team = ForeignKeyField(Team)
away_score = IntegerField(null=True)
home_score = IntegerField(null=True)
away_manager = ForeignKeyField(Manager, null=True)
home_manager = ForeignKeyField(Manager, null=True)
scorecard_url = CharField(null=True)
def update_standings(self):
away_stan = Standings.get_season(self.away_team)
home_stan = Standings.get_season(self.home_team)
away_div = Division.get_by_id(self.away_team.division.id)
home_div = Division.get_by_id(self.home_team.division.id)
# Home Team Won
if self.home_score > self.away_score:
# - generic w/l & home/away w/l
home_stan.wins += 1
home_stan.home_wins += 1
away_stan.losses += 1
away_stan.away_losses += 1
# - update streak wl and num
if home_stan.streak_wl == 'w':
home_stan.streak_num += 1
else:
home_stan.streak_wl = 'w'
home_stan.streak_num = 1
if away_stan.streak_wl == 'l':
away_stan.streak_num += 1
else:
away_stan.streak_wl = 'l'
away_stan.streak_num = 1
# - if 1-run, tally accordingly
if self.home_score == self.away_score + 1:
home_stan.one_run_wins += 1
away_stan.one_run_losses += 1
# Used for one league with 3 divisions
# - update record v division
# if away_div.division_abbrev == 'BE':
# home_stan.div1_wins += 1
# elif away_div.division_abbrev == 'DO':
# home_stan.div2_wins += 1
# else:
# home_stan.div3_wins += 1
#
# if home_div.division_abbrev == 'BE':
# away_stan.div1_losses += 1
# elif home_div.division_abbrev == 'DO':
# away_stan.div2_losses += 1
# else:
# away_stan.div3_losses += 1
# Used for one league with 4 divisions
# - update record v division (check opponent's division)
if away_div.division_abbrev == 'TC':
home_stan.div1_wins += 1
elif away_div.division_abbrev == 'ETSOS':
home_stan.div2_wins += 1
elif away_div.division_abbrev == 'APL':
home_stan.div3_wins += 1
elif away_div.division_abbrev == 'BBC':
home_stan.div4_wins += 1
if home_div.division_abbrev == 'TC':
away_stan.div1_losses += 1
elif home_div.division_abbrev == 'ETSOS':
away_stan.div2_losses += 1
elif home_div.division_abbrev == 'APL':
away_stan.div3_losses += 1
elif home_div.division_abbrev == 'BBC':
away_stan.div4_losses += 1
# Used for two league plus divisions
# if away_div.league_abbrev == 'AL':
# if away_div.division_abbrev == 'E':
# home_stan.div1_wins += 1
# else:
# home_stan.div2_wins += 1
# else:
# if away_div.division_abbrev == 'E':
# home_stan.div3_wins += 1
# else:
# home_stan.div4_wins += 1
#
# if home_div.league_abbrev == 'AL':
# if home_div.division_abbrev == 'E':
# away_stan.div1_losses += 1
# else:
# away_stan.div2_losses += 1
# else:
# if home_div.division_abbrev == 'E':
# away_stan.div3_losses += 1
# else:
# away_stan.div4_losses += 1
# - adjust run_diff
home_stan.run_diff += self.home_score - self.away_score
away_stan.run_diff -= self.home_score - self.away_score
# Away Team Won
else:
# - generic w/l & home/away w/l
home_stan.losses += 1
home_stan.home_losses += 1
away_stan.wins += 1
away_stan.away_wins += 1
# - update streak wl and num
if home_stan.streak_wl == 'l':
home_stan.streak_num += 1
else:
home_stan.streak_wl = 'l'
home_stan.streak_num = 1
if away_stan.streak_wl == 'w':
away_stan.streak_num += 1
else:
away_stan.streak_wl = 'w'
away_stan.streak_num = 1
# - if 1-run, tally accordingly
if self.away_score == self.home_score + 1:
home_stan.one_run_losses += 1
away_stan.one_run_wins += 1
# Used for one league with 3 divisions
# - update record v division
# if away_div.division_abbrev == 'BE':
# home_stan.div1_losses += 1
# elif away_div.division_abbrev == 'DO':
# home_stan.div2_losses += 1
# else:
# home_stan.div3_losses += 1
#
# if home_div.division_abbrev == 'BE':
# away_stan.div1_wins += 1
# elif home_div.division_abbrev == 'DO':
# away_stan.div2_wins += 1
# else:
# away_stan.div3_wins += 1
# Used for one league with 4 divisions
# - update record v division (check opponent's division)
if away_div.division_abbrev == 'TC':
home_stan.div1_losses += 1
elif away_div.division_abbrev == 'ETSOS':
home_stan.div2_losses += 1
elif away_div.division_abbrev == 'APL':
home_stan.div3_losses += 1
elif away_div.division_abbrev == 'BBC':
home_stan.div4_losses += 1
if home_div.division_abbrev == 'TC':
away_stan.div1_wins += 1
elif home_div.division_abbrev == 'ETSOS':
away_stan.div2_wins += 1
elif home_div.division_abbrev == 'APL':
away_stan.div3_wins += 1
elif home_div.division_abbrev == 'BBC':
away_stan.div4_wins += 1
# Used for two league plus divisions
# if away_div.league_abbrev == 'AL':
# if away_div.division_abbrev == 'E':
# home_stan.div1_losses += 1
# else:
# home_stan.div2_losses += 1
# else:
# if away_div.division_abbrev == 'E':
# home_stan.div3_losses += 1
# else:
# home_stan.div4_losses += 1
#
# if home_div.league_abbrev == 'AL':
# if home_div.division_abbrev == 'E':
# away_stan.div1_wins += 1
# else:
# away_stan.div2_wins += 1
# else:
# if home_div.division_abbrev == 'E':
# away_stan.div3_wins += 1
# else:
# away_stan.div4_wins += 1
# - adjust run_diff
home_stan.run_diff -= self.away_score - self.home_score
away_stan.run_diff += self.away_score - self.home_score
home_stan.save()
away_stan.save()
class StratPlay(BaseModel):
game = ForeignKeyField(StratGame)
play_num = IntegerField()
batter = ForeignKeyField(Player, null=True)
batter_team = ForeignKeyField(Team, null=True)
pitcher = ForeignKeyField(Player)
pitcher_team = ForeignKeyField(Team)
on_base_code = CharField()
inning_half = CharField()
inning_num = IntegerField()
batting_order = IntegerField()
starting_outs = IntegerField()
away_score = IntegerField()
home_score = IntegerField()
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)
on_first_final = IntegerField(null=True)
on_second = ForeignKeyField(Player, null=True)
on_second_final = IntegerField(null=True)
on_third = ForeignKeyField(Player, null=True)
on_third_final = IntegerField(null=True)
batter_final = IntegerField(null=True)
pa = IntegerField(default=0)
ab = IntegerField(default=0)
e_run = IntegerField(default=0)
run = IntegerField(default=0)
hit = IntegerField(default=0)
rbi = IntegerField(default=0)
double = IntegerField(default=0)
triple = IntegerField(default=0)
homerun = IntegerField(default=0)
bb = IntegerField(default=0)
so = IntegerField(default=0)
hbp = IntegerField(default=0)
sac = IntegerField(default=0)
ibb = IntegerField(default=0)
gidp = IntegerField(default=0)
bphr = IntegerField(default=0)
bpfo = IntegerField(default=0)
bp1b = IntegerField(default=0)
bplo = IntegerField(default=0)
sb = IntegerField(default=0)
cs = IntegerField(default=0)
outs = IntegerField(default=0)
wpa = FloatField(default=0)
# These <position> fields are only required if the play is an x-check or baserunning play
catcher = ForeignKeyField(Player, null=True)
catcher_team = ForeignKeyField(Team, null=True)
defender = ForeignKeyField(Player, null=True)
defender_team = ForeignKeyField(Team, null=True)
runner = ForeignKeyField(Player, null=True)
runner_team = ForeignKeyField(Team, null=True)
check_pos = CharField(null=True)
error = IntegerField(default=0)
wild_pitch = IntegerField(default=0)
passed_ball = IntegerField(default=0)
pick_off = IntegerField(default=0)
balk = IntegerField(default=0)
is_go_ahead = BooleanField(default=False)
is_tied = BooleanField(default=False)
is_new_inning = BooleanField(default=False)
# Season 9 addons
hand_batting = CharField(null=True)
hand_pitching = CharField(null=True)
re24_primary = FloatField(null=True)
re24_running = FloatField(null=True)
class Decision(BaseModel):
game = ForeignKeyField(StratGame)
season = IntegerField()
week = IntegerField()
game_num = IntegerField()
pitcher = ForeignKeyField(Player)
team = ForeignKeyField(Team, null=True)
win = IntegerField()
loss = IntegerField()
hold = IntegerField()
is_save = IntegerField()
b_save = IntegerField()
irunners = IntegerField()
irunners_scored = IntegerField()
rest_ip = FloatField()
rest_required = IntegerField()
is_start = BooleanField(default=False)
class CustomCommandCreator(BaseModel):
"""Model for custom command creators."""
discord_id = CharField(max_length=20, unique=True) # Discord snowflake ID as string
username = CharField(max_length=32)
display_name = CharField(max_length=32, null=True)
created_at = DateTimeField()
total_commands = IntegerField(default=0)
active_commands = IntegerField(default=0)
class Meta:
table_name = 'custom_command_creators'
class CustomCommand(BaseModel):
"""Model for custom commands created by users."""
name = CharField(max_length=32, unique=True)
content = TextField()
creator = ForeignKeyField(CustomCommandCreator, backref='commands')
# Timestamps
created_at = DateTimeField()
updated_at = DateTimeField(null=True)
last_used = DateTimeField(null=True)
# Usage tracking
use_count = IntegerField(default=0)
warning_sent = BooleanField(default=False)
# Metadata
is_active = BooleanField(default=True)
tags = TextField(null=True) # JSON string for tags list
class Meta:
table_name = 'custom_commands'
@staticmethod
def get_by_name(name: str):
"""Get a custom command by name (case-insensitive)."""
return CustomCommand.get_or_none(fn.Lower(CustomCommand.name) == name.lower())
@staticmethod
def search_by_name(partial_name: str, limit: int = 25):
"""Search commands by partial name match."""
return (CustomCommand
.select()
.where((CustomCommand.is_active == True) &
(fn.Lower(CustomCommand.name).contains(partial_name.lower())))
.order_by(CustomCommand.name)
.limit(limit))
@staticmethod
def get_popular(limit: int = 10):
"""Get most popular commands by usage."""
return (CustomCommand
.select()
.where(CustomCommand.is_active == True)
.order_by(CustomCommand.use_count.desc())
.limit(limit))
@staticmethod
def get_by_creator(creator_id: int, limit: int = 25, offset: int = 0):
"""Get commands by creator ID."""
return (CustomCommand
.select()
.where((CustomCommand.creator == creator_id) & (CustomCommand.is_active == True))
.order_by(CustomCommand.name)
.limit(limit)
.offset(offset))
@staticmethod
def get_unused_commands(days: int = 60):
"""Get commands that haven't been used in specified days."""
from datetime import datetime, timedelta
cutoff_date = datetime.now() - timedelta(days=days)
return (CustomCommand
.select()
.where((CustomCommand.is_active == True) &
((CustomCommand.last_used.is_null()) |
(CustomCommand.last_used < cutoff_date))))
@staticmethod
def get_commands_needing_warning():
"""Get commands that need deletion warning (60+ days unused, no warning sent)."""
from datetime import datetime, timedelta
cutoff_date = datetime.now() - timedelta(days=60)
return (CustomCommand
.select()
.where((CustomCommand.is_active == True) &
(CustomCommand.warning_sent == False) &
((CustomCommand.last_used.is_null()) |
(CustomCommand.last_used < cutoff_date))))
@staticmethod
def get_commands_eligible_for_deletion():
"""Get commands eligible for deletion (90+ days unused)."""
from datetime import datetime, timedelta
cutoff_date = datetime.now() - timedelta(days=90)
return (CustomCommand
.select()
.where((CustomCommand.is_active == True) &
((CustomCommand.last_used.is_null()) |
(CustomCommand.last_used < cutoff_date))))
def execute(self):
"""Execute the command and update usage statistics."""
from datetime import datetime
self.last_used = datetime.now()
self.use_count += 1
self.warning_sent = False # Reset warning on use
self.save()
def mark_warning_sent(self):
"""Mark that a deletion warning has been sent."""
self.warning_sent = True
self.save()
def get_tags_list(self):
"""Parse tags JSON string into a list."""
if not self.tags:
return []
try:
import json
return json.loads(self.tags)
except:
return []
def set_tags_list(self, tags_list):
"""Set tags from a list to JSON string."""
if tags_list:
import json
self.tags = json.dumps(tags_list)
else:
self.tags = None
class HelpCommand(BaseModel):
"""Model for admin-created help topics."""
name = CharField(max_length=32, unique=True)
title = CharField(max_length=200)
content = TextField()
category = CharField(max_length=50, null=True)
# Audit fields
created_by_discord_id = CharField(max_length=20) # Discord snowflake ID as string
created_at = DateTimeField()
updated_at = DateTimeField(null=True)
last_modified_by = CharField(max_length=20, null=True) # Discord snowflake ID as string
# Status and metrics
is_active = BooleanField(default=True)
view_count = IntegerField(default=0)
display_order = IntegerField(default=0)
class Meta:
table_name = 'help_commands'
@staticmethod
def get_by_name(name: str, include_inactive: bool = False):
"""Get a help command by name (case-insensitive)."""
query = HelpCommand.select().where(fn.Lower(HelpCommand.name) == name.lower())
if not include_inactive:
query = query.where(HelpCommand.is_active == True)
return query.first()
@staticmethod
def search_by_name(partial_name: str, limit: int = 25):
"""Search help topics by partial name match."""
return (HelpCommand
.select()
.where((HelpCommand.is_active == True) &
(fn.Lower(HelpCommand.name).contains(partial_name.lower())))
.order_by(HelpCommand.display_order, HelpCommand.name)
.limit(limit))
@staticmethod
def get_by_category(category: str, include_inactive: bool = False):
"""Get help commands by category."""
query = HelpCommand.select().where(fn.Lower(HelpCommand.category) == category.lower())
if not include_inactive:
query = query.where(HelpCommand.is_active == True)
return query.order_by(HelpCommand.display_order, HelpCommand.name)
@staticmethod
def get_all_active():
"""Get all active help topics."""
return (HelpCommand
.select()
.where(HelpCommand.is_active == True)
.order_by(HelpCommand.display_order, HelpCommand.name))
@staticmethod
def get_most_viewed(limit: int = 10):
"""Get most viewed help topics."""
return (HelpCommand
.select()
.where(HelpCommand.is_active == True)
.order_by(HelpCommand.view_count.desc())
.limit(limit))
def increment_view_count(self):
"""Increment view count for this help topic."""
self.view_count += 1
self.save()
def soft_delete(self):
"""Soft delete this help topic."""
self.is_active = False
self.save()
def restore(self):
"""Restore this soft-deleted help topic."""
self.is_active = True
self.save()
class SeasonBattingStatsView(BaseModel):
name = CharField()
player_id = IntegerField()
sbaplayer_id = IntegerField()
season = IntegerField()
player_team_id = CharField()
pa = IntegerField()
ab = IntegerField()
run = IntegerField()
hit = IntegerField()
double = IntegerField()
triple = IntegerField()
hr = IntegerField()
rbi = IntegerField()
sb = IntegerField()
cs = IntegerField()
bb = IntegerField()
so = IntegerField()
avg = FloatField()
obp = FloatField()
slg = FloatField()
ops = FloatField()
woba = FloatField()
k_pct = FloatField()
bphr = IntegerField()
bpfo = IntegerField()
bp1b = IntegerField()
bplo = IntegerField()
gidp = IntegerField()
hbp = IntegerField()
sac = IntegerField()
ibb = IntegerField()
class Meta:
table_name = 'season_batting_stats_view'
primary_key = False
@staticmethod
def get_by_season(season):
return SeasonBattingStatsView.select().where(SeasonBattingStatsView.season == season)
@staticmethod
def get_team_stats(season, team_id):
return (SeasonBattingStatsView.select()
.where(SeasonBattingStatsView.season == season,
SeasonBattingStatsView.player_team_id == team_id))
@staticmethod
def get_top_hitters(season, stat='avg', limit=10, desc=True):
"""Get top hitters by specified stat (avg, hr, rbi, ops, etc.)"""
stat_field = getattr(SeasonBattingStatsView, stat, SeasonBattingStatsView.avg)
order_field = stat_field.desc() if desc else stat_field.asc()
return (SeasonBattingStatsView.select()
.where(SeasonBattingStatsView.season == season)
.order_by(order_field)
.limit(limit))
class SeasonPitchingStats(BaseModel):
player = ForeignKeyField(Player)
sbaplayer = ForeignKeyField(SbaPlayer, null=True)
team = ForeignKeyField(Team)
season = IntegerField()
name = CharField()
player_team_id = IntegerField()
player_team_abbrev = CharField()
# Counting stats
tbf = IntegerField()
outs = IntegerField()
games = IntegerField()
# Decision Info
gs = IntegerField()
win = IntegerField()
loss = IntegerField()
hold = IntegerField()
saves = IntegerField()
bsave = IntegerField()
ir = IntegerField()
irs = IntegerField()
# Counting stats part 2
ab = IntegerField()
run = IntegerField()
e_run = IntegerField()
hits = IntegerField()
double = IntegerField()
triple = IntegerField()
homerun = IntegerField()
bb = IntegerField()
so = IntegerField()
hbp = IntegerField()
sac = IntegerField()
ibb = IntegerField()
gidp = IntegerField()
sb = IntegerField()
cs = IntegerField()
bphr = IntegerField()
bpfo = IntegerField()
bp1b = IntegerField()
bplo = IntegerField()
wp = IntegerField()
balk = IntegerField()
# Calculated stats
wpa = FloatField()
era = FloatField()
whip = FloatField()
avg = FloatField()
obp = FloatField()
slg = FloatField()
ops = FloatField()
woba = FloatField()
hper9 = FloatField()
kper9 = FloatField()
bbper9 = FloatField()
kperbb = FloatField()
lob_2outs = FloatField()
rbipercent = FloatField()
re24 = FloatField()
class Meta:
table_name = 'seasonpitchingstats'
primary_key = CompositeKey('player', 'season')
@staticmethod
def get_team_stats(season, team_id):
return (SeasonPitchingStats.select()
.where(SeasonPitchingStats.season == season,
SeasonPitchingStats.player_team_id == team_id))
@staticmethod
def get_top_pitchers(season: Optional[int] = None, stat: str = 'era', limit: Optional[int] = 200,
desc: bool = False, team_id: Optional[int] = None, player_id: Optional[int] = None,
sbaplayer_id: Optional[int] = None, min_outs: Optional[int] = None, offset: int = 0):
"""
Get top pitchers by specified stat with optional filtering.
Args:
season: Season to filter by (None for all seasons)
stat: Stat field to sort by (default: era)
limit: Maximum number of results (None for no limit)
desc: Sort descending if True, ascending if False (default False for ERA)
team_id: Filter by team ID
player_id: Filter by specific player ID
sbaplayer_id: Filter by SBA player ID
min_outs: Minimum outs pitched filter
offset: Number of results to skip for pagination
"""
stat_field = getattr(SeasonPitchingStats, stat, SeasonPitchingStats.era)
order_field = stat_field.desc() if desc else stat_field.asc()
query = SeasonPitchingStats.select().order_by(order_field)
# Apply filters
if season is not None:
query = query.where(SeasonPitchingStats.season == season)
if team_id is not None:
query = query.where(SeasonPitchingStats.player_team_id == team_id)
if player_id is not None:
query = query.where(SeasonPitchingStats.player_id == player_id)
if sbaplayer_id is not None:
query = query.where(SeasonPitchingStats.sbaplayer_id == sbaplayer_id)
if min_outs is not None:
query = query.where(SeasonPitchingStats.outs >= min_outs)
# Apply pagination
if offset > 0:
query = query.offset(offset)
if limit is not None and limit > 0:
query = query.limit(limit)
return query
class SeasonBattingStats(BaseModel):
player = ForeignKeyField(Player)
sbaplayer = ForeignKeyField(SbaPlayer, null=True)
team = ForeignKeyField(Team)
season = IntegerField()
name = CharField()
player_team_id = IntegerField()
player_team_abbrev = CharField()
# Counting stats
pa = IntegerField()
ab = IntegerField()
run = IntegerField()
hit = IntegerField()
double = IntegerField()
triple = IntegerField()
homerun = IntegerField()
rbi = IntegerField()
bb = IntegerField()
so = IntegerField()
bphr = IntegerField()
bpfo = IntegerField()
bp1b = IntegerField()
bplo = IntegerField()
gidp = IntegerField()
hbp = IntegerField()
sac = IntegerField()
ibb = IntegerField()
# Calculating stats
avg = FloatField()
obp = FloatField()
slg = FloatField()
ops = FloatField()
woba = FloatField()
k_pct = FloatField()
# Running stats
sb = IntegerField()
cs = IntegerField()
class Meta:
table_name = 'seasonbattingstats'
primary_key = CompositeKey('player', 'season')
@staticmethod
def get_team_stats(season, team_id):
return (SeasonBattingStats.select()
.where(SeasonBattingStats.season == season,
SeasonBattingStats.player_team_id == team_id))
@staticmethod
def get_top_hitters(season: Optional[int] = None, stat: str = 'woba', limit: Optional[int] = 200,
desc: bool = True, team_id: Optional[int] = None, player_id: Optional[int] = None,
sbaplayer_id: Optional[int] = None, min_pa: Optional[int] = None, offset: int = 0):
"""
Get top hitters by specified stat with optional filtering.
Args:
season: Season to filter by (None for all seasons)
stat: Stat field to sort by (default: woba)
limit: Maximum number of results (None for no limit)
desc: Sort descending if True, ascending if False
team_id: Filter by team ID
player_id: Filter by specific player ID
min_pa: Minimum plate appearances filter
offset: Number of results to skip for pagination
"""
stat_field = getattr(SeasonBattingStats, stat, SeasonBattingStats.woba)
order_field = stat_field.desc() if desc else stat_field.asc()
query = SeasonBattingStats.select().order_by(order_field)
# Apply filters
if season is not None:
query = query.where(SeasonBattingStats.season == season)
if team_id is not None:
query = query.where(SeasonBattingStats.player_team_id == team_id)
if player_id is not None:
query = query.where(SeasonBattingStats.player_id == player_id)
if sbaplayer_id is not None:
query = query.where(SeasonBattingStats.sbaplayer_id == sbaplayer_id)
if min_pa is not None:
query = query.where(SeasonBattingStats.pa >= min_pa)
# Apply pagination
if offset > 0:
query = query.offset(offset)
if limit is not None and limit > 0:
query = query.limit(limit)
return query
# class Streak(BaseModel):
# player = ForeignKeyField(Player)
# streak_type = CharField()
# start_season = IntegerField()
# start_week = IntegerField()
# start_game = IntegerField()
# end_season = IntegerField()
# end_week = IntegerField()
# end_game = IntegerField()
# game_length = IntegerField()
# active = BooleanField()
#
# def recalculate(self):
# # Pitcher streaks
# if self.streak_type in ['win', 'loss', 'save', 'scoreless']:
# all_stats = PitchingStat.select_season(self.start_season).where(
# (PitchingStat.player == self.player) & (PitchingStat.week >= self.start_week)
# )
# sorted_stats = sorted(all_stats, key=lambda x: f'{x.season:0>2}-{x.week:0>2}-{x.game:}')
#
# for line in sorted_stats:
# Table creation moved to migration scripts to avoid dependency issues
# 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, Decision, CustomCommandCreator, CustomCommand
# ])
# db.close()