Release 12

parent c00f9cdb
#!/usr/bin/env python3
"""
Script to check the current state of matches in the database
"""
import sys
from pathlib import Path
# Add the project root to Python path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from mbetterclient.database.manager import DatabaseManager
from mbetterclient.database.models import MatchModel
from mbetterclient.config.settings import get_user_data_dir
def main():
print("🔍 Checking database matches...")
# Initialize database manager
db_path = get_user_data_dir() / "mbetterclient.db"
db_manager = DatabaseManager(str(db_path))
if not db_manager.initialize():
print("❌ Failed to initialize database")
return
session = db_manager.get_session()
try:
# Get all matches
matches = session.query(MatchModel).filter(MatchModel.active_status == True).all()
print(f"📊 Found {len(matches)} active matches in database")
if not matches:
print("❌ No active matches found")
return
# Group by fixture
fixtures = {}
for match in matches:
if match.fixture_id not in fixtures:
fixtures[match.fixture_id] = []
fixtures[match.fixture_id].append(match)
for fixture_id, fixture_matches in fixtures.items():
print(f"\n🏟️ Fixture: {fixture_id}")
print(f" Total matches: {len(fixture_matches)}")
# Count by status
status_counts = {}
for match in fixture_matches:
status = match.status
if status not in status_counts:
status_counts[status] = 0
status_counts[status] += 1
print(f" Status counts: {status_counts}")
# Show details for first few matches
print(" First 5 matches:")
for i, match in enumerate(fixture_matches[:5]):
print(f" #{match.match_number}: {match.fighter1_township} vs {match.fighter2_township} - Status: {match.status} - Start: {match.start_time}")
if len(fixture_matches) > 5:
print(f" ... and {len(fixture_matches) - 5} more matches")
# Check if fixture is from yesterday
if fixture_matches and fixture_matches[0].start_time:
from datetime import datetime, timedelta
yesterday = datetime.utcnow() - timedelta(days=1)
match_date = fixture_matches[0].start_time.date()
yesterday_date = yesterday.date()
is_yesterday = match_date == yesterday_date
print(f" Date: {match_date} - Is yesterday: {is_yesterday}")
except Exception as e:
print(f"❌ Error checking database: {e}")
import traceback
traceback.print_exc()
finally:
session.close()
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
"""
Script to create test data for cross-day fixture testing.
Removes all existing fixtures and creates 10 matches for a "yesterday fixture":
- 5 completed matches (status='done')
- 5 matches in bet status (status='bet')
"""
import sys
import os
from datetime import datetime, timedelta
from pathlib import Path
# Add the project root to Python path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from mbetterclient.database.manager import DatabaseManager
from mbetterclient.database.models import MatchModel, MatchOutcomeModel, MatchTemplateModel, MatchOutcomeTemplateModel, BetModel, BetDetailModel, ExtractionStatsModel
from mbetterclient.config.settings import get_user_data_dir
from sqlalchemy.orm import joinedload
def create_basic_templates(session):
"""Create basic match templates for testing"""
templates_data = [
("John Doe", "Mike Smith", "Sports Arena"),
("Alex Johnson", "Chris Brown", "Championship Hall"),
("David Wilson", "Tom Davis", "Boxing Center"),
("Robert Lee", "James Miller", "Fight Club"),
("Kevin White", "Brian Taylor", "Ring Stadium"),
("Steve Harris", "Paul Walker", "Combat Zone"),
("Mark Thompson", "Jason Clark", "Battle Ground"),
("Andrew Lewis", "Scott Hall", "Warrior Arena"),
("Peter Parker", "Bruce Wayne", "Gladiator Hall"),
("Tony Stark", "Clark Kent", "Champions Ring")
]
for i, (fighter1, fighter2, venue) in enumerate(templates_data):
# Create template
template = MatchTemplateModel(
match_number=i + 1,
fighter1_township=fighter1,
fighter2_township=fighter2,
venue_kampala_township=venue,
filename=f"template_{i+1}.txt",
file_sha1sum=f"template_sha1_{i}",
fixture_id=f"template_fixture_{i+1}", # Templates need a fixture_id
active_status=True,
zip_filename=f"template_{i+1}.zip",
zip_sha1sum=f"template_zip_sha1_{i}",
zip_upload_status='completed',
zip_validation_status='valid'
)
session.add(template)
session.flush() # Get the template ID
# Create template outcomes
outcomes = [
('WIN1', 2.10),
('X', 3.20),
('WIN2', 1.85),
('UNDER', 1.75),
('OVER', 2.05),
('KO1', 4.50),
('KO2', 6.00),
('PTS1', 2.80),
('PTS2', 3.10)
]
for outcome_name, coefficient in outcomes:
outcome = MatchOutcomeTemplateModel(
match_id=template.id,
column_name=outcome_name,
float_value=coefficient
)
session.add(outcome)
session.commit()
def main():
print("🔄 Starting test fixture creation...")
# Initialize database manager
db_path = get_user_data_dir() / "mbetterclient.db"
db_manager = DatabaseManager(str(db_path))
if not db_manager.initialize():
raise RuntimeError("Failed to initialize database")
session = db_manager.get_session()
try:
print("🗑️ Removing all existing matches and bets (keeping templates)...")
# Remove all bets and bet details first (due to foreign key constraints)
session.query(BetDetailModel).delete()
session.query(BetModel).delete()
# Remove extraction stats (references matches)
session.query(ExtractionStatsModel).delete()
# Remove all match outcomes (due to foreign key constraints)
session.query(MatchOutcomeModel).delete()
# Remove all matches (but keep templates)
session.query(MatchModel).delete()
session.commit()
print("✅ All existing matches and bets removed (templates preserved)")
# Check if templates exist, create some if needed
template_count = session.query(MatchTemplateModel).count()
if template_count == 0:
print("📋 No match templates found, creating basic templates...")
create_basic_templates(session)
template_count = session.query(MatchTemplateModel).count()
print(f"✅ Created {template_count} basic match templates")
# Calculate yesterday's date
yesterday = datetime.utcnow() - timedelta(days=1)
fixture_id = "test_yesterday_fixture"
print(f"📅 Creating 10 matches for yesterday fixture: {fixture_id}")
print(f"📅 Yesterday's date: {yesterday.date()}")
# Get all available templates
templates = session.query(MatchTemplateModel).options(joinedload(MatchTemplateModel.outcomes)).all()
if len(templates) < 10:
print(f"⚠️ Only {len(templates)} templates available, will reuse templates for remaining matches")
for i in range(10):
match_number = i + 1
status = 'done' if i < 5 else 'bet' # First 5 completed, next 5 in bet status
# Select template (reuse if we run out)
template = templates[i % len(templates)]
# Create match from template
match = MatchModel(
match_number=match_number,
fighter1_township=template.fighter1_township,
fighter2_township=template.fighter2_township,
venue_kampala_township=template.venue_kampala_township,
start_time=yesterday,
status=status,
fixture_id=fixture_id,
filename=template.filename,
file_sha1sum=template.file_sha1sum,
active_status=True,
zip_filename=template.zip_filename,
zip_sha1sum=template.zip_sha1sum,
zip_upload_status='completed',
zip_validation_status='valid',
fixture_active_time=int(yesterday.timestamp()),
result=None if status == 'bet' else 'WIN1', # Set result for completed matches
end_time=yesterday if status == 'done' else None,
done=(status == 'done'),
running=False
)
session.add(match)
session.flush() # Get the match ID
# Copy match outcomes from template
for template_outcome in template.outcomes:
outcome = MatchOutcomeModel(
match_id=match.id,
column_name=template_outcome.column_name,
float_value=template_outcome.float_value
)
session.add(outcome)
print(f"✅ Created match #{match_number}: {template.fighter1_township} vs {template.fighter2_township} - Status: {status}")
session.commit()
print("🎉 Test fixture creation completed!")
print(f"📊 Created fixture '{fixture_id}' with 10 matches:")
print(" - 5 completed matches (status='done')")
print(" - 5 matches in bet status (status='bet')")
print(f" - All matches dated: {yesterday.date()}")
except Exception as e:
print(f"❌ Error creating test fixture: {e}")
session.rollback()
raise
finally:
session.close()
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
"""
Test script to create a yesterday fixture with 5 completed matches and 5 bet matches
for testing cross-day fixture handling.
"""
import sys
import os
from datetime import datetime, timedelta
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
# Add the project root to the Python path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from mbetterclient.database.manager import DatabaseManager
from mbetterclient.database.models import MatchModel, MatchOutcomeModel, MatchTemplateModel, MatchOutcomeTemplateModel
from mbetterclient.config.settings import get_user_data_dir
def create_yesterday_fixture():
"""Create a fixture from yesterday with 5 completed and 5 bet matches"""
# Initialize database manager
db_path = get_user_data_dir() / "mbetterclient.db"
db_manager = DatabaseManager(str(db_path))
if not db_manager.initialize():
print("Failed to initialize database manager")
return False
Session = sessionmaker(bind=db_manager.engine)
session = Session()
try:
# Get yesterday's date
yesterday = datetime.utcnow() - timedelta(days=1)
yesterday_start = datetime.combine(yesterday.date(), datetime.min.time())
# Generate unique fixture ID
import uuid
fixture_id = f"yesterday_test_{uuid.uuid4().hex[:8]}"
print(f"Creating yesterday fixture: {fixture_id}")
# Get some match templates to use as base
templates = session.query(MatchTemplateModel).filter(
MatchTemplateModel.active_status == True
).limit(10).all()
if len(templates) < 10:
print(f"Warning: Only {len(templates)} templates available, need 10")
if len(templates) < 5:
print("Error: Need at least 5 templates")
return False
match_number = 1
# Create 5 completed matches
print("Creating 5 completed matches...")
for i in range(5):
template = templates[i % len(templates)]
# Create completed match
match = MatchModel(
match_number=match_number,
fighter1_township=template.fighter1_township,
fighter2_township=template.fighter2_township,
venue_kampala_township=template.venue_kampala_township,
start_time=yesterday_start + timedelta(hours=i), # Spread throughout yesterday
status='done',
fixture_id=fixture_id,
filename=template.filename,
file_sha1sum=template.file_sha1sum,
active_status=True,
zip_filename=template.zip_filename,
zip_sha1sum=template.zip_sha1sum,
zip_upload_status='completed',
zip_validation_status='valid',
fixture_active_time=int(yesterday_start.timestamp()),
result='WIN1', # Simple result
end_time=yesterday_start + timedelta(hours=i, minutes=30),
done=True,
running=False
)
session.add(match)
session.flush()
# Copy outcomes from template
for template_outcome in template.outcomes:
outcome = MatchOutcomeModel(
match_id=match.id,
column_name=template_outcome.column_name,
float_value=template_outcome.float_value
)
session.add(outcome)
print(f" Created completed match #{match_number}: {match.fighter1_township} vs {match.fighter2_township}")
match_number += 1
# Create 5 bet matches
print("Creating 5 bet matches...")
for i in range(5, 10):
template = templates[i % len(templates)]
# Create bet match
match = MatchModel(
match_number=match_number,
fighter1_township=template.fighter1_township,
fighter2_township=template.fighter2_township,
venue_kampala_township=template.venue_kampala_township,
start_time=yesterday_start + timedelta(hours=i), # Continue from yesterday
status='bet',
fixture_id=fixture_id,
filename=template.filename,
file_sha1sum=template.file_sha1sum,
active_status=True,
zip_filename=template.zip_filename,
zip_sha1sum=template.zip_sha1sum,
zip_upload_status='completed',
zip_validation_status='valid',
fixture_active_time=int(yesterday_start.timestamp()),
result=None, # No result yet
end_time=None,
done=False,
running=False
)
session.add(match)
session.flush()
# Copy outcomes from template
for template_outcome in template.outcomes:
outcome = MatchOutcomeModel(
match_id=match.id,
column_name=template_outcome.column_name,
float_value=template_outcome.float_value
)
session.add(outcome)
print(f" Created bet match #{match_number}: {match.fighter1_township} vs {match.fighter2_township}")
match_number += 1
session.commit()
print(f"\nSuccessfully created yesterday fixture '{fixture_id}' with:")
print(" - 5 completed matches")
print(" - 5 bet matches")
print(" - All matches from yesterday")
print("\nTo test: Start the game without providing a fixture_id.")
print("The system should create a new fixture for today and play the remaining 5 bet matches from yesterday first.")
return True
except Exception as e:
print(f"Error creating yesterday fixture: {e}")
session.rollback()
return False
finally:
session.close()
if __name__ == "__main__":
print("Creating test fixture with yesterday's matches...")
success = create_yesterday_fixture()
if success:
print("\nTest fixture created successfully!")
else:
print("\nFailed to create test fixture!")
sys.exit(1)
\ No newline at end of file
This diff is collapsed.
......@@ -424,12 +424,17 @@ class MatchTimerComponent(ThreadedComponent):
remaining_matches = self._count_remaining_matches_in_fixture(fixture_id, session)
logger.info(f"Fixture {fixture_id} has {remaining_matches} remaining matches")
if remaining_matches < 5:
# Check if fixture is from yesterday - do not add matches to yesterday fixtures
is_yesterday_fixture = self._is_fixture_from_yesterday(fixture_id, session)
if remaining_matches < 5 and not is_yesterday_fixture:
logger.info(f"Only {remaining_matches} matches remaining (minimum 5 required) - ensuring minimum matches")
self._ensure_minimum_matches_in_fixture(fixture_id, 5 - remaining_matches, session)
# Recount after adding matches
remaining_matches = self._count_remaining_matches_in_fixture(fixture_id, session)
logger.info(f"After ensuring minimum matches, fixture {fixture_id} now has {remaining_matches} remaining matches")
elif remaining_matches < 5 and is_yesterday_fixture:
logger.info(f"Fixture {fixture_id} is from yesterday - not adding matches, proceeding with {remaining_matches} remaining matches")
# Send START_INTRO message
start_intro_message = MessageBuilder.start_intro(
......@@ -520,6 +525,47 @@ class MatchTimerComponent(ThreadedComponent):
logger.error(f"Failed to count remaining matches in fixture {fixture_id}: {e}")
return 0
def _is_fixture_from_yesterday(self, fixture_id: str, session) -> bool:
"""Check if the specified fixture has any matches from yesterday"""
try:
if not fixture_id:
return False
# Get today's date in venue timezone
today = self._get_today_venue_date()
from ..database.models import MatchModel
# Get all active matches for this fixture
matches = session.query(MatchModel).filter(
MatchModel.fixture_id == fixture_id,
MatchModel.active_status == True
).all()
for match in matches:
if match.start_time:
# Convert UTC start_time to venue timezone for date comparison
from ..utils.timezone_utils import utc_to_venue_datetime
venue_start_time = utc_to_venue_datetime(match.start_time, self.db_manager)
match_date = venue_start_time.date()
# Check if the match date is yesterday
if (today - match_date).days == 1:
logger.debug(f"Fixture {fixture_id} has match from yesterday: {match_date}, today: {today}")
return True
logger.debug(f"Fixture {fixture_id} has no matches from yesterday")
return False
except Exception as e:
logger.error(f"Failed to check if fixture {fixture_id} has matches from yesterday: {e}")
return False
def _get_today_venue_date(self) -> datetime.date:
"""Get today's date in venue timezone (for day change detection)"""
from ..utils.timezone_utils import get_today_venue_date
return get_today_venue_date(self.db_manager)
def _ensure_minimum_matches_in_fixture(self, fixture_id: str, minimum_required: int, session):
"""Ensure fixture has at least minimum_required matches by creating new ones from templates or old matches"""
try:
......
......@@ -2826,12 +2826,14 @@ class Migration_038_AddWin1Win2Associations(DatabaseMigration):
'sort_order': sort_order
})
# Add WIN1 associations: X1 and 12 -> WIN1
# Add WIN1 associations: X1 and 12 -> WIN1, and WIN1 -> WIN1
associations = [
('X1', 'WIN1'),
('12', 'WIN1'),
('WIN1', 'WIN1'),
('X2', 'WIN2'),
('12', 'WIN2')
('12', 'WIN2'),
('WIN2', 'WIN2')
]
for outcome_name, extraction_result in associations:
......@@ -2866,8 +2868,10 @@ class Migration_038_AddWin1Win2Associations(DatabaseMigration):
associations = [
('X1', 'WIN1'),
('12', 'WIN1'),
('WIN1', 'WIN1'),
('X2', 'WIN2'),
('12', 'WIN2')
('12', 'WIN2'),
('WIN2', 'WIN2')
]
for outcome_name, extraction_result in associations:
......
......@@ -555,6 +555,10 @@ class MatchModel(BaseModel):
def get_outcomes_dict(self) -> Dict[str, float]:
"""Get match outcomes as a dictionary"""
from sqlalchemy import inspect
if inspect(self).session is None:
# Object is not bound to a session, return empty dict to avoid lazy loading error
return {}
return {outcome.column_name: outcome.float_value for outcome in self.outcomes}
def add_outcome(self, column_name: str, float_value: float):
......@@ -575,6 +579,10 @@ class MatchModel(BaseModel):
"""Convert to dictionary with outcomes"""
result = super().to_dict(exclude_fields)
result['outcomes'] = self.get_outcomes_dict()
from sqlalchemy import inspect
if inspect(self).session is None:
result['outcome_count'] = 0
else:
result['outcome_count'] = len(self.outcomes)
return result
......@@ -699,22 +707,43 @@ class BetModel(BaseModel):
def get_total_amount(self) -> float:
"""Get total amount of all bet details"""
from sqlalchemy import inspect
if inspect(self).session is None:
# Object is not bound to a session, return 0 to avoid lazy loading error
return 0.0
return sum(detail.amount for detail in self.bet_details)
def get_bet_count(self) -> int:
"""Get number of bet details"""
from sqlalchemy import inspect
if inspect(self).session is None:
# Object is not bound to a session, return 0 to avoid lazy loading error
return 0
return len(self.bet_details)
def has_pending_bets(self) -> bool:
"""Check if bet has any pending bet details"""
from sqlalchemy import inspect
if inspect(self).session is None:
# Object is not bound to a session, return False to avoid lazy loading error
return False
return any(detail.result == 'pending' for detail in self.bet_details)
def calculate_total_winnings(self) -> float:
"""Calculate total winnings from won bets"""
from sqlalchemy import inspect
if inspect(self).session is None:
# Object is not bound to a session, return 0.0 to avoid lazy loading error
return 0.0
return sum(detail.win_amount for detail in self.bet_details if detail.result == 'win')
def get_overall_status(self) -> str:
"""Get overall bet status based on bet details"""
from sqlalchemy import inspect
if inspect(self).session is None:
# Object is not bound to a session, return 'pending' to avoid lazy loading error
return 'pending'
if not self.bet_details:
return 'pending'
......@@ -747,6 +776,16 @@ class BetModel(BaseModel):
def to_dict(self, exclude_fields: Optional[List[str]] = None) -> Dict[str, Any]:
"""Convert to dictionary with bet details"""
result = super().to_dict(exclude_fields)
from sqlalchemy import inspect
if inspect(self).session is None:
# Object is not bound to a session, avoid lazy loading
result['bet_details'] = []
result['total_amount'] = 0.0
result['bet_count'] = 0
result['has_pending'] = False
result['overall_status'] = 'pending'
result['total_winnings'] = 0.0
else:
result['bet_details'] = [detail.to_dict() for detail in self.bet_details]
result['total_amount'] = self.get_total_amount()
result['bet_count'] = self.get_bet_count()
......@@ -1062,6 +1101,10 @@ class MatchTemplateModel(BaseModel):
def get_outcomes_dict(self) -> Dict[str, float]:
"""Get match outcomes as a dictionary"""
from sqlalchemy import inspect
if inspect(self).session is None:
# Object is not bound to a session, return empty dict to avoid lazy loading error
return {}
return {outcome.column_name: outcome.float_value for outcome in self.outcomes}
def add_outcome(self, column_name: str, float_value: float):
......@@ -1082,6 +1125,10 @@ class MatchTemplateModel(BaseModel):
"""Convert to dictionary with outcomes"""
result = super().to_dict(exclude_fields)
result['outcomes'] = self.get_outcomes_dict()
from sqlalchemy import inspect
if inspect(self).session is None:
result['outcome_count'] = 0
else:
result['outcome_count'] = len(self.outcomes)
return result
......
......@@ -422,10 +422,10 @@ class OverlayWebChannel(QObject):
return None
def _get_fixture_data_from_database(self) -> Optional[List[Dict[str, Any]]]:
"""Get fixture data directly from database"""
"""Get fixture data directly from database, showing both today and yesterday fixtures until yesterday's are complete"""
try:
from ..database.models import MatchModel, MatchOutcomeModel
from datetime import datetime
from datetime import datetime, timedelta
# Use the database manager passed to this channel
if not self.db_manager:
......@@ -435,24 +435,55 @@ class OverlayWebChannel(QObject):
session = self.db_manager.get_session()
try:
# Get today's date in UTC (consistent with database storage)
today = datetime.utcnow().date()
# Get today's date in venue timezone for proper date handling
from ..utils.timezone_utils import get_today_venue_date
today_venue = get_today_venue_date(self.db_manager)
yesterday_venue = today_venue - timedelta(days=1)
# Convert venue dates to UTC datetime ranges for database queries
from ..utils.timezone_utils import venue_to_utc_datetime
today_start_utc = venue_to_utc_datetime(datetime.combine(today_venue, datetime.min.time()), self.db_manager)
today_end_utc = venue_to_utc_datetime(datetime.combine(today_venue, datetime.max.time()), self.db_manager)
yesterday_start_utc = venue_to_utc_datetime(datetime.combine(yesterday_venue, datetime.min.time()), self.db_manager)
yesterday_end_utc = venue_to_utc_datetime(datetime.combine(yesterday_venue, datetime.max.time()), self.db_manager)
# Get active matches for today (non-terminal states)
active_matches = session.query(MatchModel).filter(
today_matches = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None),
MatchModel.start_time >= today_start_utc,
MatchModel.start_time < today_end_utc,
MatchModel.status.notin_(['done', 'end', 'cancelled', 'failed', 'paused']),
MatchModel.active_status == True
).order_by(MatchModel.start_time.asc()).all()
# Check if there are any incomplete matches from yesterday
yesterday_incomplete_matches = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None),
MatchModel.start_time >= datetime.combine(today, datetime.min.time()),
MatchModel.start_time < datetime.combine(today, datetime.max.time()),
MatchModel.start_time >= yesterday_start_utc,
MatchModel.start_time < yesterday_end_utc,
MatchModel.status.notin_(['done', 'end', 'cancelled', 'failed', 'paused']),
MatchModel.active_status == True
).order_by(MatchModel.start_time.asc()).limit(5).all()
).order_by(MatchModel.start_time.asc()).all()
# Combine matches: yesterday first (if any incomplete), then today
all_matches = []
if yesterday_incomplete_matches:
logger.debug(f"Found {len(yesterday_incomplete_matches)} incomplete matches from yesterday - including them")
all_matches.extend(yesterday_incomplete_matches)
else:
logger.debug("No incomplete matches from yesterday found")
all_matches.extend(today_matches)
# Limit to 5 matches total
display_matches = all_matches[:5]
if not active_matches:
logger.debug("No active matches found")
if not display_matches:
logger.debug("No active matches found for today or yesterday")
return []
fixture_data = []
for match in active_matches:
for match in display_matches:
# Get outcomes for this match
outcomes = session.query(MatchOutcomeModel).filter(
MatchOutcomeModel.match_id == match.id
......@@ -473,7 +504,7 @@ class OverlayWebChannel(QObject):
}
fixture_data.append(match_data)
logger.debug(f"Retrieved {len(fixture_data)} matches from database")
logger.debug(f"Retrieved {len(fixture_data)} matches from database (yesterday: {len(yesterday_incomplete_matches)}, today: {len(today_matches)})")
return fixture_data
finally:
......
......@@ -378,7 +378,7 @@
<div class="overlay-container">
<div class="fixtures-panel" id="fixturesPanel">
<div class="fixtures-title">Next 5 matches:</div>
<div class="fixtures-title" id="fixturesTitle">next 5 matches:</div>
<div class="next-match-info" id="nextMatchInfo" style="display: none;"></div>
<div class="countdown-timer" id="countdownTimer" style="display: none;"></div>
<div class="loading-message" id="loadingMessage" style="display: none;">Loading fixture data...</div>
......@@ -944,6 +944,44 @@
countdownTimer.style.display = 'block';
}
// Update fixtures title based on content
function updateFixturesTitle(fixturesData) {
const fixturesTitle = document.getElementById('fixturesTitle');
if (!fixturesTitle) return;
// Check if we have yesterday's matches (incomplete from previous day)
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
let hasYesterdayMatches = false;
let hasTodayMatches = false;
fixturesData.forEach(match => {
if (match.start_time) {
const matchDate = new Date(match.start_time);
const matchDay = new Date(matchDate.getFullYear(), matchDate.getMonth(), matchDate.getDate());
if (matchDay.getTime() === yesterday.getTime()) {
hasYesterdayMatches = true;
} else if (matchDay.getTime() === today.getTime()) {
hasTodayMatches = true;
}
}
});
if (hasYesterdayMatches && hasTodayMatches) {
fixturesTitle.textContent = 'Today & Yesterday matches:';
} else if (hasYesterdayMatches) {
fixturesTitle.textContent = 'Yesterday matches:';
} else {
fixturesTitle.textContent = 'Today matches:';
}
console.log(`Updated fixtures title: ${fixturesTitle.textContent} (yesterday: ${hasYesterdayMatches}, today: ${hasTodayMatches})`);
}
// Render the fixtures table
function renderFixtures() {
debugTime('Starting renderFixtures function');
......@@ -953,6 +991,7 @@
const noMatches = document.getElementById('noMatches');
const tableHeader = document.getElementById('tableHeader');
const tableBody = document.getElementById('tableBody');
const fixturesTitle = document.getElementById('fixturesTitle');
loadingMessage.style.display = 'none';
noMatches.style.display = 'none';
......@@ -960,7 +999,7 @@
if (!fixturesData || fixturesData.length === 0) {
debugTime('No fixtures data available');
showNoMatches('No matches available for today');
showNoMatches('No matches available');
return;
}
......
......@@ -176,3 +176,13 @@ def venue_to_utc_datetime(venue_dt: datetime, db_manager) -> datetime:
else:
venue_dt = venue_dt.replace(tzinfo=venue_tz)
return venue_dt.astimezone(timezone.utc)
def get_current_venue_datetime(db_manager) -> datetime:
"""Get current datetime in venue timezone"""
venue_tz = get_venue_timezone(db_manager)
if HAS_PYTZ:
return datetime.now(venue_tz)
else:
# Without pytz, assume venue_tz is already a timezone object
return datetime.now(venue_tz)
\ No newline at end of file
This diff is collapsed.
......@@ -535,6 +535,9 @@ function updateBetsTable(data, container) {
// Collect unique match numbers
const matchNumbers = [...new Set(bet.details ? bet.details.map(detail => detail.match ? detail.match.match_number : 'Unknown').filter(n => n !== 'Unknown') : [])];
// Collect outcomes for display
const outcomes = bet.details ? bet.details.map(detail => detail.outcome).join(', ') : 'N/A';
// Determine overall bet status based on details
let overallStatus = 'pending';
let statusBadge = '';
......@@ -567,7 +570,7 @@ function updateBetsTable(data, container) {
if (overallStatus === 'won' && bet.details) {
payoutAmount = bet.details
.filter(detail => detail.result === 'win')
.reduce((sum, detail) => sum + parseFloat(detail.win_amount || 0), 0);
.reduce((sum, detail) => sum + parseFloat(detail.potential_winning || 0), 0);
}
const payoutDisplay = payoutAmount > 0 ? formatCurrency(payoutAmount.toFixed(2)) : '-';
......@@ -576,7 +579,7 @@ function updateBetsTable(data, container) {
<td><strong>${bet.uuid.substring(0, 8)}...</strong></td>
<td>${bet.barcode_data ? bet.barcode_data.substring(0, 16) + '...' : 'N/A'}</td>
<td>${betDateTime}</td>
<td>${bet.details ? bet.details.length : 0} selections</td>
<td>${outcomes}</td>
<td>${matchNumbers.length > 0 ? matchNumbers.join(', ') : 'N/A'}</td>
<td><strong class="currency-amount" data-amount="${totalAmount}">${formatCurrency(totalAmount)}</strong></td>
<td><strong class="currency-amount" data-amount="${payoutAmount}">${payoutDisplay}</strong></td>
......
......@@ -533,7 +533,10 @@ function updateBetsTable(data, container) {
const totalAmount = parseFloat(bet.total_amount).toFixed(2);
// Collect unique match numbers
const matchNumbers = [...new Set(bet.details ? bet.details.map(detail => detail.match_number || 'Unknown').filter(n => n !== 'Unknown') : [])];
const matchNumbers = [...new Set(bet.details ? bet.details.map(detail => detail.match ? detail.match.match_number : 'Unknown').filter(n => n !== 'Unknown') : [])];
// Collect outcomes for display
const outcomes = bet.details ? bet.details.map(detail => detail.outcome).join(', ') : 'N/A';
// Determine overall bet status based on details
let overallStatus = 'pending';
......@@ -567,7 +570,7 @@ function updateBetsTable(data, container) {
if (overallStatus === 'won' && bet.details) {
payoutAmount = bet.details
.filter(detail => detail.result === 'win')
.reduce((sum, detail) => sum + parseFloat(detail.win_amount || 0), 0);
.reduce((sum, detail) => sum + parseFloat(detail.potential_winning || 0), 0);
}
const payoutDisplay = payoutAmount > 0 ? formatCurrency(payoutAmount.toFixed(2)) : '-';
......@@ -576,7 +579,7 @@ function updateBetsTable(data, container) {
<td><strong>${bet.uuid.substring(0, 8)}...</strong></td>
<td>${bet.barcode_data ? bet.barcode_data.substring(0, 16) + '...' : 'N/A'}</td>
<td>${betDateTime}</td>
<td>${bet.details ? bet.details.length : 0} selections</td>
<td>${outcomes}</td>
<td>${matchNumbers.length > 0 ? matchNumbers.join(', ') : 'N/A'}</td>
<td><strong class="currency-amount" data-amount="${totalAmount}">${formatCurrency(totalAmount)}</strong></td>
<td><strong class="currency-amount" data-amount="${payoutAmount}">${payoutDisplay}</strong></td>
......
......@@ -80,7 +80,7 @@
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-list me-2"></i>Today's Matches Available for Betting
<i class="fas fa-list me-2"></i>Available Matches for Betting (Today & Yesterday)
<span class="badge bg-success ms-2" id="available-matches-count">0</span>
</h5>
</div>
......@@ -640,7 +640,7 @@ function updateAvailableMatchesDisplay(data, container) {
if (data.total === 0) {
container.innerHTML = `
<div class="text-center text-muted">
<i class="fas fa-info-circle me-2"></i>No matches available for betting today
<i class="fas fa-info-circle me-2"></i>No matches available for betting
<div class="mt-2">
<small>Matches must be in 'bet' status to accept wagers</small>
</div>
......@@ -716,17 +716,6 @@ function updateAvailableMatchesDisplay(data, container) {
// Add event listeners for amount inputs only
container.querySelectorAll('.amount-input').forEach(input => {
input.addEventListener('input', function() {
// Clear other outcomes for this match when entering an amount
const matchId = this.getAttribute('data-match-id');
const currentOutcome = this.getAttribute('data-outcome');
// Clear all other amount inputs for this match
container.querySelectorAll(`.amount-input[data-match-id="${matchId}"]`).forEach(otherInput => {
if (otherInput !== this) {
otherInput.value = '';
}
});
updateBetSummary();
});
});
......
#!/usr/bin/env python3
"""
Test script to verify the cross-day fixture fix.
This script creates a yesterday fixture with 5 completed matches and 5 bet matches,
then tests that starting the game without fixture_id activates the yesterday fixture first.
"""
import sys
import os
import time
from datetime import datetime, timedelta
from pathlib import Path
# Add the project root to Python path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from mbetterclient.database.manager import DatabaseManager
from mbetterclient.database.models import MatchModel, MatchOutcomeModel
from mbetterclient.core.games_thread import GamesThread
from mbetterclient.core.message_bus import MessageBus, Message, MessageType
from mbetterclient.config.settings import get_user_data_dir
def create_yesterday_fixture_with_remaining_matches(db_manager):
"""Create a fixture from yesterday with 5 completed matches and 5 bet matches"""
print("Creating yesterday fixture with remaining matches...")
session = db_manager.get_session()
try:
# Get yesterday's date in venue timezone
from mbetterclient.utils.timezone_utils import get_today_venue_date
yesterday = get_today_venue_date(db_manager) - timedelta(days=1)
# Convert to UTC for database storage
from mbetterclient.utils.timezone_utils import venue_to_utc_datetime
yesterday_start = datetime.combine(yesterday, datetime.min.time())
yesterday_utc = venue_to_utc_datetime(yesterday_start, db_manager)
# Generate fixture ID
import uuid
fixture_id = f"yesterday_test_{uuid.uuid4().hex[:8]}"
# Create 5 completed matches
for i in range(1, 6):
match = MatchModel(
match_number=i,
fighter1_township=f"Fighter {i}A",
fighter2_township=f"Fighter {i}B",
venue_kampala_township=f"Venue {i}",
start_time=yesterday_utc + timedelta(minutes=i*10),
status='done',
fixture_id=fixture_id,
active_status=True,
fixture_active_time=int(time.time()),
result=f'WIN{i}A',
end_time=yesterday_utc + timedelta(minutes=i*10 + 5),
done=True,
running=False,
filename=f"match_{i}_completed.mp4",
file_sha1sum=f"sha1_{i}_completed"
)
session.add(match)
session.flush()
# Add some outcomes
outcome = MatchOutcomeModel(
match_id=match.id,
column_name=f'WIN{i}A',
float_value=1.85
)
session.add(outcome)
# Create 5 bet matches (remaining)
for i in range(6, 11):
match = MatchModel(
match_number=i,
fighter1_township=f"Fighter {i}A",
fighter2_township=f"Fighter {i}B",
venue_kampala_township=f"Venue {i}",
start_time=yesterday_utc + timedelta(minutes=i*10),
status='bet',
fixture_id=fixture_id,
active_status=True,
fixture_active_time=int(time.time()),
result=None,
end_time=None,
done=False,
running=False,
filename=f"match_{i}_bet.mp4",
file_sha1sum=f"sha1_{i}_bet"
)
session.add(match)
session.flush()
# Add some outcomes
outcome = MatchOutcomeModel(
match_id=match.id,
column_name=f'WIN{i}A',
float_value=1.85
)
session.add(outcome)
session.commit()
print(f"Created yesterday fixture {fixture_id} with 5 completed and 5 bet matches")
return fixture_id
except Exception as e:
session.rollback()
print(f"Failed to create yesterday fixture: {e}")
return None
finally:
session.close()
def test_cross_day_fixture_logic():
"""Test the cross-day fixture logic"""
print("Testing cross-day fixture logic...")
# Initialize database manager
db_path = get_user_data_dir() / "mbetterclient.db"
db_manager = DatabaseManager(str(db_path))
if not db_manager.initialize():
print("Failed to initialize database")
return False
# Create yesterday fixture
yesterday_fixture_id = create_yesterday_fixture_with_remaining_matches(db_manager)
if not yesterday_fixture_id:
print("Failed to create test fixture")
return False
# Initialize message bus and games thread
message_bus = MessageBus()
games_thread = GamesThread("test_games_thread", message_bus, db_manager)
# Mock the initialization (we don't need full initialization for this test)
games_thread.db_manager = db_manager
games_thread.current_fixture_id = None
games_thread.game_active = False
# Create a test message for START_GAME without fixture_id
test_message = Message(
type=MessageType.START_GAME,
sender="test",
recipient="games_thread",
data={"timestamp": time.time()},
correlation_id="test_123"
)
# Mock the _send_response method to capture responses
responses = []
original_send_response = games_thread._send_response
def mock_send_response(message, status, response_message=None):
responses.append({"status": status, "message": response_message})
print(f"Mock response: {status} - {response_message}")
games_thread._send_response = mock_send_response
# Mock the _activate_fixture method to capture activation calls
activations = []
original_activate_fixture = games_thread._activate_fixture
def mock_activate_fixture(fixture_id, message):
activations.append(fixture_id)
print(f"Mock activation: fixture {fixture_id}")
# Set the current fixture to simulate activation
games_thread.current_fixture_id = fixture_id
games_thread.game_active = True
games_thread._activate_fixture = mock_activate_fixture
# Mock the _initialize_new_fixture method
original_initialize_new_fixture = games_thread._initialize_new_fixture
def mock_initialize_new_fixture():
# Return a mock today fixture ID
import uuid
return f"today_test_{uuid.uuid4().hex[:8]}"
games_thread._initialize_new_fixture = mock_initialize_new_fixture
try:
# Call the handler
games_thread._handle_start_game(test_message)
# Check results
print(f"Activations: {activations}")
print(f"Responses: {responses}")
# Verify that yesterday fixture was activated first
if len(activations) >= 1 and activations[0] == yesterday_fixture_id:
print("✅ SUCCESS: Yesterday fixture was activated first")
return True
else:
print(f"❌ FAILURE: Expected yesterday fixture {yesterday_fixture_id} to be activated first, got {activations}")
return False
except Exception as e:
print(f"Test failed with exception: {e}")
import traceback
traceback.print_exc()
return False
finally:
# Restore original methods
games_thread._send_response = original_send_response
games_thread._activate_fixture = original_activate_fixture
games_thread._initialize_new_fixture = original_initialize_new_fixture
def cleanup_test_data(db_manager, fixture_id):
"""Clean up test data"""
if not fixture_id:
return
print(f"Cleaning up test fixture {fixture_id}...")
session = db_manager.get_session()
try:
# Delete matches and outcomes
matches = session.query(MatchModel).filter(MatchModel.fixture_id == fixture_id).all()
for match in matches:
session.query(MatchOutcomeModel).filter(MatchOutcomeModel.match_id == match.id).delete()
session.query(MatchModel).filter(MatchModel.fixture_id == fixture_id).delete()
session.commit()
print(f"Cleaned up test fixture {fixture_id}")
except Exception as e:
print(f"Failed to cleanup test data: {e}")
session.rollback()
finally:
session.close()
def main():
"""Main test function"""
print("Cross-Day Fixture Fix Test")
print("=" * 40)
# Initialize database manager
db_path = get_user_data_dir() / "mbetterclient.db"
db_manager = DatabaseManager(str(db_path))
try:
# Run the test
success = test_cross_day_fixture_logic()
if success:
print("\n🎉 All tests passed! The cross-day fixture fix is working correctly.")
else:
print("\n💥 Test failed! The cross-day fixture fix needs more work.")
return success
except Exception as e:
print(f"Test execution failed: {e}")
import traceback
traceback.print_exc()
return False
finally:
db_manager.close()
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)
\ No newline at end of file
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