Merge pull request 'fix: preserve batter at plate when half-inning ends on caught stealing' (#11) from fix/cs-batter-skip-1.9.1 into main
All checks were successful
Build Docker Image / build (push) Successful in 1m31s

This commit is contained in:
cal 2026-02-11 04:37:53 +00:00
commit 128b669b85
3 changed files with 94 additions and 2 deletions

View File

@ -1 +1 @@
1.9.0
1.9.1

View File

@ -759,7 +759,7 @@ def complete_play(session: Session, this_play: Play):
opponent_play = get_last_team_play(
session, this_play.game, this_play.pitcher.team
)
nbo = opponent_play.batting_order + 1
nbo = opponent_play.batting_order + 1 if opponent_play.pa == 1 else opponent_play.batting_order
except PlayNotFoundException as e:
logger.info(
f"logic_gameplay - complete_play - No last play found for {this_play.pitcher.team.sname}, setting upcoming batting order to 1"

View File

@ -569,3 +569,95 @@ def test_pinch_runner_entry_and_scoring(session: Session):
# Entry play should NOT be in this list (PA=0)
assert entry_play not in pitcher_plays
async def test_cs_end_of_inning_preserves_batter(session: Session):
"""
Test that when a half-inning ends on a Caught Stealing, the batter who was
at the plate (but did not complete a plate appearance) leads off the next
time their team bats.
Scenario:
Top 1 - Away team batting:
Batter 1 (bo=1): strikeout 1 out
Batter 2 (bo=2): strikeout 2 outs
Batter 3 (bo=3): single, reaches first
Batter 4 (bo=4): at the plate, runner caught stealing 3 outs, side switch
Bot 1 - Home team batting:
Batter 1 (bo=1): strikeout 1 out
Batter 2 (bo=2): strikeout 2 outs
Batter 3 (bo=3): strikeout 3 outs, side switch
Top 2 - Away team batting:
The next batter should be Batter 4 (bo=4), NOT Batter 5 (bo=5),
because Batter 4 never completed a plate appearance.
"""
this_game = session.get(Game, 3)
play = this_game.initialize_play(session)
# --- TOP 1 ---
# Batter 1 (bo=1): strikeout → 1 out
assert play.batting_order == 1
assert play.inning_half == 'top'
play = await strikeouts(session, None, play)
play = complete_play(session, play)
# Batter 2 (bo=2): strikeout → 2 outs
assert play.batting_order == 2
assert play.starting_outs == 1
play = await strikeouts(session, None, play)
play = complete_play(session, play)
# Batter 3 (bo=3): single → runner on first
assert play.batting_order == 3
assert play.starting_outs == 2
play = await singles(session, None, play, '*')
play = complete_play(session, play)
# Batter 4 (bo=4) is at the plate with runner on first
assert play.batting_order == 4
assert play.starting_outs == 2
assert play.on_first is not None
assert play.inning_half == 'top'
# Runner on first is caught stealing → 3rd out, side switch
play = await steals(session, None, play, 'caught-stealing', to_base=2)
assert play.pa == 0 # CS is not a plate appearance
assert play.outs == 1 # CS records one out
play = complete_play(session, play)
# Should now be bottom of inning 1
assert play.inning_half == 'bot'
assert play.inning_num == 1
assert play.is_new_inning is True
# --- BOT 1 ---
# Home batter 1 (bo=1): strikeout → 1 out
assert play.batting_order == 1
play = await strikeouts(session, None, play)
play = complete_play(session, play)
# Home batter 2 (bo=2): strikeout → 2 outs
assert play.batting_order == 2
play = await strikeouts(session, None, play)
play = complete_play(session, play)
# Home batter 3 (bo=3): strikeout → 3 outs, side switch
assert play.batting_order == 3
play = await strikeouts(session, None, play)
play = complete_play(session, play)
# --- TOP 2 ---
# Should be top of inning 2, and batter 4 (bo=4) should lead off
# because they never completed a PA in the previous half-inning
assert play.inning_half == 'top'
assert play.inning_num == 2
assert play.is_new_inning is True
assert play.batting_order == 4, (
f"Expected batter 4 to lead off (never completed PA), "
f"but got batter {play.batting_order}"
)