Add cleanup for incomplete matches from past days

- Add _cleanup_old_incomplete_matches() to close matches 2+ days old as 'failed'
- Add _get_yesterday_incomplete_matches() to find yesterday's incomplete matches
- Add _get_or_create_today_fixture() to ensure new matches go to today's fixture
- Add yesterday_fixture_id attribute to track yesterday's fixture for completion
- Modify initialize() to process past incomplete matches before starting
- Pre-create today's fixture when yesterday's incomplete matches are found
- Ensure new matches are created in today's fixture, not yesterday's
parent d7d1b0a7
...@@ -30,7 +30,7 @@ class GamesThread(ThreadedComponent): ...@@ -30,7 +30,7 @@ class GamesThread(ThreadedComponent):
self.message_queue = None self.message_queue = None
self.waiting_for_validation_fixture: Optional[str] = None self.waiting_for_validation_fixture: Optional[str] = None
self.pending_today_fixture_id: Optional[str] = None self.pending_today_fixture_id: Optional[str] = None
self.pending_today_fixture_id: Optional[str] = None self.yesterday_fixture_id: Optional[str] = None # Track yesterday's fixture for completion
self._last_orphan_cleanup_time: float = 0.0 # Track last orphan cleanup time self._last_orphan_cleanup_time: float = 0.0 # Track last orphan cleanup time
self._orphan_cleanup_interval: int = 10 # Run orphan cleanup every 10 seconds self._orphan_cleanup_interval: int = 10 # Run orphan cleanup every 10 seconds
...@@ -315,6 +315,156 @@ class GamesThread(ThreadedComponent): ...@@ -315,6 +315,156 @@ class GamesThread(ThreadedComponent):
except Exception as e: except Exception as e:
logger.error(f"Failed to cleanup stale matches: {e}") logger.error(f"Failed to cleanup stale matches: {e}")
def _cleanup_old_incomplete_matches(self):
"""Close matches from 2+ days ago with 'failed' status.
This method should be called during initialization to handle matches
that were never completed from 2 or more days ago.
"""
try:
session = self.db_manager.get_session()
try:
# Get today's date in venue timezone
today = self._get_today_venue_date()
# Convert venue date range to UTC for database query
from ..utils.timezone_utils import venue_to_utc_datetime
venue_start = datetime.combine(today, datetime.min.time())
utc_start = venue_to_utc_datetime(venue_start, self.db_manager)
# Calculate the start of yesterday (1 day ago) in UTC
yesterday_start = utc_start - timedelta(days=1)
# Find all incomplete matches from 2+ days ago
# These are matches that:
# - Have start_time before yesterday_start (2+ days ago)
# - Are NOT in terminal states (done, cancelled, failed, paused)
# - Have active_status = True
incomplete_statuses = ['pending', 'scheduled', 'bet', 'ingame']
old_incomplete_matches = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None),
MatchModel.start_time < yesterday_start,
MatchModel.status.in_(incomplete_statuses),
MatchModel.active_status == True
).all()
if old_incomplete_matches:
logger.info(f"⚠️ Found {len(old_incomplete_matches)} incomplete matches from 2+ days ago - marking as failed")
for match in old_incomplete_matches:
logger.info(f"Marking old incomplete match {match.match_number} (fixture: {match.fixture_id}) as failed: {match.fighter1_township} vs {match.fighter2_township}")
match.status = 'failed'
# Cancel/refund associated bets
self._cancel_match_bets(match.id, session)
session.commit()
logger.info(f"✅ Marked {len(old_incomplete_matches)} old incomplete matches as failed")
else:
logger.info("No incomplete matches from 2+ days ago found")
finally:
session.close()
except Exception as e:
logger.error(f"Failed to cleanup old incomplete matches: {e}")
def _get_yesterday_incomplete_matches(self) -> List[MatchModel]:
"""Get all incomplete matches from yesterday.
Returns a list of matches from yesterday that are not in terminal states.
"""
try:
session = self.db_manager.get_session()
try:
# Get today's date in venue timezone
today = self._get_today_venue_date()
# Convert venue date range to UTC for database query
from ..utils.timezone_utils import venue_to_utc_datetime
venue_start = datetime.combine(today, datetime.min.time())
venue_end = datetime.combine(today, datetime.max.time())
utc_start = venue_to_utc_datetime(venue_start, self.db_manager)
utc_end = venue_to_utc_datetime(venue_end, self.db_manager)
# Calculate yesterday's date range in UTC
yesterday_start = utc_start - timedelta(days=1)
yesterday_end = utc_end - timedelta(days=1)
# Find all incomplete matches from yesterday
incomplete_statuses = ['pending', 'scheduled', 'bet', 'ingame']
yesterday_matches = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None),
MatchModel.start_time >= yesterday_start,
MatchModel.start_time < yesterday_end,
MatchModel.status.in_(incomplete_statuses),
MatchModel.active_status == True
).order_by(MatchModel.match_number.asc()).all()
if yesterday_matches:
logger.info(f"Found {len(yesterday_matches)} incomplete matches from yesterday")
else:
logger.info("No incomplete matches from yesterday found")
return yesterday_matches
finally:
session.close()
except Exception as e:
logger.error(f"Failed to get yesterday's incomplete matches: {e}")
return []
def _get_or_create_today_fixture(self) -> Optional[str]:
"""Get existing today's fixture or create a new one.
This method ensures that new matches are created in today's fixture,
not in yesterday's fixture.
"""
try:
session = self.db_manager.get_session()
try:
# Get today's date in venue timezone
today = self._get_today_venue_date()
# Convert venue date range to UTC for database query
from ..utils.timezone_utils import venue_to_utc_datetime
venue_start = datetime.combine(today, datetime.min.time())
venue_end = datetime.combine(today, datetime.max.time())
utc_start = venue_to_utc_datetime(venue_start, self.db_manager)
utc_end = venue_to_utc_datetime(venue_end, self.db_manager)
# Check if there's already a fixture with today's matches
today_match = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None),
MatchModel.start_time >= utc_start,
MatchModel.start_time < utc_end,
MatchModel.active_status == True
).order_by(MatchModel.created_at.desc()).first()
if today_match:
logger.info(f"Found existing today fixture: {today_match.fixture_id}")
return today_match.fixture_id
# No today fixture exists - create a new one
logger.info("No today fixture found - creating new fixture")
session.close()
new_fixture_id = self._initialize_new_fixture()
if new_fixture_id:
logger.info(f"Created new today fixture: {new_fixture_id}")
return new_fixture_id
finally:
if session.is_active:
session.close()
except Exception as e:
logger.error(f"Failed to get or create today fixture: {e}")
return None
def _cancel_match_bets(self, match_id: int, session): def _cancel_match_bets(self, match_id: int, session):
"""Cancel all pending bets for a match""" """Cancel all pending bets for a match"""
try: try:
...@@ -487,14 +637,34 @@ class GamesThread(ThreadedComponent): ...@@ -487,14 +637,34 @@ class GamesThread(ThreadedComponent):
try: try:
logger.info("Initializing GamesThread...") logger.info("Initializing GamesThread...")
# Clean up any stale 'ingame' matches from previous crashed sessions # STEP 1: Close matches from 2+ days ago with 'failed' status
logger.info("🧹 Step 1: Cleaning up matches from 2+ days ago...")
self._cleanup_old_incomplete_matches()
# STEP 2: Clean up any stale 'ingame' matches from previous crashed sessions
logger.info("🧹 Step 2: Cleaning up stale ingame matches...")
self._cleanup_stale_ingame_matches() self._cleanup_stale_ingame_matches()
# Clean up any orphaned pending bets from previous crashed sessions # STEP 3: Clean up any orphaned pending bets from previous crashed sessions
# This ensures bets from matches that completed but had errors are resolved # This ensures bets from matches that completed but had errors are resolved
logger.info("🧹 Checking for orphaned pending bets from previous sessions...") logger.info("🧹 Step 3: Checking for orphaned pending bets from previous sessions...")
self._cleanup_orphaned_pending_bets() self._cleanup_orphaned_pending_bets()
# STEP 4: Check for yesterday's incomplete matches and prepare for continuation
logger.info("🧹 Step 4: Checking for yesterday's incomplete matches...")
yesterday_incomplete = self._get_yesterday_incomplete_matches()
if yesterday_incomplete:
logger.info(f"📋 Found {len(yesterday_incomplete)} incomplete matches from yesterday - will play these first")
# Store the yesterday fixture ID for later processing during START_GAME
self.yesterday_fixture_id = yesterday_incomplete[0].fixture_id
# Create today's fixture now so it's ready when yesterday's matches complete
today_fixture = self._get_or_create_today_fixture()
if today_fixture:
self.pending_today_fixture_id = today_fixture
logger.info(f"📋 Pre-created today fixture {today_fixture} for after yesterday's matches complete")
else:
self.yesterday_fixture_id = None
# Register with message bus first # Register with message bus first
self.message_queue = self.message_bus.register_component(self.name) self.message_queue = self.message_bus.register_component(self.name)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment