fix: batch BattingCard/BattingCardRatings lookups in lineup builder (#18)

Replace per-player get_or_none() calls in get_bratings() with two bulk
SELECT queries before the position loop, keyed by player_id and card+hand.
This reduces DB round trips from O(3N) to O(2) for all lineup difficulties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-03-03 17:34:08 -06:00
parent 8227b57875
commit 62b205bde2

View File

@ -354,17 +354,35 @@ async def get_team_lineup(
"DH": {"player": None, "vl": None, "vr": None, "ops": 0},
}
# Batch-fetch BattingCards and ratings for all candidate players to avoid
# per-player DB round trips inside the lineup construction loop below.
if backup_players is not None:
_batch_bcards = BattingCard.select().where(
(BattingCard.player << legal_players)
| (BattingCard.player << backup_players)
)
else:
_batch_bcards = BattingCard.select().where(BattingCard.player << legal_players)
_batting_cards_by_player = {bc.player_id: bc for bc in _batch_bcards}
_all_bratings = (
BattingCardRatings.select().where(
BattingCardRatings.battingcard << list(_batting_cards_by_player.values())
)
if _batting_cards_by_player
else []
)
_ratings_by_card_hand = {}
for _r in _all_bratings:
_ratings_by_card_hand.setdefault(_r.battingcard_id, {})[_r.vs_hand] = _r
def get_bratings(player_id):
this_bcard = BattingCard.get_or_none(BattingCard.player_id == player_id)
vl_ratings = BattingCardRatings.get_or_none(
BattingCardRatings.battingcard == this_bcard,
BattingCardRatings.vs_hand == "L",
this_bcard = _batting_cards_by_player.get(player_id)
card_ratings = (
_ratings_by_card_hand.get(this_bcard.id, {}) if this_bcard else {}
)
vl_ratings = card_ratings.get("L")
vr_ratings = card_ratings.get("R")
vl_ops = vl_ratings.obp + vl_ratings.slg
vr_ratings = BattingCardRatings.get_or_none(
BattingCardRatings.battingcard == this_bcard,
BattingCardRatings.vs_hand == "R",
)
vr_ops = vr_ratings.obp + vr_ratings.slg
return (
model_to_dict(vl_ratings),