diff --git a/commands/league/submit_scorecard.py b/commands/league/submit_scorecard.py index fca00ab..caa2985 100644 --- a/commands/league/submit_scorecard.py +++ b/commands/league/submit_scorecard.py @@ -210,17 +210,41 @@ class SubmitScorecardCommands(commands.Cog): game_id = scheduled_game.id - # Phase 6: Read Scorecard Data + # Phase 6: Read ALL Scorecard Data (before any DB writes) + # Reading everything first prevents partial commits if the + # spreadsheet has formula errors (e.g. #N/A in pitching decisions) await interaction.edit_original_response( - content="📊 Reading play-by-play data..." + content="📊 Reading scorecard data..." ) plays_data = await self.sheets_service.read_playtable_data(scorecard) + box_score = await self.sheets_service.read_box_score(scorecard) + decisions_data = await self.sheets_service.read_pitching_decisions( + scorecard + ) # Add game_id to each play for play in plays_data: play["game_id"] = game_id + # Add game metadata to each decision + for decision in decisions_data: + decision["game_id"] = game_id + decision["season"] = current.season + decision["week"] = setup_data["week"] + decision["game_num"] = setup_data["game_num"] + + # Validate WP and LP exist and fetch Player objects + wp, lp, sv, holders, _blown_saves = ( + await decision_service.find_winning_losing_pitchers(decisions_data) + ) + + if wp is None or lp is None: + await interaction.edit_original_response( + content="❌ Your card is missing either a Winning Pitcher or Losing Pitcher" + ) + return + # Phase 7: POST Plays await interaction.edit_original_response( content="💾 Submitting plays to database..." @@ -244,10 +268,7 @@ class SubmitScorecardCommands(commands.Cog): ) return - # Phase 8: Read Box Score - box_score = await self.sheets_service.read_box_score(scorecard) - - # Phase 9: PATCH Game + # Phase 8: PATCH Game await interaction.edit_original_response( content="⚾ Updating game result..." ) @@ -275,33 +296,7 @@ class SubmitScorecardCommands(commands.Cog): ) return - # Phase 10: Read Pitching Decisions - decisions_data = await self.sheets_service.read_pitching_decisions( - scorecard - ) - - # Add game metadata to each decision - for decision in decisions_data: - decision["game_id"] = game_id - decision["season"] = current.season - decision["week"] = setup_data["week"] - decision["game_num"] = setup_data["game_num"] - - # Validate WP and LP exist and fetch Player objects - wp, lp, sv, holders, _blown_saves = ( - await decision_service.find_winning_losing_pitchers(decisions_data) - ) - - if wp is None or lp is None: - # Rollback - await game_service.wipe_game_data(game_id) - await play_service.delete_plays_for_game(game_id) - await interaction.edit_original_response( - content="❌ Your card is missing either a Winning Pitcher or Losing Pitcher" - ) - return - - # Phase 11: POST Decisions + # Phase 9: POST Decisions await interaction.edit_original_response( content="🎯 Submitting pitching decisions..." ) @@ -361,6 +356,30 @@ class SubmitScorecardCommands(commands.Cog): # Success! await interaction.edit_original_response(content="✅ You are all set!") + except SheetsException as e: + # Spreadsheet reading error - show the detailed message to the user + self.logger.error( + f"Spreadsheet error in scorecard submission: {e}", error=e + ) + + if rollback_state and game_id: + try: + if rollback_state == "GAME_PATCHED": + await game_service.wipe_game_data(game_id) + await play_service.delete_plays_for_game(game_id) + elif rollback_state == "PLAYS_POSTED": + await play_service.delete_plays_for_game(game_id) + except Exception: + pass # Best effort rollback + + await interaction.edit_original_response( + content=( + f"❌ There's a problem with your scorecard:\n\n" + f"{str(e)}\n\n" + f"Please fix the issue in your spreadsheet and resubmit." + ) + ) + except Exception as e: # Unexpected error - attempt rollback self.logger.error(f"Unexpected error in scorecard submission: {e}", error=e) diff --git a/services/sheets_service.py b/services/sheets_service.py index 0e83359..f723f4b 100644 --- a/services/sheets_service.py +++ b/services/sheets_service.py @@ -415,6 +415,8 @@ class SheetsService: self.logger.info(f"Read {len(pit_data)} valid pitching decisions") return pit_data + except SheetsException: + raise except Exception as e: self.logger.error(f"Failed to read pitching decisions: {e}") raise SheetsException("Unable to read pitching decisions") from e @@ -457,6 +459,8 @@ class SheetsService: "home": [int(x) for x in score_table[1]], # [R, H, E] } + except SheetsException: + raise except Exception as e: self.logger.error(f"Failed to read box score: {e}") raise SheetsException("Unable to read box score") from e