214 lines
6.4 KiB
Python
214 lines
6.4 KiB
Python
"""Pure helper functions for the /dev refractor-test command.
|
|
|
|
Builds synthetic game data to push a card over its next refractor
|
|
tier threshold with the minimum number of plays.
|
|
"""
|
|
|
|
import math
|
|
|
|
# Batter: value = PA + (TB * 2). A HR play: PA=1, TB=4 → value = 1 + 8 = 9
|
|
BATTER_VALUE_PER_PLAY = 9
|
|
|
|
# Pitcher: value = IP + K. A K play: outs=1 (IP=1/3), K=1 → value = 1/3 + 1 = 4/3
|
|
PITCHER_VALUE_PER_PLAY = 4 / 3
|
|
|
|
|
|
def calculate_plays_needed(gap: int, card_type: str) -> dict:
|
|
"""Calculate the number of synthetic plays needed to close a refractor gap.
|
|
|
|
Args:
|
|
gap: Points needed to reach the next tier threshold.
|
|
A gap of 0 means the card is exactly at threshold — we still
|
|
need 1 play to push past it.
|
|
card_type: One of "batter", "sp", "rp".
|
|
|
|
Returns:
|
|
dict with keys:
|
|
num_plays: int — number of plays to create
|
|
total_value: float — total refractor value those plays will add
|
|
value_per_play: float — value each play contributes
|
|
"""
|
|
if card_type == "batter":
|
|
value_per_play = BATTER_VALUE_PER_PLAY
|
|
else:
|
|
value_per_play = PITCHER_VALUE_PER_PLAY
|
|
|
|
num_plays = max(1, math.ceil(gap / value_per_play))
|
|
total_value = num_plays * value_per_play
|
|
|
|
return {
|
|
"num_plays": num_plays,
|
|
"total_value": total_value,
|
|
"value_per_play": value_per_play,
|
|
}
|
|
|
|
|
|
def build_game_data(team_id: int, season: int) -> dict:
|
|
"""Build a minimal game record for refractor testing.
|
|
|
|
Creates a self-play game (team vs itself) with game_type='test'.
|
|
All score and ranking fields are zeroed; short_game=True avoids
|
|
full simulation overhead when this record is posted to the API.
|
|
"""
|
|
return {
|
|
"season": season,
|
|
"game_type": "test",
|
|
"away_team_id": team_id,
|
|
"home_team_id": team_id,
|
|
"week": 1,
|
|
"away_score": 0,
|
|
"home_score": 0,
|
|
"away_team_value": 0,
|
|
"home_team_value": 0,
|
|
"away_team_ranking": 0,
|
|
"home_team_ranking": 0,
|
|
"ranked": False,
|
|
"short_game": True,
|
|
"forfeit": False,
|
|
}
|
|
|
|
|
|
def build_batter_plays(
|
|
game_id: int,
|
|
batter_id: int,
|
|
team_id: int,
|
|
pitcher_id: int,
|
|
num_plays: int,
|
|
) -> list[dict]:
|
|
"""Build a list of synthetic solo-HR batter plays for refractor testing.
|
|
|
|
Each play is a solo home run (PA=1, AB=1, H=1, HR=1, R=1, RBI=1).
|
|
Structural fields use safe defaults so the batch is accepted by the
|
|
plays API endpoint without requiring real game context. play_num is
|
|
sequential starting at 1.
|
|
|
|
Args:
|
|
game_id: ID of the game these plays belong to.
|
|
batter_id: Card/player ID of the batter receiving credit.
|
|
team_id: Team ID used for both batter_team_id and pitcher_team_id
|
|
(self-play game).
|
|
pitcher_id: Card/player ID of the opposing pitcher.
|
|
num_plays: Number of HR plays to generate.
|
|
|
|
Returns:
|
|
List of play dicts, one per home run.
|
|
"""
|
|
plays = []
|
|
for i in range(num_plays):
|
|
plays.append(
|
|
{
|
|
"game_id": game_id,
|
|
"play_num": i + 1,
|
|
"batter_id": batter_id,
|
|
"batter_team_id": team_id,
|
|
"pitcher_id": pitcher_id,
|
|
"pitcher_team_id": team_id,
|
|
"pa": 1,
|
|
"ab": 1,
|
|
"hit": 1,
|
|
"homerun": 1,
|
|
"run": 1,
|
|
"rbi": 1,
|
|
"on_base_code": "000",
|
|
"inning_half": "bot",
|
|
"inning_num": 1,
|
|
"batting_order": 1,
|
|
"starting_outs": 0,
|
|
"away_score": 0,
|
|
"home_score": 0,
|
|
}
|
|
)
|
|
return plays
|
|
|
|
|
|
def build_pitcher_plays(
|
|
game_id: int,
|
|
pitcher_id: int,
|
|
team_id: int,
|
|
batter_id: int,
|
|
num_plays: int,
|
|
) -> list[dict]:
|
|
"""Build a list of synthetic strikeout pitcher plays for refractor testing.
|
|
|
|
Each play is a strikeout (PA=1, AB=1, SO=1, outs=1, hit=0, homerun=0).
|
|
Structural fields use the same safe defaults as build_batter_plays.
|
|
play_num is sequential starting at 1.
|
|
|
|
Args:
|
|
game_id: ID of the game these plays belong to.
|
|
pitcher_id: Card/player ID of the pitcher receiving credit.
|
|
team_id: Team ID used for both pitcher_team_id and batter_team_id
|
|
(self-play game).
|
|
batter_id: Card/player ID of the opposing batter.
|
|
num_plays: Number of strikeout plays to generate.
|
|
|
|
Returns:
|
|
List of play dicts, one per strikeout.
|
|
"""
|
|
plays = []
|
|
for i in range(num_plays):
|
|
plays.append(
|
|
{
|
|
"game_id": game_id,
|
|
"play_num": i + 1,
|
|
"pitcher_id": pitcher_id,
|
|
"pitcher_team_id": team_id,
|
|
"batter_id": batter_id,
|
|
"batter_team_id": team_id,
|
|
"pa": 1,
|
|
"ab": 1,
|
|
"so": 1,
|
|
"outs": 1,
|
|
"hit": 0,
|
|
"homerun": 0,
|
|
"on_base_code": "000",
|
|
"inning_half": "bot",
|
|
"inning_num": 1,
|
|
"batting_order": 1,
|
|
"starting_outs": 0,
|
|
"away_score": 0,
|
|
"home_score": 0,
|
|
}
|
|
)
|
|
return plays
|
|
|
|
|
|
def build_decision_data(
|
|
game_id: int,
|
|
pitcher_id: int,
|
|
team_id: int,
|
|
season: int,
|
|
) -> dict:
|
|
"""Build a minimal pitcher decision payload for refractor testing.
|
|
|
|
Returns a decisions wrapper dict containing a single no-decision start
|
|
entry. All win/loss/hold/save flags default to 0; is_start is True
|
|
so the pitcher accrues IP-based refractor value from the associated plays.
|
|
|
|
Args:
|
|
game_id: ID of the game the decision belongs to.
|
|
pitcher_id: Card/player ID of the pitcher.
|
|
team_id: Team ID for pitcher_team_id.
|
|
season: Season number for the decision record.
|
|
|
|
Returns:
|
|
Dict with key "decisions" containing a list with one decision dict.
|
|
"""
|
|
return {
|
|
"decisions": [
|
|
{
|
|
"game_id": game_id,
|
|
"season": season,
|
|
"week": 1,
|
|
"pitcher_id": pitcher_id,
|
|
"pitcher_team_id": team_id,
|
|
"win": 0,
|
|
"loss": 0,
|
|
"hold": 0,
|
|
"is_save": 0,
|
|
"is_start": True,
|
|
"b_save": 0,
|
|
}
|
|
]
|
|
}
|