Update extraction cap calculation

parent a4fd9ee8
...@@ -234,6 +234,11 @@ def collect_hidden_imports() -> List[str]: ...@@ -234,6 +234,11 @@ def collect_hidden_imports() -> List[str]:
'barcode.codex', 'barcode.codex',
'barcode.ean', 'barcode.ean',
'barcode.isxn', 'barcode.isxn',
# Excel file generation
'openpyxl',
'openpyxl.styles',
'openpyxl.utils',
] ]
# Conditionally add ffmpeg module if available # Conditionally add ffmpeg module if available
......
...@@ -202,10 +202,11 @@ class SportsResponseHandler(ResponseHandler): ...@@ -202,10 +202,11 @@ class SportsResponseHandler(ResponseHandler):
class UpdatesResponseHandler(ResponseHandler): class UpdatesResponseHandler(ResponseHandler):
"""Response handler for /api/updates endpoint - synchronizes match data""" """Response handler for /api/updates endpoint - synchronizes match data"""
def __init__(self, db_manager, user_data_dir, api_client=None): def __init__(self, db_manager, user_data_dir, api_client=None, message_bus=None):
self.db_manager = db_manager self.db_manager = db_manager
self.user_data_dir = user_data_dir self.user_data_dir = user_data_dir
self.api_client = api_client # Reference to parent API client for token access self.api_client = api_client # Reference to parent API client for token access
self.message_bus = message_bus # Reference to message bus for progress updates
self.zip_storage_dir = Path(user_data_dir) / "zip_files" self.zip_storage_dir = Path(user_data_dir) / "zip_files"
self.zip_storage_dir.mkdir(parents=True, exist_ok=True) self.zip_storage_dir.mkdir(parents=True, exist_ok=True)
...@@ -289,6 +290,9 @@ class UpdatesResponseHandler(ResponseHandler): ...@@ -289,6 +290,9 @@ class UpdatesResponseHandler(ResponseHandler):
logger.debug(f"Starting ZIP file downloads for {len(fixtures)} fixtures") logger.debug(f"Starting ZIP file downloads for {len(fixtures)} fixtures")
total_matches_with_zips = 0 total_matches_with_zips = 0
# Send initial progress update - starting downloads
self._send_download_progress(0, processed_data['expected_zips'], "Starting downloads...")
for fixture_data in fixtures: for fixture_data in fixtures:
fixture_id = fixture_data.get('fixture_id', 'unknown') fixture_id = fixture_data.get('fixture_id', 'unknown')
matches = fixture_data.get('matches', []) matches = fixture_data.get('matches', [])
...@@ -310,10 +314,20 @@ class UpdatesResponseHandler(ResponseHandler): ...@@ -310,10 +314,20 @@ class UpdatesResponseHandler(ResponseHandler):
match_data['zip_url'] = match_data['zip_download_url'] match_data['zip_url'] = match_data['zip_download_url']
logger.debug(f"Found ZIP file to download: {zip_filename} for match {match_number} in fixture {fixture_id}") logger.debug(f"Found ZIP file to download: {zip_filename} for match {match_number} in fixture {fixture_id}")
# Send progress update - starting individual download
progress_percent = int((processed_data['downloaded_zips'] / max(1, processed_data['expected_zips'])) * 100)
self._send_download_progress(processed_data['downloaded_zips'], processed_data['expected_zips'],
f"Downloading {zip_filename}...")
download_success = self._download_zip_file(match_data) download_success = self._download_zip_file(match_data)
if download_success: if download_success:
processed_data['downloaded_zips'] += 1 processed_data['downloaded_zips'] += 1
logger.debug(f"Successfully downloaded ZIP file: {zip_filename} for match {match_number}") logger.debug(f"Successfully downloaded ZIP file: {zip_filename} for match {match_number}")
# Send progress update - download completed
progress_percent = int((processed_data['downloaded_zips'] / max(1, processed_data['expected_zips'])) * 100)
self._send_download_progress(processed_data['downloaded_zips'], processed_data['expected_zips'],
f"Downloaded {zip_filename}")
elif 'zip_download_url' in match_data: elif 'zip_download_url' in match_data:
logger.debug(f"ZIP file download skipped or failed: {zip_filename} for match {match_number}") logger.debug(f"ZIP file download skipped or failed: {zip_filename} for match {match_number}")
...@@ -336,6 +350,13 @@ class UpdatesResponseHandler(ResponseHandler): ...@@ -336,6 +350,13 @@ class UpdatesResponseHandler(ResponseHandler):
logger.debug(f"ZIP download summary: {processed_data['downloaded_zips']}/{processed_data['expected_zips']} ZIP files downloaded successfully from {total_matches_with_zips} matches") logger.debug(f"ZIP download summary: {processed_data['downloaded_zips']}/{processed_data['expected_zips']} ZIP files downloaded successfully from {total_matches_with_zips} matches")
# Send final progress update - downloads completed
if processed_data['expected_zips'] > 0:
self._send_download_progress(processed_data['downloaded_zips'], processed_data['expected_zips'],
f"Downloads completed - {processed_data['downloaded_zips']}/{processed_data['expected_zips']} files")
else:
self._send_download_progress(0, 0, "No downloads needed")
logger.info(f"Synchronized {processed_data['synchronized_matches']} matches, downloaded {processed_data['downloaded_zips']} ZIP files") logger.info(f"Synchronized {processed_data['synchronized_matches']} matches, downloaded {processed_data['downloaded_zips']} ZIP files")
return processed_data return processed_data
...@@ -592,6 +613,31 @@ class UpdatesResponseHandler(ResponseHandler): ...@@ -592,6 +613,31 @@ class UpdatesResponseHandler(ResponseHandler):
logger.error(f"Failed to validate ZIP file {zip_path}: {e}") logger.error(f"Failed to validate ZIP file {zip_path}: {e}")
return False return False
def _send_download_progress(self, downloaded: int, total: int, message: str):
"""Send download progress update via message bus"""
try:
if self.message_bus:
from ..core.message_bus import Message, MessageType
progress_data = {
'downloaded': downloaded,
'total': total,
'percentage': int((downloaded / max(1, total)) * 100) if total > 0 else 0,
'message': message,
'timestamp': time.time()
}
progress_message = Message(
type=MessageType.CUSTOM,
sender='api_client',
data={
'download_progress': progress_data
}
)
self.message_bus.publish(progress_message)
logger.debug(f"Sent download progress: {downloaded}/{total} - {message}")
except Exception as e:
logger.error(f"Failed to send download progress: {e}")
def _validate_existing_fixtures(self): def _validate_existing_fixtures(self):
"""Validate existing fixtures on startup and remove invalid ones""" """Validate existing fixtures on startup and remove invalid ones"""
try: try:
...@@ -719,7 +765,7 @@ class APIClient(ThreadedComponent): ...@@ -719,7 +765,7 @@ class APIClient(ThreadedComponent):
'default': DefaultResponseHandler(), 'default': DefaultResponseHandler(),
'news': NewsResponseHandler(), 'news': NewsResponseHandler(),
'sports': SportsResponseHandler(), 'sports': SportsResponseHandler(),
'updates': UpdatesResponseHandler(self.db_manager, get_user_data_dir(), self) 'updates': UpdatesResponseHandler(self.db_manager, get_user_data_dir(), self, self.message_bus)
} }
# Statistics # Statistics
......
...@@ -2361,26 +2361,50 @@ class GamesThread(ThreadedComponent): ...@@ -2361,26 +2361,50 @@ class GamesThread(ThreadedComponent):
total_bet_amount = sum(bet.amount for bet in all_bets) if all_bets else 0.0 total_bet_amount = sum(bet.amount for bet in all_bets) if all_bets else 0.0
logger.info(f"💵 [EXTRACTION DEBUG] Total bet amount calculated: {total_bet_amount:.2f} from {len(all_bets)} bets") logger.info(f"💵 [EXTRACTION DEBUG] Total bet amount calculated: {total_bet_amount:.2f} from {len(all_bets)} bets")
# Step 5: Get redistribution CAP # Step 5: Get UNDER/OVER result and calculate payouts for CAP adjustment
logger.info(f"🎯 [EXTRACTION DEBUG] Step 5: Retrieving redistribution CAP") logger.info(f"🎯 [EXTRACTION DEBUG] Step 5: Getting UNDER/OVER result and calculating payouts for CAP adjustment")
# Get the stored UNDER/OVER result from the match
match = session.query(MatchModel).filter_by(id=match_id).first()
under_over_result = match.under_over_result if match else None
logger.info(f"🎯 [EXTRACTION DEBUG] Stored under_over_result: '{under_over_result}'")
# Calculate UNDER/OVER payouts
under_payout = self._calculate_payout(match_id, 'UNDER', under_coeff, session)
over_payout = self._calculate_payout(match_id, 'OVER', over_coeff, session)
logger.info(f"🎯 [EXTRACTION DEBUG] UNDER payout: {under_payout:.2f}, OVER payout: {over_payout:.2f}")
# Get redistribution CAP
cap_percentage = self._get_redistribution_cap() cap_percentage = self._get_redistribution_cap()
# Step 5.1: Get accumulated shortfall from previous extractions for today # Get accumulated shortfall from previous extractions for today
logger.info(f"🎯 [EXTRACTION DEBUG] Step 5.1: Retrieving accumulated shortfall for today")
today = datetime.now().date() today = datetime.now().date()
accumulated_shortfall = self._get_daily_shortfall(today, session) accumulated_shortfall = self._get_daily_shortfall(today, session)
logger.info(f"🎯 [EXTRACTION DEBUG] Accumulated shortfall: {accumulated_shortfall:.2f}") logger.info(f"🎯 [EXTRACTION DEBUG] Accumulated shortfall: {accumulated_shortfall:.2f}")
# Step 5.2: Calculate CAP threshold including shortfall # Calculate base CAP threshold
base_cap_threshold = total_bet_amount * (cap_percentage / 100.0) base_cap_threshold = total_bet_amount * (cap_percentage / 100.0)
# Adjust CAP threshold based on UNDER/OVER result
cap_threshold = base_cap_threshold + accumulated_shortfall cap_threshold = base_cap_threshold + accumulated_shortfall
logger.info(f"🎯 [EXTRACTION DEBUG] CAP percentage: {cap_percentage}%, base threshold: {base_cap_threshold:.2f}, shortfall: {accumulated_shortfall:.2f}, final threshold: {cap_threshold:.2f}")
# If UNDER/OVER wins, subtract the winning payout from CAP threshold
if under_over_result == 'UNDER':
cap_threshold -= under_payout
logger.info(f"🎯 [EXTRACTION DEBUG] UNDER wins - adjusted CAP threshold: {cap_threshold:.2f} (subtracted {under_payout:.2f})")
elif under_over_result == 'OVER':
cap_threshold -= over_payout
logger.info(f"🎯 [EXTRACTION DEBUG] OVER wins - adjusted CAP threshold: {cap_threshold:.2f} (subtracted {over_payout:.2f})")
else:
logger.info(f"🎯 [EXTRACTION DEBUG] No UNDER/OVER winner - CAP threshold: {cap_threshold:.2f}")
logger.info(f"🎯 [EXTRACTION DEBUG] CAP percentage: {cap_percentage}%, base threshold: {base_cap_threshold:.2f}, final threshold: {cap_threshold:.2f}")
logger.info(f"📊 [EXTRACTION DEBUG] Extraction summary - {len(payouts)} results, total_bet_amount={total_bet_amount:.2f}, CAP={cap_percentage}%, threshold={cap_threshold:.2f}") logger.info(f"📊 [EXTRACTION DEBUG] Extraction summary - {len(payouts)} results, total_bet_amount={total_bet_amount:.2f}, CAP={cap_percentage}%, threshold={cap_threshold:.2f}")
logger.info(f"📊 [EXTRACTION DEBUG] Payouts: {payouts}") logger.info(f"📊 [EXTRACTION DEBUG] Payouts: {payouts}")
# Step 6: Filter payouts below CAP threshold # Step 6: Filter payouts below adjusted CAP threshold
logger.info(f"🎯 [EXTRACTION DEBUG] Step 6: Filtering payouts below CAP threshold") logger.info(f"🎯 [EXTRACTION DEBUG] Step 6: Filtering payouts below adjusted CAP threshold")
eligible_payouts = {k: v for k, v in payouts.items() if v <= cap_threshold} eligible_payouts = {k: v for k, v in payouts.items() if v <= cap_threshold}
logger.info(f"🎯 [EXTRACTION DEBUG] Eligible payouts (≤ {cap_threshold:.2f}): {eligible_payouts}") logger.info(f"🎯 [EXTRACTION DEBUG] Eligible payouts (≤ {cap_threshold:.2f}): {eligible_payouts}")
...@@ -2564,11 +2588,40 @@ class GamesThread(ThreadedComponent): ...@@ -2564,11 +2588,40 @@ class GamesThread(ThreadedComponent):
bet.set_result('win', win_amount) bet.set_result('win', win_amount)
logger.info(f"DEBUG _update_bet_results: Set bet {bet.id} to win with amount {win_amount}") logger.info(f"DEBUG _update_bet_results: Set bet {bet.id} to win with amount {win_amount}")
# Update bets for associated winning outcomes to 'win'
if extraction_winning_outcome_names:
logger.info(f"DEBUG _update_bet_results: Updating bets for {len(extraction_winning_outcome_names)} associated winning outcomes: {extraction_winning_outcome_names}")
for outcome_name in extraction_winning_outcome_names:
# Skip if this outcome is already handled above (selected_result)
if outcome_name == selected_result:
continue
# Get coefficient for this associated outcome
associated_coefficient = self._get_outcome_coefficient(match_id, outcome_name, session)
associated_winning_bets = session.query(BetDetailModel).filter(
BetDetailModel.match_id == match_id,
BetDetailModel.outcome == outcome_name,
BetDetailModel.result == 'pending'
).all()
logger.info(f"DEBUG _update_bet_results: Found {len(associated_winning_bets)} winning {outcome_name} bets (associated)")
for bet in associated_winning_bets:
win_amount = bet.amount * associated_coefficient
bet.set_result('win', win_amount)
logger.info(f"DEBUG _update_bet_results: Set associated bet {bet.id} ({outcome_name}) to win with amount {win_amount}")
# Update all other bets to 'lost' # Update all other bets to 'lost'
losing_outcomes = [selected_result, 'UNDER', 'OVER']
if extraction_winning_outcome_names:
losing_outcomes.extend(extraction_winning_outcome_names)
losing_count = session.query(BetDetailModel).filter( losing_count = session.query(BetDetailModel).filter(
BetDetailModel.match_id == match_id, BetDetailModel.match_id == match_id,
BetDetailModel.result == 'pending', BetDetailModel.result == 'pending',
~BetDetailModel.outcome.in_([selected_result, 'UNDER', 'OVER']) ~BetDetailModel.outcome.in_(losing_outcomes)
).update({'result': 'lost'}) ).update({'result': 'lost'})
logger.info(f"DEBUG _update_bet_results: Set {losing_count} other bets to lost") logger.info(f"DEBUG _update_bet_results: Set {losing_count} other bets to lost")
......
...@@ -585,6 +585,7 @@ class WebDashboard(ThreadedComponent): ...@@ -585,6 +585,7 @@ class WebDashboard(ThreadedComponent):
response = message.data.get("response") response = message.data.get("response")
timer_update = message.data.get("timer_update") timer_update = message.data.get("timer_update")
fixture_status_update = message.data.get("fixture_status_update") fixture_status_update = message.data.get("fixture_status_update")
download_progress = message.data.get("download_progress")
if response == "timer_state": if response == "timer_state":
# Update stored timer state # Update stored timer state
...@@ -619,9 +620,17 @@ class WebDashboard(ThreadedComponent): ...@@ -619,9 +620,17 @@ class WebDashboard(ThreadedComponent):
# Add fixture status update to notification queue for long-polling clients # Add fixture status update to notification queue for long-polling clients
self._add_client_notification("FIXTURE_STATUS_UPDATE", fixture_status_update, message.timestamp) self._add_client_notification("FIXTURE_STATUS_UPDATE", fixture_status_update, message.timestamp)
elif download_progress:
# Handle download progress updates from API client
if self.socketio:
# Broadcast download progress to all connected clients
self.socketio.emit('download_progress', download_progress)
logger.debug(f"Broadcasted download progress: {download_progress}")
except Exception as e: except Exception as e:
logger.error(f"Failed to handle custom message: {e}") logger.error(f"Failed to handle custom message: {e}")
def _handle_client_notification(self, message: Message): def _handle_client_notification(self, message: Message):
"""Handle messages that should be sent to long-polling clients""" """Handle messages that should be sent to long-polling clients"""
try: try:
......
This diff is collapsed.
...@@ -64,10 +64,29 @@ window.Dashboard = (function() { ...@@ -64,10 +64,29 @@ window.Dashboard = (function() {
} }
}); });
// Setup SocketIO event listeners if available
setupSocketIOListeners();
// Toast notifications // Toast notifications
setupToastNotifications(); setupToastNotifications();
} }
// Setup SocketIO event listeners
function setupSocketIOListeners() {
// Check if SocketIO is available
if (typeof io !== 'undefined' && window.socket) {
console.log('Setting up SocketIO event listeners for download progress');
// Listen for download progress events
window.socket.on('download_progress', function(data) {
console.log('Received download_progress via SocketIO:', data);
updateDownloadProgress(data);
});
} else {
console.log('SocketIO not available, using long polling for download progress');
}
}
// Setup offline/online detection // Setup offline/online detection
function setupOfflineDetection() { function setupOfflineDetection() {
window.addEventListener('online', function() { window.addEventListener('online', function() {
...@@ -803,6 +822,10 @@ window.Dashboard = (function() { ...@@ -803,6 +822,10 @@ window.Dashboard = (function() {
} }
} }
} }
else if (notification.type === 'download_progress') {
console.log('Download progress update:', notification.data);
updateDownloadProgress(notification.data);
}
else { else {
console.log('Unknown notification type:', notification.type); console.log('Unknown notification type:', notification.type);
} }
...@@ -861,6 +884,56 @@ window.Dashboard = (function() { ...@@ -861,6 +884,56 @@ window.Dashboard = (function() {
return new Date(timestamp).toLocaleString(); return new Date(timestamp).toLocaleString();
} }
// Download progress handling
function updateDownloadProgress(data) {
const progressContainer = document.getElementById('download-progress-container');
const progressBar = document.getElementById('download-progress-bar');
const progressText = document.getElementById('download-progress-text');
if (!progressContainer || !progressBar || !progressText) {
console.warn('Download progress elements not found');
return;
}
const percentage = data.percentage || 0;
const message = data.message || '';
const downloaded = data.downloaded || 0;
const total = data.total || 0;
// Show progress container
progressContainer.classList.remove('d-none');
// Update progress bar
progressBar.style.width = percentage + '%';
progressBar.setAttribute('aria-valuenow', percentage);
// Update progress text
if (total > 0) {
progressText.textContent = `${percentage}% (${downloaded}/${total})`;
} else {
progressText.textContent = message || `${percentage}%`;
}
// Change progress bar color based on completion
if (percentage >= 100) {
progressBar.classList.remove('progress-bar-striped', 'progress-bar-animated');
progressBar.classList.add('bg-success');
// Hide progress after 3 seconds when complete
setTimeout(function() {
progressContainer.classList.add('d-none');
// Reset progress bar for next download
progressBar.style.width = '0%';
progressBar.classList.remove('bg-success');
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
}, 3000);
} else {
progressBar.classList.remove('bg-success');
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
}
console.log('Updated download progress:', data);
}
// Public API // Public API
return { return {
init: init, init: init,
......
...@@ -77,12 +77,12 @@ ...@@ -77,12 +77,12 @@
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
{% if request.endpoint not in ['main.cashier_dashboard', 'main.cashier_bets', 'main.cashier_new_bet', 'main.cashier_bet_details', 'main.cashier_verify_bet_page'] %} {% if request.endpoint not in ['main.cashier_dashboard', 'main.cashier_bets', 'main.cashier_new_bet', 'main.cashier_bet_details', 'main.cashier_verify_bet_page', 'main.cashier_reports'] %}
<ul class="navbar-nav me-auto"> <ul class="navbar-nav me-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'main.index' %}active{% endif %}" <a class="nav-link {% if request.endpoint == 'main.index' %}active{% endif %}"
href="{{ url_for('main.index') }}"> href="{{ url_for('main.index') }}">
<i class="fas fa-tachometer-alt me-1"></i>Dashboard <i class="fas fa-tachometer-alt me-1"></i>Home
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
...@@ -109,18 +109,6 @@ ...@@ -109,18 +109,6 @@
<i class="fas fa-cogs me-1"></i>Extraction <i class="fas fa-cogs me-1"></i>Extraction
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'main.video_test' %}active{% endif %}"
href="{{ url_for('main.video_test') }}">
<i class="fas fa-upload me-1"></i>Video Test
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'screen_cast.screen_cast_dashboard' %}active{% endif %}"
href="{{ url_for('screen_cast.screen_cast_dashboard') }}">
<i class="fas fa-cast me-1"></i>Screen Cast
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'main.api_tokens' %}active{% endif %}" <a class="nav-link {% if request.endpoint == 'main.api_tokens' %}active{% endif %}"
href="{{ url_for('main.api_tokens') }}"> href="{{ url_for('main.api_tokens') }}">
...@@ -133,6 +121,12 @@ ...@@ -133,6 +121,12 @@
<i class="fas fa-chart-bar me-1"></i>Statistics <i class="fas fa-chart-bar me-1"></i>Statistics
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'main.reports' %}active{% endif %}"
href="{{ url_for('main.reports') }}">
<i class="fas fa-file-alt me-1"></i>Reports
</a>
</li>
{% if current_user.is_admin %} {% if current_user.is_admin %}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
...@@ -148,6 +142,9 @@ ...@@ -148,6 +142,9 @@
<li><a class="dropdown-item" href="{{ url_for('main.logs') }}"> <li><a class="dropdown-item" href="{{ url_for('main.logs') }}">
<i class="fas fa-file-alt me-1"></i>Logs <i class="fas fa-file-alt me-1"></i>Logs
</a></li> </a></li>
<li><a class="dropdown-item" href="{{ url_for('screen_cast.screen_cast_dashboard') }}">
<i class="fas fa-cast me-1"></i>Screen Cast
</a></li>
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
...@@ -173,6 +170,12 @@ ...@@ -173,6 +170,12 @@
<i class="fas fa-qrcode me-1"></i>Verify Bet <i class="fas fa-qrcode me-1"></i>Verify Bet
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'main.cashier_reports' %}active{% endif %}"
href="{{ url_for('main.cashier_reports') }}">
<i class="fas fa-file-alt me-1"></i>Reports
</a>
</li>
</ul> </ul>
{% endif %} {% endif %}
...@@ -232,6 +235,22 @@ ...@@ -232,6 +235,22 @@
<!-- System Status Bar --> <!-- System Status Bar -->
<div id="status-bar" class="fixed-bottom bg-light border-top p-2 d-none d-lg-block"> <div id="status-bar" class="fixed-bottom bg-light border-top p-2 d-none d-lg-block">
<div class="container-fluid"> <div class="container-fluid">
<!-- Download Progress Bar (hidden by default) -->
<div id="download-progress-container" class="row mb-2 d-none">
<div class="col-12">
<div class="d-flex align-items-center">
<span class="text-muted me-2">
<i class="fas fa-download me-1"></i>Downloading Fixtures:
</span>
<div class="progress flex-grow-1 me-2" style="height: 8px;">
<div id="download-progress-bar" class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%"></div>
</div>
<small id="download-progress-text" class="text-muted">0%</small>
</div>
</div>
</div>
<!-- Status Information -->
<div class="row align-items-center text-small"> <div class="row align-items-center text-small">
<div class="col-auto"> <div class="col-auto">
<span class="text-muted">Status:</span> <span class="text-muted">Status:</span>
......
This diff is collapsed.
...@@ -37,6 +37,9 @@ opencv-python>=4.5.0 ...@@ -37,6 +37,9 @@ opencv-python>=4.5.0
Pillow>=9.0.0 Pillow>=9.0.0
python-barcode[images]>=0.14.0 python-barcode[images]>=0.14.0
# Excel file generation
openpyxl>=3.0.0
# Screen capture and streaming (optional dependencies) # Screen capture and streaming (optional dependencies)
ffmpeg-python>=0.2.0 ffmpeg-python>=0.2.0
pychromecast>=13.0.0 pychromecast>=13.0.0
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Progress Bar</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<style>
body { padding: 20px; }
.fixed-bottom { position: fixed; bottom: 0; left: 0; right: 0; }
</style>
</head>
<body>
<h1>Test Progress Bar</h1>
<button id="test-progress" class="btn btn-primary">Test Progress Bar</button>
<!-- System Status Bar -->
<div id="status-bar" class="fixed-bottom bg-light border-top p-2 d-none d-lg-block">
<div class="container-fluid">
<!-- Download Progress Bar (hidden by default) -->
<div id="download-progress-container" class="row mb-2 d-none">
<div class="col-12">
<div class="d-flex align-items-center">
<span class="text-muted me-2">
<i class="fas fa-download me-1"></i>Downloading Fixtures:
</span>
<div class="progress flex-grow-1 me-2" style="height: 8px;">
<div id="download-progress-bar" class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%"></div>
</div>
<small id="download-progress-text" class="text-muted">0%</small>
</div>
</div>
</div>
<!-- Status Information -->
<div class="row align-items-center text-small">
<div class="col-auto">
<span class="text-muted">Status:</span>
<span id="system-status" class="badge bg-success">Online</span>
</div>
<div class="col-auto">
<span class="text-muted">Video:</span>
<span id="video-status" class="badge bg-secondary">Stopped</span>
</div>
<div class="col-auto">
<span class="text-muted">Match Timer:</span>
<span id="match-timer" class="badge bg-warning text-dark">--:--</span>
</div>
<div class="col-auto">
<span class="text-muted">Last Updated:</span>
<span id="last-updated" class="text-muted">--</span>
</div>
<div class="col text-end">
<small class="text-muted">Test v1.0.0</small>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Test function to simulate download progress
function updateDownloadProgress(data) {
const progressContainer = document.getElementById('download-progress-container');
const progressBar = document.getElementById('download-progress-bar');
const progressText = document.getElementById('download-progress-text');
if (!progressContainer || !progressBar || !progressText) {
console.warn('Download progress elements not found');
return;
}
const percentage = data.percentage || 0;
const message = data.message || '';
const downloaded = data.downloaded || 0;
const total = data.total || 0;
// Show progress container
progressContainer.classList.remove('d-none');
// Update progress bar
progressBar.style.width = percentage + '%';
progressBar.setAttribute('aria-valuenow', percentage);
// Update progress text
if (total > 0) {
progressText.textContent = `${percentage}% (${downloaded}/${total})`;
} else {
progressText.textContent = message || `${percentage}%`;
}
// Change progress bar color based on completion
if (percentage >= 100) {
progressBar.classList.remove('progress-bar-striped', 'progress-bar-animated');
progressBar.classList.add('bg-success');
// Hide progress after 3 seconds when complete
setTimeout(function() {
progressContainer.classList.add('d-none');
// Reset progress bar for next download
progressBar.style.width = '0%';
progressBar.classList.remove('bg-success');
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
}, 3000);
} else {
progressBar.classList.remove('bg-success');
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
}
console.log('Updated download progress:', data);
}
// Test button click handler
document.getElementById('test-progress').addEventListener('click', function() {
// Simulate a download progress sequence
let progress = 0;
const total = 5;
let downloaded = 0;
const interval = setInterval(function() {
if (downloaded < total) {
downloaded++;
progress = Math.round((downloaded / total) * 100);
updateDownloadProgress({
downloaded: downloaded,
total: total,
percentage: progress,
message: `Downloading file ${downloaded}/${total}...`
});
} else {
// Final completion
updateDownloadProgress({
downloaded: total,
total: total,
percentage: 100,
message: `Downloads completed - ${total}/${total} files`
});
clearInterval(interval);
}
}, 1000);
});
</script>
</body>
</html>
\ 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