Fix multiple today fixtures creation and reset fixtures foreign key error

- Fix _get_or_create_today_fixture() to use created_at as fallback when start_time is NULL
- Replace _initialize_new_fixture() calls with _get_or_create_today_fixture() in _handle_start_game()
- Fix reset_fixtures() to use raw SQL DELETE statements for proper MySQL foreign key handling
- Delete tables in correct order: extraction_stats, bets_details, bets, match_outcomes, matches, match_outcomes_templates, matches_templates
parent 074153e0
...@@ -466,7 +466,8 @@ class GamesThread(ThreadedComponent): ...@@ -466,7 +466,8 @@ class GamesThread(ThreadedComponent):
"""Get existing today's fixture or create a new one. """Get existing today's fixture or create a new one.
This method ensures that new matches are created in today's fixture, This method ensures that new matches are created in today's fixture,
not in yesterday's fixture. not in yesterday's fixture. It uses multiple strategies to find existing
today fixtures to prevent creating duplicates.
""" """
try: try:
session = self.db_manager.get_session() session = self.db_manager.get_session()
...@@ -481,17 +482,29 @@ class GamesThread(ThreadedComponent): ...@@ -481,17 +482,29 @@ class GamesThread(ThreadedComponent):
utc_start = venue_to_utc_datetime(venue_start, self.db_manager) utc_start = venue_to_utc_datetime(venue_start, self.db_manager)
utc_end = venue_to_utc_datetime(venue_end, 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 # Strategy 1: Check for matches with start_time in today's range
today_match = session.query(MatchModel).filter( today_match_with_start_time = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None), MatchModel.start_time.isnot(None),
MatchModel.start_time >= utc_start, MatchModel.start_time >= utc_start,
MatchModel.start_time < utc_end, MatchModel.start_time < utc_end,
MatchModel.active_status == True MatchModel.active_status == True
).order_by(MatchModel.created_at.desc()).first() ).order_by(MatchModel.created_at.desc()).first()
if today_match: if today_match_with_start_time:
logger.info(f"Found existing today fixture: {today_match.fixture_id}") logger.info(f"Found existing today fixture (by start_time): {today_match_with_start_time.fixture_id}")
return today_match.fixture_id return today_match_with_start_time.fixture_id
# Strategy 2: Check for matches created today (using created_at as fallback)
# This catches matches that were just created and don't have start_time yet
today_match_by_created = session.query(MatchModel).filter(
MatchModel.created_at >= utc_start,
MatchModel.created_at < utc_end,
MatchModel.active_status == True
).order_by(MatchModel.created_at.asc()).first()
if today_match_by_created:
logger.info(f"Found existing today fixture (by created_at): {today_match_by_created.fixture_id}")
return today_match_by_created.fixture_id
# No today fixture exists - create a new one # No today fixture exists - create a new one
logger.info("No today fixture found - creating new fixture") logger.info("No today fixture found - creating new fixture")
...@@ -826,18 +839,18 @@ class GamesThread(ThreadedComponent): ...@@ -826,18 +839,18 @@ class GamesThread(ThreadedComponent):
is_yesterday_fixture = self._is_fixture_from_yesterday(fixture_id, session) is_yesterday_fixture = self._is_fixture_from_yesterday(fixture_id, session)
if is_yesterday_fixture: if is_yesterday_fixture:
# Fixture is from yesterday - create new fixture for today but activate yesterday first # Fixture is from yesterday - get or create today fixture but activate yesterday first
logger.info(f"Fixture {fixture_id} is from yesterday - creating today fixture and activating yesterday to play remaining matches first") logger.info(f"Fixture {fixture_id} is from yesterday - getting/creating today fixture and activating yesterday to play remaining matches first")
new_fixture_id = self._initialize_new_fixture() new_fixture_id = self._get_or_create_today_fixture()
if new_fixture_id: if new_fixture_id:
logger.info(f"Created today fixture {new_fixture_id} - will play yesterday matches first, then switch to today") logger.info(f"Today fixture {new_fixture_id} ready - will play yesterday matches first, then switch to today")
# Store the today fixture ID for later use # Store the today fixture ID for later use
self.pending_today_fixture_id = new_fixture_id self.pending_today_fixture_id = new_fixture_id
# Activate yesterday fixture to play remaining matches # Activate yesterday fixture to play remaining matches
self._activate_fixture(fixture_id, message) self._activate_fixture(fixture_id, message)
return return
else: else:
logger.warning("Could not create today fixture - activating yesterday fixture as fallback") logger.warning("Could not get/create today fixture - activating yesterday fixture as fallback")
self._activate_fixture(fixture_id, message) self._activate_fixture(fixture_id, message)
return return
else: else:
...@@ -916,33 +929,33 @@ class GamesThread(ThreadedComponent): ...@@ -916,33 +929,33 @@ class GamesThread(ThreadedComponent):
).count() ).count()
if completed_count == total_fixture_matches: if completed_count == total_fixture_matches:
# All matches completed, create new fixture from templates # All matches completed, get or create today fixture
logger.info(f"All {total_fixture_matches} matches in fixture {fixture_id} are completed - creating new fixture from templates") logger.info(f"All {total_fixture_matches} matches in fixture {fixture_id} are completed - getting/creating today fixture")
new_fixture_id = self._initialize_new_fixture() new_fixture_id = self._get_or_create_today_fixture()
if new_fixture_id: if new_fixture_id:
self._activate_fixture(new_fixture_id, message) self._activate_fixture(new_fixture_id, message)
return return
else: else:
logger.warning("Could not create new fixture from templates") logger.warning("Could not get/create today fixture")
self._send_response(message, "error", "Could not create new fixture") self._send_response(message, "error", "Could not get/create today fixture")
return return
else: else:
# Some matches are not completed, check if fixture is from yesterday # Some matches are not completed, check if fixture is from yesterday
is_yesterday_fixture = self._is_fixture_from_yesterday(fixture_id, session) is_yesterday_fixture = self._is_fixture_from_yesterday(fixture_id, session)
if is_yesterday_fixture: if is_yesterday_fixture:
# Fixture is from yesterday - activate yesterday fixture first to play remaining matches, create today fixture for later # Fixture is from yesterday - activate yesterday fixture first to play remaining matches, get/create today fixture for later
logger.info(f"Fixture {fixture_id} is from yesterday and has remaining matches - activating yesterday fixture first, will create today fixture after completion") logger.info(f"Fixture {fixture_id} is from yesterday and has remaining matches - activating yesterday fixture first, will get/create today fixture after completion")
new_fixture_id = self._initialize_new_fixture() new_fixture_id = self._get_or_create_today_fixture()
if new_fixture_id: if new_fixture_id:
# Store the today fixture ID for later use # Store the today fixture ID for later use
self.pending_today_fixture_id = new_fixture_id self.pending_today_fixture_id = new_fixture_id
logger.info(f"Created today fixture {new_fixture_id} - will play yesterday matches first, then switch to today") logger.info(f"Today fixture {new_fixture_id} ready - will play yesterday matches first, then switch to today")
# Activate yesterday fixture to play remaining matches # Activate yesterday fixture to play remaining matches
self._activate_fixture(fixture_id, message) self._activate_fixture(fixture_id, message)
return return
else: else:
logger.warning("Could not create today fixture - activating yesterday fixture as fallback") logger.warning("Could not get/create today fixture - activating yesterday fixture as fallback")
self._activate_fixture(fixture_id, message) self._activate_fixture(fixture_id, message)
return return
else: else:
...@@ -985,13 +998,13 @@ class GamesThread(ThreadedComponent): ...@@ -985,13 +998,13 @@ class GamesThread(ThreadedComponent):
else: else:
logger.info("No matches available in database - creating new fixture from templates") logger.info("No matches available in database - creating new fixture from templates")
# Create new fixture from templates # Get or create today fixture
new_fixture_id = self._initialize_new_fixture() new_fixture_id = self._get_or_create_today_fixture()
if new_fixture_id: if new_fixture_id:
self._activate_fixture(new_fixture_id, message) self._activate_fixture(new_fixture_id, message)
return return
else: else:
logger.warning("Could not create new fixture from templates - waiting for templates to become available") logger.warning("Could not get or create today fixture - waiting for templates to become available")
self._send_response(message, "waiting_for_downloads", "Waiting for match templates to be downloaded and validated") self._send_response(message, "waiting_for_downloads", "Waiting for match templates to be downloaded and validated")
return return
......
...@@ -4370,20 +4370,34 @@ def reset_fixtures(): ...@@ -4370,20 +4370,34 @@ def reset_fixtures():
extraction_stats_count = session.query(ExtractionStatsModel).count() extraction_stats_count = session.query(ExtractionStatsModel).count()
# Delete in correct order to handle foreign key constraints # Delete in correct order to handle foreign key constraints
# Use raw SQL to ensure proper deletion with MySQL foreign key constraints
# 1. Delete extraction_stats first (references matches) # 1. Delete extraction_stats first (references matches)
deleted_extraction_stats = session.query(ExtractionStatsModel).delete() deleted_extraction_stats = session.execute(text("DELETE FROM extraction_stats"))
session.commit()
# 2. Delete bet_details first (references both bets and matches)
deleted_bet_details = session.execute(text("DELETE FROM bets_details"))
session.commit()
# 3. Delete bets
deleted_bets = session.execute(text("DELETE FROM bets"))
session.commit()
# 4. Delete match_outcomes (references matches)
deleted_outcomes = session.execute(text("DELETE FROM match_outcomes"))
session.commit() session.commit()
# 2. Delete bets (will cascade to bet_details due to CASCADE constraint) # 5. Delete matches
deleted_bets = session.query(BetModel).delete() deleted_matches = session.execute(text("DELETE FROM matches"))
session.commit() session.commit()
# 3. Delete matches (will cascade to match_outcomes due to CASCADE constraint) # 6. Delete match_outcomes_templates (references matches_templates)
deleted_matches = session.query(MatchModel).delete() deleted_template_outcomes = session.execute(text("DELETE FROM match_outcomes_templates"))
session.commit() session.commit()
# 4. Delete match templates (will cascade to match_outcomes_templates due to CASCADE constraint) # 7. Delete match templates
deleted_templates = session.query(MatchTemplateModel).delete() deleted_templates = session.execute(text("DELETE FROM matches_templates"))
session.commit() session.commit()
# Clear ZIP files from persistent storage # Clear ZIP files from persistent storage
......
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