Merge pull request 'fix: key plays score text shows "tied at X" correctly (closes #48)' (#52) from fix/key-plays-tied-score into next-release
Reviewed-on: #52
This commit is contained in:
commit
98d47a1262
160
models/play.py
160
models/play.py
@ -7,6 +7,7 @@ This model matches the database schema at /database/app/routers_v3/stratplay.py.
|
|||||||
NOTE: ID fields have corresponding optional model object fields for API-populated nested data.
|
NOTE: ID fields have corresponding optional model object fields for API-populated nested data.
|
||||||
Future enhancement could add validators to ensure consistency between ID and model fields.
|
Future enhancement could add validators to ensure consistency between ID and model fields.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional, Literal
|
from typing import Optional, Literal
|
||||||
from pydantic import Field, field_validator
|
from pydantic import Field, field_validator
|
||||||
from models.base import SBABaseModel
|
from models.base import SBABaseModel
|
||||||
@ -28,9 +29,11 @@ class Play(SBABaseModel):
|
|||||||
game: Optional[Game] = Field(None, description="Game object (API-populated)")
|
game: Optional[Game] = Field(None, description="Game object (API-populated)")
|
||||||
play_num: int = Field(..., description="Sequential play number in game")
|
play_num: int = Field(..., description="Sequential play number in game")
|
||||||
pitcher_id: Optional[int] = Field(None, description="Pitcher ID")
|
pitcher_id: Optional[int] = Field(None, description="Pitcher ID")
|
||||||
pitcher: Optional[Player] = Field(None, description="Pitcher object (API-populated)")
|
pitcher: Optional[Player] = Field(
|
||||||
|
None, description="Pitcher object (API-populated)"
|
||||||
|
)
|
||||||
on_base_code: str = Field(..., description="Base runners code (e.g., '100', '011')")
|
on_base_code: str = Field(..., description="Base runners code (e.g., '100', '011')")
|
||||||
inning_half: Literal['top', 'bot'] = Field(..., description="Inning half")
|
inning_half: Literal["top", "bot"] = Field(..., description="Inning half")
|
||||||
inning_num: int = Field(..., description="Inning number")
|
inning_num: int = Field(..., description="Inning number")
|
||||||
batting_order: int = Field(..., description="Batting order position")
|
batting_order: int = Field(..., description="Batting order position")
|
||||||
starting_outs: int = Field(..., description="Outs at start of play")
|
starting_outs: int = Field(..., description="Outs at start of play")
|
||||||
@ -41,21 +44,37 @@ class Play(SBABaseModel):
|
|||||||
batter_id: Optional[int] = Field(None, description="Batter ID")
|
batter_id: Optional[int] = Field(None, description="Batter ID")
|
||||||
batter: Optional[Player] = Field(None, description="Batter object (API-populated)")
|
batter: Optional[Player] = Field(None, description="Batter object (API-populated)")
|
||||||
batter_team_id: Optional[int] = Field(None, description="Batter's team ID")
|
batter_team_id: Optional[int] = Field(None, description="Batter's team ID")
|
||||||
batter_team: Optional[Team] = Field(None, description="Batter's team object (API-populated)")
|
batter_team: Optional[Team] = Field(
|
||||||
|
None, description="Batter's team object (API-populated)"
|
||||||
|
)
|
||||||
pitcher_team_id: Optional[int] = Field(None, description="Pitcher's team ID")
|
pitcher_team_id: Optional[int] = Field(None, description="Pitcher's team ID")
|
||||||
pitcher_team: Optional[Team] = Field(None, description="Pitcher's team object (API-populated)")
|
pitcher_team: Optional[Team] = Field(
|
||||||
|
None, description="Pitcher's team object (API-populated)"
|
||||||
|
)
|
||||||
batter_pos: Optional[str] = Field(None, description="Batter's position")
|
batter_pos: Optional[str] = Field(None, description="Batter's position")
|
||||||
|
|
||||||
# Base runner information
|
# Base runner information
|
||||||
on_first_id: Optional[int] = Field(None, description="Runner on first ID")
|
on_first_id: Optional[int] = Field(None, description="Runner on first ID")
|
||||||
on_first: Optional[Player] = Field(None, description="Runner on first object (API-populated)")
|
on_first: Optional[Player] = Field(
|
||||||
on_first_final: Optional[int] = Field(None, description="Runner on first final base")
|
None, description="Runner on first object (API-populated)"
|
||||||
|
)
|
||||||
|
on_first_final: Optional[int] = Field(
|
||||||
|
None, description="Runner on first final base"
|
||||||
|
)
|
||||||
on_second_id: Optional[int] = Field(None, description="Runner on second ID")
|
on_second_id: Optional[int] = Field(None, description="Runner on second ID")
|
||||||
on_second: Optional[Player] = Field(None, description="Runner on second object (API-populated)")
|
on_second: Optional[Player] = Field(
|
||||||
on_second_final: Optional[int] = Field(None, description="Runner on second final base")
|
None, description="Runner on second object (API-populated)"
|
||||||
|
)
|
||||||
|
on_second_final: Optional[int] = Field(
|
||||||
|
None, description="Runner on second final base"
|
||||||
|
)
|
||||||
on_third_id: Optional[int] = Field(None, description="Runner on third ID")
|
on_third_id: Optional[int] = Field(None, description="Runner on third ID")
|
||||||
on_third: Optional[Player] = Field(None, description="Runner on third object (API-populated)")
|
on_third: Optional[Player] = Field(
|
||||||
on_third_final: Optional[int] = Field(None, description="Runner on third final base")
|
None, description="Runner on third object (API-populated)"
|
||||||
|
)
|
||||||
|
on_third_final: Optional[int] = Field(
|
||||||
|
None, description="Runner on third final base"
|
||||||
|
)
|
||||||
batter_final: Optional[int] = Field(None, description="Batter's final base")
|
batter_final: Optional[int] = Field(None, description="Batter's final base")
|
||||||
|
|
||||||
# Statistical fields (all default to 0)
|
# Statistical fields (all default to 0)
|
||||||
@ -96,17 +115,27 @@ class Play(SBABaseModel):
|
|||||||
|
|
||||||
# Defensive players
|
# Defensive players
|
||||||
catcher_id: Optional[int] = Field(None, description="Catcher ID")
|
catcher_id: Optional[int] = Field(None, description="Catcher ID")
|
||||||
catcher: Optional[Player] = Field(None, description="Catcher object (API-populated)")
|
catcher: Optional[Player] = Field(
|
||||||
|
None, description="Catcher object (API-populated)"
|
||||||
|
)
|
||||||
catcher_team_id: Optional[int] = Field(None, description="Catcher's team ID")
|
catcher_team_id: Optional[int] = Field(None, description="Catcher's team ID")
|
||||||
catcher_team: Optional[Team] = Field(None, description="Catcher's team object (API-populated)")
|
catcher_team: Optional[Team] = Field(
|
||||||
|
None, description="Catcher's team object (API-populated)"
|
||||||
|
)
|
||||||
defender_id: Optional[int] = Field(None, description="Defender ID")
|
defender_id: Optional[int] = Field(None, description="Defender ID")
|
||||||
defender: Optional[Player] = Field(None, description="Defender object (API-populated)")
|
defender: Optional[Player] = Field(
|
||||||
|
None, description="Defender object (API-populated)"
|
||||||
|
)
|
||||||
defender_team_id: Optional[int] = Field(None, description="Defender's team ID")
|
defender_team_id: Optional[int] = Field(None, description="Defender's team ID")
|
||||||
defender_team: Optional[Team] = Field(None, description="Defender's team object (API-populated)")
|
defender_team: Optional[Team] = Field(
|
||||||
|
None, description="Defender's team object (API-populated)"
|
||||||
|
)
|
||||||
runner_id: Optional[int] = Field(None, description="Runner ID")
|
runner_id: Optional[int] = Field(None, description="Runner ID")
|
||||||
runner: Optional[Player] = Field(None, description="Runner object (API-populated)")
|
runner: Optional[Player] = Field(None, description="Runner object (API-populated)")
|
||||||
runner_team_id: Optional[int] = Field(None, description="Runner's team ID")
|
runner_team_id: Optional[int] = Field(None, description="Runner's team ID")
|
||||||
runner_team: Optional[Team] = Field(None, description="Runner's team object (API-populated)")
|
runner_team: Optional[Team] = Field(
|
||||||
|
None, description="Runner's team object (API-populated)"
|
||||||
|
)
|
||||||
|
|
||||||
# Defensive plays
|
# Defensive plays
|
||||||
check_pos: Optional[str] = Field(None, description="Position checked")
|
check_pos: Optional[str] = Field(None, description="Position checked")
|
||||||
@ -126,35 +155,35 @@ class Play(SBABaseModel):
|
|||||||
hand_pitching: Optional[str] = Field(None, description="Pitcher handedness (L/R)")
|
hand_pitching: Optional[str] = Field(None, description="Pitcher handedness (L/R)")
|
||||||
|
|
||||||
# Validators from database model
|
# Validators from database model
|
||||||
@field_validator('on_first_final')
|
@field_validator("on_first_final")
|
||||||
@classmethod
|
@classmethod
|
||||||
def no_final_if_no_runner_one(cls, v, info):
|
def no_final_if_no_runner_one(cls, v, info):
|
||||||
"""Validate on_first_final is None if no runner on first."""
|
"""Validate on_first_final is None if no runner on first."""
|
||||||
if info.data.get('on_first_id') is None:
|
if info.data.get("on_first_id") is None:
|
||||||
return None
|
return None
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@field_validator('on_second_final')
|
@field_validator("on_second_final")
|
||||||
@classmethod
|
@classmethod
|
||||||
def no_final_if_no_runner_two(cls, v, info):
|
def no_final_if_no_runner_two(cls, v, info):
|
||||||
"""Validate on_second_final is None if no runner on second."""
|
"""Validate on_second_final is None if no runner on second."""
|
||||||
if info.data.get('on_second_id') is None:
|
if info.data.get("on_second_id") is None:
|
||||||
return None
|
return None
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@field_validator('on_third_final')
|
@field_validator("on_third_final")
|
||||||
@classmethod
|
@classmethod
|
||||||
def no_final_if_no_runner_three(cls, v, info):
|
def no_final_if_no_runner_three(cls, v, info):
|
||||||
"""Validate on_third_final is None if no runner on third."""
|
"""Validate on_third_final is None if no runner on third."""
|
||||||
if info.data.get('on_third_id') is None:
|
if info.data.get("on_third_id") is None:
|
||||||
return None
|
return None
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@field_validator('batter_final')
|
@field_validator("batter_final")
|
||||||
@classmethod
|
@classmethod
|
||||||
def no_final_if_no_batter(cls, v, info):
|
def no_final_if_no_batter(cls, v, info):
|
||||||
"""Validate batter_final is None if no batter."""
|
"""Validate batter_final is None if no batter."""
|
||||||
if info.data.get('batter_id') is None:
|
if info.data.get("batter_id") is None:
|
||||||
return None
|
return None
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@ -170,25 +199,28 @@ class Play(SBABaseModel):
|
|||||||
Formatted string like: "Top 3: Player Name (NYY) homers in 2 runs"
|
Formatted string like: "Top 3: Player Name (NYY) homers in 2 runs"
|
||||||
"""
|
"""
|
||||||
# Determine inning text
|
# Determine inning text
|
||||||
inning_text = f"{'Top' if self.inning_half == 'top' else 'Bot'} {self.inning_num}"
|
inning_text = (
|
||||||
|
f"{'Top' if self.inning_half == 'top' else 'Bot'} {self.inning_num}"
|
||||||
|
)
|
||||||
|
|
||||||
# Determine team abbreviation based on inning half
|
# Determine team abbreviation based on inning half
|
||||||
away_score = self.away_score
|
away_score = self.away_score
|
||||||
home_score = self.home_score
|
home_score = self.home_score
|
||||||
if self.inning_half == 'top':
|
if self.inning_half == "top":
|
||||||
away_score += self.rbi
|
away_score += self.rbi
|
||||||
else:
|
else:
|
||||||
home_score += self.rbi
|
home_score += self.rbi
|
||||||
|
|
||||||
score_text = f'tied at {home_score}'
|
|
||||||
if home_score > away_score:
|
if home_score > away_score:
|
||||||
score_text = f'{home_team.abbrev} up {home_score}-{away_score}'
|
score_text = f"{home_team.abbrev} up {home_score}-{away_score}"
|
||||||
|
elif away_score > home_score:
|
||||||
|
score_text = f"{away_team.abbrev} up {away_score}-{home_score}"
|
||||||
else:
|
else:
|
||||||
score_text = f'{away_team.abbrev} up {away_score}-{home_score}'
|
score_text = f"tied at {home_score}"
|
||||||
|
|
||||||
# Build play description based on play type
|
# Build play description based on play type
|
||||||
description_parts = []
|
description_parts = []
|
||||||
which_player = 'batter'
|
which_player = "batter"
|
||||||
|
|
||||||
# Offensive plays
|
# Offensive plays
|
||||||
if self.homerun > 0:
|
if self.homerun > 0:
|
||||||
@ -199,63 +231,79 @@ class Play(SBABaseModel):
|
|||||||
elif self.triple > 0:
|
elif self.triple > 0:
|
||||||
description_parts.append("triples")
|
description_parts.append("triples")
|
||||||
if self.rbi > 0:
|
if self.rbi > 0:
|
||||||
description_parts.append(f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}")
|
description_parts.append(
|
||||||
|
f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}"
|
||||||
|
)
|
||||||
elif self.double > 0:
|
elif self.double > 0:
|
||||||
description_parts.append("doubles")
|
description_parts.append("doubles")
|
||||||
if self.rbi > 0:
|
if self.rbi > 0:
|
||||||
description_parts.append(f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}")
|
description_parts.append(
|
||||||
|
f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}"
|
||||||
|
)
|
||||||
elif self.hit > 0:
|
elif self.hit > 0:
|
||||||
description_parts.append("singles")
|
description_parts.append("singles")
|
||||||
if self.rbi > 0:
|
if self.rbi > 0:
|
||||||
description_parts.append(f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}")
|
description_parts.append(
|
||||||
|
f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}"
|
||||||
|
)
|
||||||
elif self.bb > 0:
|
elif self.bb > 0:
|
||||||
if self.ibb > 0:
|
if self.ibb > 0:
|
||||||
description_parts.append("intentionally walked")
|
description_parts.append("intentionally walked")
|
||||||
else:
|
else:
|
||||||
description_parts.append("walks")
|
description_parts.append("walks")
|
||||||
if self.rbi > 0:
|
if self.rbi > 0:
|
||||||
description_parts.append(f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}")
|
description_parts.append(
|
||||||
|
f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}"
|
||||||
|
)
|
||||||
elif self.hbp > 0:
|
elif self.hbp > 0:
|
||||||
description_parts.append("hit by pitch")
|
description_parts.append("hit by pitch")
|
||||||
if self.rbi > 0:
|
if self.rbi > 0:
|
||||||
description_parts.append(f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}")
|
description_parts.append(
|
||||||
|
f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}"
|
||||||
|
)
|
||||||
elif self.sac > 0:
|
elif self.sac > 0:
|
||||||
description_parts.append("sacrifice fly")
|
description_parts.append("sacrifice fly")
|
||||||
if self.rbi > 0:
|
if self.rbi > 0:
|
||||||
description_parts.append(f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}")
|
description_parts.append(
|
||||||
|
f"scoring {self.rbi} run{'s' if self.rbi > 1 else ''}"
|
||||||
|
)
|
||||||
elif self.sb > 0:
|
elif self.sb > 0:
|
||||||
description_parts.append("steals a base")
|
description_parts.append("steals a base")
|
||||||
elif self.cs > 0:
|
elif self.cs > 0:
|
||||||
which_player = 'catcher'
|
which_player = "catcher"
|
||||||
description_parts.append("guns down a baserunner")
|
description_parts.append("guns down a baserunner")
|
||||||
elif self.gidp > 0:
|
elif self.gidp > 0:
|
||||||
description_parts.append("grounds into double play")
|
description_parts.append("grounds into double play")
|
||||||
elif self.so > 0:
|
elif self.so > 0:
|
||||||
which_player = 'pitcher'
|
which_player = "pitcher"
|
||||||
description_parts.append(f"gets a strikeout")
|
description_parts.append(f"gets a strikeout")
|
||||||
# Defensive plays
|
# Defensive plays
|
||||||
elif self.error > 0:
|
elif self.error > 0:
|
||||||
which_player = 'defender'
|
which_player = "defender"
|
||||||
description_parts.append("commits an error")
|
description_parts.append("commits an error")
|
||||||
if self.rbi > 0:
|
if self.rbi > 0:
|
||||||
description_parts.append(f"allowing {self.rbi} run{'s' if self.rbi > 1 else ''}")
|
description_parts.append(
|
||||||
|
f"allowing {self.rbi} run{'s' if self.rbi > 1 else ''}"
|
||||||
|
)
|
||||||
elif self.wild_pitch > 0:
|
elif self.wild_pitch > 0:
|
||||||
which_player = 'pitcher'
|
which_player = "pitcher"
|
||||||
description_parts.append("uncorks a wild pitch")
|
description_parts.append("uncorks a wild pitch")
|
||||||
elif self.passed_ball > 0:
|
elif self.passed_ball > 0:
|
||||||
which_player = 'catcher'
|
which_player = "catcher"
|
||||||
description_parts.append("passed ball")
|
description_parts.append("passed ball")
|
||||||
elif self.pick_off > 0:
|
elif self.pick_off > 0:
|
||||||
which_player = 'runner'
|
which_player = "runner"
|
||||||
description_parts.append("picked off")
|
description_parts.append("picked off")
|
||||||
elif self.balk > 0:
|
elif self.balk > 0:
|
||||||
which_player = 'pitcher'
|
which_player = "pitcher"
|
||||||
description_parts.append("balk")
|
description_parts.append("balk")
|
||||||
else:
|
else:
|
||||||
# Generic out
|
# Generic out
|
||||||
if self.outs > 0:
|
if self.outs > 0:
|
||||||
which_player = 'pitcher'
|
which_player = "pitcher"
|
||||||
description_parts.append(f'records out number {self.starting_outs + self.outs}')
|
description_parts.append(
|
||||||
|
f"records out number {self.starting_outs + self.outs}"
|
||||||
|
)
|
||||||
|
|
||||||
# Combine parts
|
# Combine parts
|
||||||
if description_parts:
|
if description_parts:
|
||||||
@ -264,18 +312,18 @@ class Play(SBABaseModel):
|
|||||||
play_desc = "makes a play"
|
play_desc = "makes a play"
|
||||||
|
|
||||||
player_dict = {
|
player_dict = {
|
||||||
'batter': self.batter,
|
"batter": self.batter,
|
||||||
'pitcher': self.pitcher,
|
"pitcher": self.pitcher,
|
||||||
'catcher': self.catcher,
|
"catcher": self.catcher,
|
||||||
'runner': self.runner,
|
"runner": self.runner,
|
||||||
'defender': self.defender
|
"defender": self.defender,
|
||||||
}
|
}
|
||||||
team_dict = {
|
team_dict = {
|
||||||
'batter': self.batter_team,
|
"batter": self.batter_team,
|
||||||
'pitcher': self.pitcher_team,
|
"pitcher": self.pitcher_team,
|
||||||
'catcher': self.catcher_team,
|
"catcher": self.catcher_team,
|
||||||
'runner': self.runner_team,
|
"runner": self.runner_team,
|
||||||
'defender': self.defender_team
|
"defender": self.defender_team,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Format: "Top 3: Derek Jeter (NYY) homers in 2 runs, NYY up 2-0"
|
# Format: "Top 3: Derek Jeter (NYY) homers in 2 runs, NYY up 2-0"
|
||||||
|
|||||||
129
tests/test_models_play.py
Normal file
129
tests/test_models_play.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
"""Tests for Play model descriptive_text method.
|
||||||
|
|
||||||
|
Covers score text generation for key plays display, specifically
|
||||||
|
ensuring tied games show 'tied at X' instead of 'Team up X-X'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from models.play import Play
|
||||||
|
from models.player import Player
|
||||||
|
from models.team import Team
|
||||||
|
|
||||||
|
|
||||||
|
def _make_team(abbrev: str) -> Team:
|
||||||
|
"""Create a minimal Team for descriptive_text tests."""
|
||||||
|
return Team.model_construct(
|
||||||
|
id=1,
|
||||||
|
abbrev=abbrev,
|
||||||
|
sname=abbrev,
|
||||||
|
lname=f"Team {abbrev}",
|
||||||
|
season=13,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_player(name: str, team: Team) -> Player:
|
||||||
|
"""Create a minimal Player for descriptive_text tests."""
|
||||||
|
return Player.model_construct(id=1, name=name, wara=0.0, season=13, team_id=team.id)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_play(**overrides) -> Play:
|
||||||
|
"""Create a Play with sensible defaults for descriptive_text tests."""
|
||||||
|
tst_team = _make_team("TST")
|
||||||
|
opp_team = _make_team("OPP")
|
||||||
|
defaults = dict(
|
||||||
|
id=1,
|
||||||
|
game_id=1,
|
||||||
|
play_num=1,
|
||||||
|
on_base_code="000",
|
||||||
|
inning_half="top",
|
||||||
|
inning_num=7,
|
||||||
|
batting_order=1,
|
||||||
|
starting_outs=2,
|
||||||
|
away_score=0,
|
||||||
|
home_score=0,
|
||||||
|
outs=1,
|
||||||
|
batter_id=10,
|
||||||
|
batter=_make_player("Test Batter", tst_team),
|
||||||
|
batter_team=tst_team,
|
||||||
|
pitcher_id=20,
|
||||||
|
pitcher=_make_player("Test Pitcher", opp_team),
|
||||||
|
pitcher_team=opp_team,
|
||||||
|
)
|
||||||
|
defaults.update(overrides)
|
||||||
|
return Play.model_construct(**defaults)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDescriptiveTextScoreText:
|
||||||
|
"""Tests for score text in Play.descriptive_text (issue #48)."""
|
||||||
|
|
||||||
|
def test_tied_score_shows_tied_at(self):
|
||||||
|
"""When scores are equal after the play, should show 'tied at X' not 'Team up X-X'."""
|
||||||
|
away = _make_team("BSG")
|
||||||
|
home = _make_team("DEN")
|
||||||
|
# Top 7: away scores 1 RBI, making it 2-2
|
||||||
|
play = _make_play(
|
||||||
|
inning_half="top",
|
||||||
|
inning_num=7,
|
||||||
|
away_score=1,
|
||||||
|
home_score=2,
|
||||||
|
rbi=1,
|
||||||
|
hit=1,
|
||||||
|
)
|
||||||
|
result = play.descriptive_text(away, home)
|
||||||
|
assert "tied at 2" in result
|
||||||
|
assert "up" not in result
|
||||||
|
|
||||||
|
def test_home_team_leading(self):
|
||||||
|
"""When home team leads, should show 'HOME up X-Y'."""
|
||||||
|
away = _make_team("BSG")
|
||||||
|
home = _make_team("DEN")
|
||||||
|
play = _make_play(
|
||||||
|
inning_half="top",
|
||||||
|
away_score=0,
|
||||||
|
home_score=3,
|
||||||
|
outs=1,
|
||||||
|
)
|
||||||
|
result = play.descriptive_text(away, home)
|
||||||
|
assert "DEN up 3-0" in result
|
||||||
|
|
||||||
|
def test_away_team_leading(self):
|
||||||
|
"""When away team leads, should show 'AWAY up X-Y'."""
|
||||||
|
away = _make_team("BSG")
|
||||||
|
home = _make_team("DEN")
|
||||||
|
play = _make_play(
|
||||||
|
inning_half="bot",
|
||||||
|
away_score=5,
|
||||||
|
home_score=2,
|
||||||
|
outs=1,
|
||||||
|
)
|
||||||
|
result = play.descriptive_text(away, home)
|
||||||
|
assert "BSG up 5-2" in result
|
||||||
|
|
||||||
|
def test_tied_at_zero(self):
|
||||||
|
"""Tied at 0-0 should show 'tied at 0'."""
|
||||||
|
away = _make_team("BSG")
|
||||||
|
home = _make_team("DEN")
|
||||||
|
play = _make_play(
|
||||||
|
inning_half="top",
|
||||||
|
away_score=0,
|
||||||
|
home_score=0,
|
||||||
|
outs=1,
|
||||||
|
)
|
||||||
|
result = play.descriptive_text(away, home)
|
||||||
|
assert "tied at 0" in result
|
||||||
|
|
||||||
|
def test_rbi_creates_tie_bottom_inning(self):
|
||||||
|
"""Bottom inning RBI that ties the game should show 'tied at X'."""
|
||||||
|
away = _make_team("BSG")
|
||||||
|
home = _make_team("DEN")
|
||||||
|
# Bot 5: home scores 2 RBI, tying at 4-4
|
||||||
|
play = _make_play(
|
||||||
|
inning_half="bot",
|
||||||
|
inning_num=5,
|
||||||
|
away_score=4,
|
||||||
|
home_score=2,
|
||||||
|
rbi=2,
|
||||||
|
hit=1,
|
||||||
|
)
|
||||||
|
result = play.descriptive_text(away, home)
|
||||||
|
assert "tied at 4" in result
|
||||||
|
assert "up" not in result
|
||||||
Loading…
Reference in New Issue
Block a user