Fix critical week rollover bugs causing 60x freeze message spam

Three bugs identified and fixed:

1. Deduplication logic tracked wrong week (transaction_freeze.py:216-219)
   - Saved freeze_from_week BEFORE _begin_freeze() modifies current.week
   - Prevents re-execution when API returns stale data

2. _run_transactions() bypassed service layer (transaction_freeze.py:350-394)
   - Added get_regular_transactions_by_week() to transaction_service.py
   - Now properly filters frozen=false and cancelled=false
   - Uses Transaction model objects instead of raw dict access

3. CRITICAL: Hardcoded current_id=1 (league_service.py:88-106)
   - Current table has one row PER SEASON, not a single row
   - Was patching Season 3 (id=1) instead of Season 13 (id=11)
   - Now fetches actual current state ID before patching

Root cause: The hardcoded ID caused every PATCH to update the wrong
season's record, so freeze was never actually set to True on the
current season. This caused the dedup check to pass 60 times (once
per minute during hour 0).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-12-22 14:15:26 -06:00
parent 3ea743a489
commit 37bf797254
3 changed files with 65 additions and 24 deletions

View File

@ -85,17 +85,24 @@ class LeagueService(BaseService[Current]):
logger.warning("update_current_state called with no updates")
return await self.get_current_state()
# Current state always has ID of 1 (single record)
current_id = 1
# Get the current state to find its actual ID
# (Current table has one row per season, NOT a single row with id=1)
current = await self.get_current_state()
if not current:
logger.error("Cannot update current state - unable to fetch current state")
return None
current_id = current.id
logger.debug(f"Updating current state id={current_id} (season {current.season})")
# Use BaseService patch method
updated_current = await self.patch(current_id, update_data)
if updated_current:
logger.info(f"Updated current state: {update_data}")
logger.info(f"Updated current state id={current_id}: {update_data}")
return updated_current
else:
logger.error("Failed to update current state - patch returned None")
logger.error(f"Failed to update current state id={current_id} - patch returned None")
return None
except Exception as e:

View File

@ -384,6 +384,43 @@ class TransactionService(BaseService[Transaction]):
logger.error(f"Error getting frozen transactions for weeks {week_start}-{week_end}: {e}")
return []
async def get_regular_transactions_by_week(
self,
season: int,
week: int
) -> List[Transaction]:
"""
Get non-frozen, non-cancelled transactions for a specific week.
This is used during freeze begin to process regular transactions
that were submitted during the non-freeze period and should take
effect immediately when the new week starts.
Args:
season: Season number
week: Week number to get transactions for
Returns:
List of regular (non-frozen, non-cancelled) transactions for the week
"""
try:
params = [
('season', str(season)),
('week_start', str(week)),
('week_end', str(week)),
('frozen', 'false'),
('cancelled', 'false')
]
transactions = await self.get_all_items(params=params)
logger.debug(f"Retrieved {len(transactions)} regular transactions for week {week}")
return transactions
except Exception as e:
logger.error(f"Error getting regular transactions for week {week}: {e}")
return []
async def get_contested_transactions(self, season: int, week: int) -> List[Transaction]:
"""
Get transactions that may be contested (multiple teams want same player).

View File

@ -213,9 +213,10 @@ class TransactionFreezeTask:
# Only run if we haven't already frozen this week
# Track the week we're freezing FROM (before increment)
if self.last_freeze_week != current.week:
freeze_from_week = current.week # Save BEFORE _begin_freeze modifies it
self.logger.info("Triggering freeze begin", current_week=current.week)
await self._begin_freeze(current)
self.last_freeze_week = current.week # Track the week we froze (before increment)
self.last_freeze_week = freeze_from_week # Track the week we froze FROM
self.error_notification_sent = False # Reset error flag for new cycle
else:
self.logger.debug("Freeze already executed for week", week=current.week)
@ -341,26 +342,22 @@ class TransactionFreezeTask:
async def _run_transactions(self, current: Current):
"""
Process regular (non-frozen) transactions for the current week.
Process regular (non-frozen, non-cancelled) transactions for the current week.
These are transactions that take effect immediately.
These are transactions that were submitted during the non-freeze period
and should take effect immediately when the new week starts.
"""
try:
# Get all non-frozen transactions for current week
client = await transaction_service.get_client()
params = [
('season', str(current.season)),
('week_start', str(current.week)),
('week_end', str(current.week))
]
# Get non-frozen, non-cancelled transactions for current week via service
transactions = await transaction_service.get_regular_transactions_by_week(
season=current.season,
week=current.week
)
response = await client.get('transactions', params=params)
if not response or response.get('count', 0) == 0:
if not transactions:
self.logger.info(f"No regular transactions to process for week {current.week}")
return
transactions = response.get('transactions', [])
self.logger.info(f"Processing {len(transactions)} regular transactions for week {current.week}")
# Execute player roster updates for all transactions
@ -371,9 +368,9 @@ class TransactionFreezeTask:
try:
# Update player's team via PATCH /players/{player_id}?team_id={new_team_id}
await self._execute_player_update(
player_id=transaction['player']['id'],
new_team_id=transaction['newteam']['id'],
player_name=transaction['player']['name']
player_id=transaction.player.id,
new_team_id=transaction.newteam.id,
player_name=transaction.player.name
)
success_count += 1
@ -382,9 +379,9 @@ class TransactionFreezeTask:
except Exception as e:
self.logger.error(
f"Failed to execute transaction for {transaction['player']['name']}",
player_id=transaction['player']['id'],
new_team_id=transaction['newteam']['id'],
f"Failed to execute transaction for {transaction.player.name}",
player_id=transaction.player.id,
new_team_id=transaction.newteam.id,
error=str(e)
)
failure_count += 1