import csv import datetime import pandas as pd import random import logging from db_calls import db_get from db_calls_card_creation import * D20_CHANCES = { '2': { 'chances': 1, 'inc': .05 }, '3': { 'chances': 2, 'inc': .1 }, '4': { 'chances': 3, 'inc': .15 }, '5': { 'chances': 4, 'inc': .2 }, '6': { 'chances': 5, 'inc': .25 }, '7': { 'chances': 6, 'inc': .3 }, '8': { 'chances': 5, 'inc': .25 }, '9': { 'chances': 4, 'inc': .2 }, '10': { 'chances': 3, 'inc': .15 }, '11': { 'chances': 2, 'inc': .1 }, '12': { 'chances': 1, 'inc': .05 } } BLANK_RESULTS = { 'vL': { '1': { '2': { 'result': None, 'splits': None, '2d6': None }, '3': { 'result': None, 'splits': None, '2d6': None }, '4': { 'result': None, 'splits': None, '2d6': None }, '5': { 'result': None, 'splits': None, '2d6': None }, '6': { 'result': None, 'splits': None, '2d6': None }, '7': { 'result': None, 'splits': None, '2d6': None }, '8': { 'result': None, 'splits': None, '2d6': None }, '9': { 'result': None, 'splits': None, '2d6': None }, '10': { 'result': None, 'splits': None, '2d6': None }, '11': { 'result': None, 'splits': None, '2d6': None }, '12': { 'result': None, 'splits': None, '2d6': None }, 'splits': 0 }, '2': { '2': { 'result': None, 'splits': None, '2d6': None }, '3': { 'result': None, 'splits': None, '2d6': None }, '4': { 'result': None, 'splits': None, '2d6': None }, '5': { 'result': None, 'splits': None, '2d6': None }, '6': { 'result': None, 'splits': None, '2d6': None }, '7': { 'result': None, 'splits': None, '2d6': None }, '8': { 'result': None, 'splits': None, '2d6': None }, '9': { 'result': None, 'splits': None, '2d6': None }, '10': { 'result': None, 'splits': None, '2d6': None }, '11': { 'result': None, 'splits': None, '2d6': None }, '12': { 'result': None, 'splits': None, '2d6': None }, 'splits': 0 }, '3': { '2': { 'result': None, 'splits': None, '2d6': None }, '3': { 'result': None, 'splits': None, '2d6': None }, '4': { 'result': None, 'splits': None, '2d6': None }, '5': { 'result': None, 'splits': None, '2d6': None }, '6': { 'result': None, 'splits': None, '2d6': None }, '7': { 'result': None, 'splits': None, '2d6': None }, '8': { 'result': None, 'splits': None, '2d6': None }, '9': { 'result': None, 'splits': None, '2d6': None }, '10': { 'result': None, 'splits': None, '2d6': None }, '11': { 'result': None, 'splits': None, '2d6': None }, '12': { 'result': None, 'splits': None, '2d6': None }, 'splits': 0 } }, 'vR': { '1': { '2': { 'result': None, 'splits': None, '2d6': None }, '3': { 'result': None, 'splits': None, '2d6': None }, '4': { 'result': None, 'splits': None, '2d6': None }, '5': { 'result': None, 'splits': None, '2d6': None }, '6': { 'result': None, 'splits': None, '2d6': None }, '7': { 'result': None, 'splits': None, '2d6': None }, '8': { 'result': None, 'splits': None, '2d6': None }, '9': { 'result': None, 'splits': None, '2d6': None }, '10': { 'result': None, 'splits': None, '2d6': None }, '11': { 'result': None, 'splits': None, '2d6': None }, '12': { 'result': None, 'splits': None, '2d6': None }, 'splits': 0 }, '2': { '2': { 'result': None, 'splits': None, '2d6': None }, '3': { 'result': None, 'splits': None, '2d6': None }, '4': { 'result': None, 'splits': None, '2d6': None }, '5': { 'result': None, 'splits': None, '2d6': None }, '6': { 'result': None, 'splits': None, '2d6': None }, '7': { 'result': None, 'splits': None, '2d6': None }, '8': { 'result': None, 'splits': None, '2d6': None }, '9': { 'result': None, 'splits': None, '2d6': None }, '10': { 'result': None, 'splits': None, '2d6': None }, '11': { 'result': None, 'splits': None, '2d6': None }, '12': { 'result': None, 'splits': None, '2d6': None }, 'splits': 0 }, '3': { '2': { 'result': None, 'splits': None, '2d6': None }, '3': { 'result': None, 'splits': None, '2d6': None }, '4': { 'result': None, 'splits': None, '2d6': None }, '5': { 'result': None, 'splits': None, '2d6': None }, '6': { 'result': None, 'splits': None, '2d6': None }, '7': { 'result': None, 'splits': None, '2d6': None }, '8': { 'result': None, 'splits': None, '2d6': None }, '9': { 'result': None, 'splits': None, '2d6': None }, '10': { 'result': None, 'splits': None, '2d6': None }, '11': { 'result': None, 'splits': None, '2d6': None }, '12': { 'result': None, 'splits': None, '2d6': None }, 'splits': 0 } } } TESTING = False YES = ['y', 'yes', 'yeet', 'please', 'yeah'] async def pd_players_df(cardset_id: int): p_query = await db_get( 'players', params=[('inc_dex', False), ('cardset_id', cardset_id), ('short_output', True)] ) if p_query['count'] == 0: raise ValueError(f'No players returned from Paper Dynasty API') return pd.DataFrame(p_query['players']) async def pd_battingcards_df(cardset_id: int): bc_query = await db_get('battingcards', params=[('cardset_id', cardset_id), ('short_output', True)]) if bc_query['count'] == 0: raise ValueError(f'No batting cards returned from Paper Dynasty API') return pd.DataFrame(bc_query['cards']).rename(columns={'id': 'battingcard_id', 'player': 'player_id'}) async def pd_battingcardratings_df(cardset_id: int): vl_query = await db_get( 'battingcardratings', params=[('cardset_id', cardset_id), ('vs_hand', 'L'), ('short_output', True)]) vr_query = await db_get( 'battingcardratings', params=[('cardset_id', cardset_id), ('vs_hand', 'R'), ('short_output', True)]) if 0 in [vl_query['count'], vr_query['count']]: raise ValueError(f'No batting card ratings returned from Paper Dynasty API') vl = pd.DataFrame(vl_query['ratings']) vr = pd.DataFrame(vr_query['ratings']) ratings = (pd.merge(vl, vr, on='battingcard', suffixes=('_vL', '_vR')) .rename(columns={'battingcard': 'battingcard_id'})) def get_total_ops(df_data): ops_vl = df_data['obp_vL'] + df_data['slg_vL'] ops_vr = df_data['obp_vR'] + df_data['slg_vR'] return (ops_vr + ops_vl + min(ops_vl, ops_vr)) / 3 ratings['total_OPS'] = ratings.apply(get_total_ops, axis=1) def new_rarity_id(df_data): if df_data['total_OPS'] >= 1.2: return 99 elif df_data['total_OPS'] >= 1: return 1 elif df_data['total_OPS'] >= .9: return 2 elif df_data['total_OPS'] >= .8: return 3 elif df_data['total_OPS'] >= .7: return 4 else: return 5 ratings['new_rarity_id'] = ratings.apply(new_rarity_id, axis=1) return ratings # return pd.DataFrame(bcr_query['ratings']).rename(columns={'battingcard': 'battingcard_id'}) def get_batting_stats(file_path: str = None, start_date: datetime.datetime = None, end_date: datetime.datetime = None): if file_path is not None: vl_basic = pd.read_csv(f'{file_path}vlhp-basic.csv').query('PA >= 20') vr_basic = pd.read_csv(f'{file_path}vrhp-basic.csv').query('PA >= 40') total_basic = pd.merge(vl_basic, vr_basic, on="playerId", suffixes=('_vL', '_vR')) vl_rate = pd.read_csv(f'{file_path}vlhp-rate.csv').query('PA >= 20') vr_rate = pd.read_csv(f'{file_path}vrhp-rate.csv').query('PA >= 40') total_rate = pd.merge(vl_rate, vr_rate, on="playerId", suffixes=('_vL', '_vR')) return pd.merge(total_basic, total_rate, on="playerId", suffixes=('', '_rate')) else: raise LookupError(f'Date-based stat pulls not implemented, yet. Please provide batting csv files.') def get_pitching_stats(file_path: str = None, start_date: datetime.datetime = None, end_date: datetime.datetime = None): if file_path is not None: vl_basic = pd.read_csv(f'{file_path}vlhh-basic.csv').query('TBF >= 20') vr_basic = pd.read_csv(f'{file_path}vrhh-basic.csv').query('TBF >= 40') total_basic = pd.merge(vl_basic, vr_basic, on="playerId", suffixes=('_vL', '_vR')) vl_rate = pd.read_csv(f'{file_path}vlhh-rate.csv').query('TBF >= 20') vr_rate = pd.read_csv(f'{file_path}vrhh-rate.csv').query('TBF >= 40') total_rate = pd.merge(vl_rate, vr_rate, on="playerId", suffixes=('_vL', '_vR')) return pd.merge(total_basic, total_rate, on="playerId", suffixes=('', '_rate')) else: raise LookupError(f'Date-based stat pulls not implemented, yet. Please provide batting csv files.') def mround(x, prec=2, base=.05): return round(base * round(float(x) / base), prec) def chances_from_row(row_num): if row_num == '2' or row_num == '12': return 1 if row_num == '3' or row_num == '11': return 2 if row_num == '4' or row_num == '10': return 3 if row_num == '5' or row_num == '9': return 4 if row_num == '6' or row_num == '8': return 5 if row_num == '7': return 6 raise ValueError(f'No chance count found for row_num {row_num}') def legal_splits(tot_chances): legal_2d6 = [] for x in D20_CHANCES: num_incs = mround(tot_chances) / D20_CHANCES[x]['inc'] if num_incs - int(num_incs) == 0 and int(20 - num_incs) > 0: legal_2d6.append({ '2d6': int(x), 'incs': int(num_incs), 'bad_chances': mround(D20_CHANCES[x]['chances'] * (int(20 - num_incs) / 20)), 'bad_incs': int(20 - num_incs) }) random.shuffle(legal_2d6) # if TESTING: print(f'tot_chances: {myround(tot_chances)}') # if TESTING: print(f'legal_2d6: {legal_2d6}') return legal_2d6 def result_string(tba_data, row_num, split_min=None, split_max=None): bold1 = f'{"" if tba_data["bold"] else ""}' bold2 = f'{"" if tba_data["bold"] else ""}' row_string = f'{" " if int(row_num) < 10 else ""}{row_num}' if TESTING: print(f'adding {tba_data["string"]} to row {row_num} / ' f'split_min: {split_min} / split_max: {split_max}') # No splits; standard result if not split_min: return f'{bold1}{row_string}-{tba_data["string"]}{bold2}' # With splits split_nums = f'{split_min if split_min != 20 else ""}{"-" if split_min != 20 else ""}{split_max}' data_string = tba_data["sm-string"] if "sm-string" in tba_data.keys() else tba_data["string"] spaces = 18 - len(data_string) - len(split_nums) if 'WALK' in data_string: spaces -= 3 elif 'SI**' in data_string: spaces += 1 elif 'DO*' in data_string: spaces -= 1 elif 'DO*' in data_string: spaces -= 2 elif 'so' in data_string: spaces += 3 elif 'gb' in data_string: spaces -= 3 if TESTING: print(f'len(tba_data["string"]): {len(data_string)} / len(split_nums): {len(split_nums)} ' f'spaces: {spaces}') if split_min == 1 or split_min is None: row_output = f'{row_string}-' else: row_output = ' ' if TESTING: print(f'row_output: {row_output}') return f'{bold1}{row_output}{data_string}{" " * spaces}{split_nums}{bold2}' def result_data(tba_data, row_num, tba_data_bottom=None, top_split_max=None, fatigue=False): ret_data = {} top_bold1 = f'{"" if tba_data["bold"] else ""}' top_bold2 = f'{"" if tba_data["bold"] else ""}' bot_bold1 = None bot_bold2 = None if tba_data_bottom: bot_bold1 = f'{"" if tba_data_bottom["bold"] else ""}' bot_bold2 = f'{"" if tba_data_bottom["bold"] else ""}' if tba_data_bottom is None: ret_data['2d6'] = f'{top_bold1}{int(row_num)}-{top_bold2}' ret_data['splits'] = f'{top_bold1}‎{top_bold2}' ret_data['result'] = f'{top_bold1}' \ f'{tba_data["string"]}{" •" if fatigue else ""}' \ f'{top_bold2}' else: ret_data['2d6'] = f'{top_bold1}{int(row_num)}-{top_bold2}\n' ret_data['splits'] = f'{top_bold1}1{"-" if top_split_max != 1 else ""}' \ f'{top_split_max if top_split_max != 1 else ""}{top_bold2}\n' \ f'{bot_bold1}{top_split_max+1}{"-20" if top_split_max != 19 else ""}{bot_bold2}' ret_data['result'] = \ f'{top_bold1}{tba_data["sm-string"] if "sm-string" in tba_data.keys() else tba_data["string"]}' \ f'{top_bold2}\n' \ f'{bot_bold1}' \ f'{tba_data_bottom["sm-string"] if "sm-string" in tba_data_bottom.keys() else tba_data_bottom["string"]}' \ f'{bot_bold2}' return ret_data def get_of(batter_hand, pitcher_hand, pull_side=True): if batter_hand == 'R': return 'lf' if pull_side else 'rf' if batter_hand == 'L': return 'rf' if pull_side else 'lf' if batter_hand == 'S': if pitcher_hand == 'L': return 'rf' if pull_side else 'rf' else: return 'lf' if pull_side else 'lf' def get_col(col_num): if col_num == '1': return 'one' if col_num == '2': return 'two' if col_num == '3': return 'three' def write_to_csv(output_path, file_name: str, row_data: list): # Build the csv output fpath = (output_path / f'{file_name}').with_suffix('.csv') # logging.info(f'Printing following data to {file_name}:\n\n{row_data}') with fpath.open(mode='w+', newline='', encoding='utf-8') as csv_File: writer = csv.writer(csv_File) writer.writerows(row_data) def get_position_string(all_pos: list, inc_p: bool): if len(all_pos) == 0: return 'dh' of_arm = None of_error = None of_innings = None lf_range = None lf_innings = 0 cf_range = None cf_innings = 0 rf_range = None rf_innings = 0 all_def = [] for x in all_pos: if x.position == 'OF': of_arm = f'{"+" if "-" not in x.arm else ""}{x.arm}' of_error = x.error of_innings = x.innings elif x.position == 'CF': cf_range = x.range cf_innings = x.innings elif x.position == 'LF': lf_range = x.range lf_innings = x.innings elif x.position == 'RF': rf_range = x.range rf_innings = x.innings elif x.position == 'C': all_def.append( (f'c-{x.range}({"+" if int(x.arm) >= 0 else ""}{x.arm}) e{x.error} T-{x.overthrow}(pb-{x.pb})', x.innings) ) elif 'P' in x.position and not inc_p: pass else: all_def.append((f'{x.position.lower()}-{x.range}e{x.error}', x.innings)) if of_arm is not None: logging.info( f'\n\nProcessing OF player ID {all_pos[0].player_id}\nlf-{lf_range} / cf-{cf_range} / rf-{rf_range}' ) all_of = [] if lf_innings > 0: all_of.append((lf_range, lf_innings, 'lf')) if cf_innings > 0: all_of.append((cf_range, cf_innings, 'cf')) if rf_innings > 0: all_of.append((rf_range, rf_innings, 'rf')) logging.info(f'all_of: {all_of}') if len(all_of) > 0: all_of.sort(key=lambda y: y[1], reverse=True) logging.info(f'sorted of: {all_of}') out_string = f'{all_of[0][2]}-{all_of[0][0]}({of_arm})e{of_error}' if len(all_of) >= 2: out_string += f', {all_of[1][2]}-{all_of[1][0]}e{of_error}' if len(all_of) >= 3: out_string += f', {all_of[2][2]}-{all_of[2][0]}e{of_error}' logging.info(f'of string: {out_string}') all_def.append((out_string, of_innings)) all_def.sort(key=lambda z: z[1], reverse=True) final_defense = '' for x in all_def: if len(final_defense) > 0: final_defense += f', ' final_defense += f'{x[0]}' return final_defense def ordered_positions(all_pos: list) -> list: if len(all_pos) == 0: return ['DH'] all_def = [] for x in all_pos: if x.position not in ['OF', 'P', 'SP', 'RP', 'CP']: all_def.append((x.innings, x.position)) all_def.sort(key=lambda y: y[0], reverse=True) return [x[1] for x in all_def] def ordered_pitching_positions(all_pos: list) -> list: all_def = [] for x in all_pos: if x.position in ['SP', 'RP', 'CP']: all_def.append((x.innings, x.position)) all_def.sort(key=lambda y: y[0], reverse=True) return [x[1] for x in all_def] def defense_rg(all_pos: list) -> list: rg_data = [ None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None ] all_pitcher = True for line in all_pos: if 'P' not in line.position: all_pitcher = False break for line in all_pos: if line.position == 'P' and all_pitcher: this_pit = PitcherData.get_or_none(PitcherData.player == line.player, PitcherData.cardset == line.cardset) if this_pit: rg_data[0] = line.range rg_data[9] = line.error rg_data[22] = this_pit.wild_pitch rg_data[23] = this_pit.balk elif line.position == 'C': rg_data[1] = line.range rg_data[10] = line.error rg_data[19] = line.arm rg_data[20] = line.overthrow rg_data[21] = line.pb elif line.position == '1B': rg_data[2] = line.range rg_data[11] = line.error elif line.position == '2B': rg_data[3] = line.range rg_data[12] = line.error elif line.position == '3B': rg_data[4] = line.range rg_data[13] = line.error elif line.position == 'SS': rg_data[5] = line.range rg_data[14] = line.error elif line.position == 'LF': rg_data[6] = line.range elif line.position == 'CF': rg_data[7] = line.range elif line.position == 'RF': rg_data[8] = line.range elif line.position == 'OF': rg_data[15] = line.error rg_data[16] = line.error rg_data[17] = line.error rg_data[18] = line.arm return rg_data