From a814aadd616b12efd0135d648c6a3745f7f088ec Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 12 Dec 2025 15:01:48 -0600 Subject: [PATCH] Optimize draft sheet batch writes to single API call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed write_picks_batch() from 105 individual API calls to 1 batch call - Builds 2D array covering full pick range and writes in single update_values() - Eliminates Google Sheets 429 rate limiting during resync operations - Reduces resync time from ~2 minutes to seconds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- VERSION | 2 +- services/draft_sheet_service.py | 58 ++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/VERSION b/VERSION index ad22619..0f5dfbe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.24.0 +2.24.1 diff --git a/services/draft_sheet_service.py b/services/draft_sheet_service.py index 61af59b..ba8d320 100644 --- a/services/draft_sheet_service.py +++ b/services/draft_sheet_service.py @@ -129,6 +129,8 @@ class DraftSheetService(SheetsService): Write multiple draft picks to the sheet in a single batch operation. Used for resync operations to repopulate the entire sheet from database. + Uses true batch updates to write all picks in a single API call, + avoiding rate limiting issues. Args: season: Draft season number @@ -169,41 +171,51 @@ class DraftSheetService(SheetsService): self._config.draft_sheet_worksheet ) - # Sort picks by overall to write in order + # Sort picks by overall to find range bounds sorted_picks = sorted(picks, key=lambda p: p[0]) - # Build batch data - each pick goes to its calculated row - # We'll write one row at a time to handle non-contiguous picks - success_count = 0 - failure_count = 0 + # Find min and max overall to determine row range + min_overall = sorted_picks[0][0] + max_overall = sorted_picks[-1][0] + # Build a 2D array for the entire range (sparse - empty rows for missing picks) + # Row index 0 = min_overall, row index N = max_overall + num_rows = max_overall - min_overall + 1 + batch_data: List[List[str]] = [['', '', '', ''] for _ in range(num_rows)] + + # Populate the batch data array for overall, orig_owner, owner, player_name, swar in sorted_picks: - try: - pick_data = [[orig_owner, owner, player_name, swar]] - row = overall + 1 - start_column = self._config.draft_sheet_start_column - cell_range = f'{start_column}{row}' + row_index = overall - min_overall + batch_data[row_index] = [orig_owner, owner, player_name, str(swar)] - await loop.run_in_executor( - None, - lambda cr=cell_range, pd=pick_data: worksheet.update_values( - crange=cr, values=pd - ) - ) - success_count += 1 - except Exception as e: - self.logger.error(f"Failed to write pick {overall}: {e}") - failure_count += 1 + # Calculate the cell range for the batch write + start_row = min_overall + 1 # +1 for header row + start_column = self._config.draft_sheet_start_column + end_column = chr(ord(start_column) + 3) # 4 columns: D -> G + end_row = max_overall + 1 + + cell_range = f'{start_column}{start_row}:{end_column}{end_row}' self.logger.info( - f"Batch write complete: {success_count} succeeded, {failure_count} failed", + f"Writing {len(picks)} picks in single batch to range {cell_range}", + season=season + ) + + # Write all picks in a single API call + await loop.run_in_executor( + None, + lambda: worksheet.update_values(crange=cell_range, values=batch_data) + ) + + self.logger.info( + f"Batch write complete: {len(picks)} picks written successfully", season=season, total_picks=len(picks) ) - return (success_count, failure_count) + return (len(picks), 0) except Exception as e: - self.logger.error(f"Failed to initialize batch write: {e}", season=season) + self.logger.error(f"Failed batch write: {e}", season=season) return (0, len(picks)) async def clear_picks_range(