From 62b205bde2cd6cb0c25eb42d3cf7acdc20f074ba Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Tue, 3 Mar 2026 17:34:08 -0600 Subject: [PATCH] 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 --- app/routers_v2/teams.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/app/routers_v2/teams.py b/app/routers_v2/teams.py index a2b31d8..ff93512 100644 --- a/app/routers_v2/teams.py +++ b/app/routers_v2/teams.py @@ -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),