feat: add play/game/decision data builders for refractor test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
777e6de3de
commit
2067a02a23
@ -41,3 +41,173 @@ def calculate_plays_needed(gap: int, card_type: str) -> dict:
|
||||
"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,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,7 +2,13 @@ import math
|
||||
|
||||
import pytest
|
||||
|
||||
from helpers.refractor_test_data import calculate_plays_needed
|
||||
from helpers.refractor_test_data import (
|
||||
build_batter_plays,
|
||||
build_decision_data,
|
||||
build_game_data,
|
||||
build_pitcher_plays,
|
||||
calculate_plays_needed,
|
||||
)
|
||||
|
||||
|
||||
class TestCalculatePlaysNeeded:
|
||||
@ -47,3 +53,136 @@ class TestCalculatePlaysNeeded:
|
||||
"""If already at threshold, still need 1 play to push over."""
|
||||
result = calculate_plays_needed(gap=0, card_type="batter")
|
||||
assert result["num_plays"] == 1
|
||||
|
||||
|
||||
class TestBuildGameData:
|
||||
"""Test synthetic game record construction for refractor testing.
|
||||
|
||||
build_game_data creates a self-play game (team vs itself) with
|
||||
game_type='test' and all score/ranking fields zeroed out. This
|
||||
gives the minimum valid game payload to POST to the API.
|
||||
"""
|
||||
|
||||
def test_basic_structure(self):
|
||||
"""Core IDs, type flags, and boolean fields are correct."""
|
||||
result = build_game_data(team_id=31, season=11)
|
||||
assert result["away_team_id"] == 31
|
||||
assert result["home_team_id"] == 31
|
||||
assert result["season"] == 11
|
||||
assert result["game_type"] == "test"
|
||||
assert result["short_game"] is True
|
||||
assert result["ranked"] is False
|
||||
assert result["forfeit"] is False
|
||||
|
||||
def test_score_reflects_zero(self):
|
||||
"""Scores start at zero — no actual game was simulated."""
|
||||
result = build_game_data(team_id=31, season=11)
|
||||
assert result["away_score"] == 0
|
||||
assert result["home_score"] == 0
|
||||
|
||||
|
||||
class TestBuildBatterPlays:
|
||||
"""Test synthetic HR play construction for batter refractor testing.
|
||||
|
||||
Each play is a solo HR: PA=1, AB=1, H=1, HR=1, R=1, RBI=1.
|
||||
Structural fields are filled with safe defaults (inning 1, bot half,
|
||||
no runners on base, zero scores). play_num is sequential from 1.
|
||||
"""
|
||||
|
||||
def test_correct_count(self):
|
||||
"""num_plays controls how many play dicts are returned."""
|
||||
plays = build_batter_plays(
|
||||
game_id=1, batter_id=100, team_id=31, pitcher_id=200, num_plays=3
|
||||
)
|
||||
assert len(plays) == 3
|
||||
|
||||
def test_play_fields(self):
|
||||
"""Each play has correct IDs and HR stat values."""
|
||||
plays = build_batter_plays(
|
||||
game_id=1, batter_id=100, team_id=31, pitcher_id=200, num_plays=1
|
||||
)
|
||||
play = plays[0]
|
||||
assert play["game_id"] == 1
|
||||
assert play["batter_id"] == 100
|
||||
assert play["batter_team_id"] == 31
|
||||
assert play["pitcher_id"] == 200
|
||||
assert play["pitcher_team_id"] == 31
|
||||
assert play["pa"] == 1
|
||||
assert play["ab"] == 1
|
||||
assert play["hit"] == 1
|
||||
assert play["homerun"] == 1
|
||||
assert play["run"] == 1
|
||||
assert play["rbi"] == 1
|
||||
|
||||
def test_play_nums_sequential(self):
|
||||
"""play_num increments from 1 for each play in the batch."""
|
||||
plays = build_batter_plays(
|
||||
game_id=1, batter_id=100, team_id=31, pitcher_id=200, num_plays=4
|
||||
)
|
||||
assert [p["play_num"] for p in plays] == [1, 2, 3, 4]
|
||||
|
||||
def test_required_structural_fields(self):
|
||||
"""Structural fields are filled with safe defaults for API acceptance."""
|
||||
plays = build_batter_plays(
|
||||
game_id=1, batter_id=100, team_id=31, pitcher_id=200, num_plays=1
|
||||
)
|
||||
play = plays[0]
|
||||
assert play["on_base_code"] == "000"
|
||||
assert play["inning_half"] == "bot"
|
||||
assert play["inning_num"] == 1
|
||||
assert play["batting_order"] == 1
|
||||
assert play["starting_outs"] == 0
|
||||
assert play["away_score"] == 0
|
||||
assert play["home_score"] == 0
|
||||
|
||||
|
||||
class TestBuildPitcherPlays:
|
||||
"""Test synthetic strikeout play construction for pitcher refractor testing.
|
||||
|
||||
Each play is a K: PA=1, AB=1, SO=1, outs=1, hit=0, homerun=0.
|
||||
Structural fields mirror the batter play defaults.
|
||||
"""
|
||||
|
||||
def test_correct_count(self):
|
||||
"""num_plays controls how many play dicts are returned."""
|
||||
plays = build_pitcher_plays(
|
||||
game_id=1, pitcher_id=200, team_id=31, batter_id=100, num_plays=5
|
||||
)
|
||||
assert len(plays) == 5
|
||||
|
||||
def test_play_fields(self):
|
||||
"""Each play has correct IDs and K stat values."""
|
||||
plays = build_pitcher_plays(
|
||||
game_id=1, pitcher_id=200, team_id=31, batter_id=100, num_plays=1
|
||||
)
|
||||
play = plays[0]
|
||||
assert play["game_id"] == 1
|
||||
assert play["pitcher_id"] == 200
|
||||
assert play["pitcher_team_id"] == 31
|
||||
assert play["batter_id"] == 100
|
||||
assert play["batter_team_id"] == 31
|
||||
assert play["pa"] == 1
|
||||
assert play["ab"] == 1
|
||||
assert play["so"] == 1
|
||||
assert play["outs"] == 1
|
||||
assert play["hit"] == 0
|
||||
assert play["homerun"] == 0
|
||||
|
||||
|
||||
class TestBuildDecisionData:
|
||||
"""Test synthetic pitcher decision construction for refractor testing.
|
||||
|
||||
Returns a decisions payload with a single no-decision start entry.
|
||||
All win/loss/hold/save flags default to 0; is_start is True.
|
||||
"""
|
||||
|
||||
def test_basic_structure(self):
|
||||
"""Decisions payload has correct IDs, season, and default flags."""
|
||||
result = build_decision_data(game_id=1, pitcher_id=200, team_id=31, season=11)
|
||||
assert result["decisions"][0]["game_id"] == 1
|
||||
assert result["decisions"][0]["pitcher_id"] == 200
|
||||
assert result["decisions"][0]["pitcher_team_id"] == 31
|
||||
assert result["decisions"][0]["season"] == 11
|
||||
assert result["decisions"][0]["is_start"] is True
|
||||
assert result["decisions"][0]["win"] == 0
|
||||
assert result["decisions"][0]["loss"] == 0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user