Commit aab8d769 authored by Your Name's avatar Your Name

Add arbitrage verification and manual fixture activation/deactivation

- Added arbitrage verification when all ZIP files are uploaded for a fixture
- Automatic fixture activation only if arbitrage check passes
- Added manual activate/deactivate buttons in fixture detail page
- Fixtures are only activated if all matches have ZIP files and pass arbitrage protection
- Logging added for arbitrage detection and fixture activation events
parent 5bf2e5a0
......@@ -633,6 +633,89 @@ def refresh_fixture(fixture_id):
return redirect(url_for('main.fixture_detail', fixture_id=fixture_id))
@csrf.exempt
@bp.route('/fixture/<fixture_id>/activate', methods=['POST'])
@login_required
@require_active_user
def activate_fixture(fixture_id):
"""Manually activate all matches in a fixture"""
try:
from app.models import Match
# Verify fixture exists and user has access
if not current_user.is_admin:
matches = Match.query.filter_by(fixture_id=fixture_id, created_by=current_user.id).all()
else:
matches = Match.query.filter_by(fixture_id=fixture_id).all()
if not matches:
flash('Fixture not found', 'error')
return redirect(url_for('main.fixtures'))
# Check if all matches have ZIP files
all_have_zip = all([m.zip_upload_status == 'completed' for m in matches])
if not all_have_zip:
flash('Cannot activate fixture: Not all matches have ZIP files uploaded', 'warning')
return redirect(url_for('main.fixture_detail', fixture_id=fixture_id))
# Activate all matches
activated_count = 0
for match in matches:
if match.set_active():
activated_count += 1
db.session.commit()
flash(f'Fixture activated successfully! {activated_count} matches are now active.', 'success')
logger.info(f"Fixture {fixture_id} manually activated by user {current_user.username}")
return redirect(url_for('main.fixture_detail', fixture_id=fixture_id))
except Exception as e:
db.session.rollback()
logger.error(f"Activate fixture error: {str(e)}")
flash(f'Error activating fixture: {str(e)}', 'error')
return redirect(url_for('main.fixture_detail', fixture_id=fixture_id))
@csrf.exempt
@bp.route('/fixture/<fixture_id>/deactivate', methods=['POST'])
@login_required
@require_active_user
def deactivate_fixture(fixture_id):
"""Manually deactivate all matches in a fixture"""
try:
from app.models import Match
# Verify fixture exists and user has access
if not current_user.is_admin:
matches = Match.query.filter_by(fixture_id=fixture_id, created_by=current_user.id).all()
else:
matches = Match.query.filter_by(fixture_id=fixture_id).all()
if not matches:
flash('Fixture not found', 'error')
return redirect(url_for('main.fixtures'))
# Deactivate all matches
deactivated_count = 0
for match in matches:
if match.active_status:
match.active_status = False
deactivated_count += 1
db.session.commit()
flash(f'Fixture deactivated successfully! {deactivated_count} matches are now inactive.', 'success')
logger.info(f"Fixture {fixture_id} manually deactivated by user {current_user.username}")
return redirect(url_for('main.fixture_detail', fixture_id=fixture_id))
except Exception as e:
db.session.rollback()
logger.error(f"Deactivate fixture error: {str(e)}")
flash(f'Error deactivating fixture: {str(e)}', 'error')
return redirect(url_for('main.fixture_detail', fixture_id=fixture_id))
@csrf.exempt
@bp.route('/fixture/<fixture_id>/delete', methods=['POST'])
@login_required
......
......@@ -333,37 +333,52 @@
<a href="{{ url_for('main.fixtures') }}" class="btn">← Back to Fixtures</a>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<!-- Activation Controls -->
{% if fixture_info.active_matches == fixture_info.total_matches %}
<form method="POST" action="{{ url_for('main.deactivate_fixture', fixture_id=fixture_info.fixture_id) }}"
style="display: inline;"
onsubmit="return confirm('Deactivate all matches in this fixture?');">
<button type="submit" class="btn btn-warning">🔴 Deactivate Fixture</button>
</form>
{% else %}
<form method="POST" action="{{ url_for('main.activate_fixture', fixture_id=fixture_info.fixture_id) }}"
style="display: inline;"
onsubmit="return confirm('Activate all matches in this fixture? All matches must have ZIP files uploaded.');">
<button type="submit" class="btn btn-success">🟢 Activate Fixture</button>
</form>
{% endif %}
<!-- Sure Bet Analysis Buttons -->
<a href="{{ url_for('main.analyze_fixture_sure_bets', fixture_id=fixture_info.fixture_id) }}"
<a href="{{ url_for('main.analyze_fixture_sure_bets', fixture_id=fixture_info.fixture_id) }}"
class="btn btn-warning" title="Analyze fixture for sure bet conditions">
🔍 Analyze Odds
</a>
{% if analysis_result and analysis_result.has_sure_bets %}
<a href="{{ url_for('main.repair_fixture_sure_bets', fixture_id=fixture_info.fixture_id) }}"
class="btn btn-success"
<a href="{{ url_for('main.repair_fixture_sure_bets', fixture_id=fixture_info.fixture_id) }}"
class="btn btn-success"
onclick="return confirm('This will adjust odds to fix sure bet conditions. Continue?');"
title="Automatically repair sure bet conditions">
🔧 Repair Odds
</a>
<a href="{{ url_for('main.save_fixture_as_new', fixture_id=fixture_info.fixture_id) }}"
class="btn btn-info"
<a href="{{ url_for('main.save_fixture_as_new', fixture_id=fixture_info.fixture_id) }}"
class="btn btn-info"
onclick="return confirm('Create a copy of this fixture as a new fixture? Clients will update to the new fixture.');"
title="Save as new fixture for clients to update">
💾 Save as New
</a>
{% endif %}
<a href="{{ url_for('main.refresh_fixture', fixture_id=fixture_info.fixture_id) }}"
class="btn btn-warning"
<a href="{{ url_for('main.refresh_fixture', fixture_id=fixture_info.fixture_id) }}"
class="btn btn-warning"
onclick="return confirm('This will refresh the fixture so clients will download it as a new update. Continue?');"
title="Refresh fixture for clients">
🔄 Refresh
</a>
<form method="POST" action="{{ url_for('main.delete_fixture', fixture_id=fixture_info.fixture_id) }}"
style="display: inline;"
<form method="POST" action="{{ url_for('main.delete_fixture', fixture_id=fixture_info.fixture_id) }}"
style="display: inline;"
onsubmit="return confirm('Are you sure you want to delete this entire fixture and all {{ fixture_info.total_matches }} matches? This action cannot be undone.');">
<button type="submit" class="btn btn-danger">Delete Entire Fixture</button>
</form>
......
......@@ -121,12 +121,31 @@ def upload_zip():
match.zip_upload_status = 'completed'
match.zip_upload_progress = 100.00
# Set match as active (both fixture and ZIP uploaded)
match.set_active()
db.session.commit()
flash(f'ZIP file uploaded successfully for Match #{match.match_number}! Match is now active.', 'success')
# Check if all matches in fixture have ZIP files uploaded
all_matches = Match.query.filter_by(fixture_id=match.fixture_id).all()
all_have_zip = all([m.zip_upload_status == 'completed' for m in all_matches])
if all_have_zip:
# Run arbitrage verification
from app.utils.sure_bet_analyzer import analyze_fixture_for_sure_bets
analysis_result = analyze_fixture_for_sure_bets(match.fixture_id)
if analysis_result.get('has_sure_bets'):
# Arbitrage detected - do not activate
flash(f'ZIP file uploaded for Match #{match.match_number}. ⚠️ Arbitrage protection: {analysis_result.get("total_issues")} issue(s) detected. Please fix odds before activation.', 'warning')
logger.warning(f"Arbitrage detected in fixture {match.fixture_id} - activation blocked")
else:
# No arbitrage - activate all matches
for m in all_matches:
m.set_active()
db.session.commit()
flash(f'ZIP file uploaded successfully for Match #{match.match_number}! All matches passed arbitrage verification and are now active.', 'success')
logger.info(f"Fixture {match.fixture_id} passed arbitrage verification and activated")
else:
flash(f'ZIP file uploaded successfully for Match #{match.match_number}. Waiting for all matches to have ZIP files before activation.', 'success')
return redirect(request.referrer or url_for('main.fixture_detail', fixture_id=match.fixture_id))
except Exception as e:
......@@ -179,12 +198,31 @@ def upload_zip_page(match_id):
match.zip_upload_status = 'completed'
match.zip_upload_progress = 100.00
# Set match as active (both fixture and ZIP uploaded)
match.set_active()
db.session.commit()
flash('ZIP file uploaded successfully! Match is now active.', 'success')
# Check if all matches in fixture have ZIP files uploaded
all_matches = Match.query.filter_by(fixture_id=match.fixture_id).all()
all_have_zip = all([m.zip_upload_status == 'completed' for m in all_matches])
if all_have_zip:
# Run arbitrage verification
from app.utils.sure_bet_analyzer import analyze_fixture_for_sure_bets
analysis_result = analyze_fixture_for_sure_bets(match.fixture_id)
if analysis_result.get('has_sure_bets'):
# Arbitrage detected - do not activate
flash(f'ZIP file uploaded. ⚠️ Arbitrage protection: {analysis_result.get("total_issues")} issue(s) detected. Please fix odds before activation.', 'warning')
logger.warning(f"Arbitrage detected in fixture {match.fixture_id} - activation blocked")
else:
# No arbitrage - activate all matches
for m in all_matches:
m.set_active()
db.session.commit()
flash('ZIP file uploaded successfully! All matches passed arbitrage verification and are now active.', 'success')
logger.info(f"Fixture {match.fixture_id} passed arbitrage verification and activated")
else:
flash('ZIP file uploaded successfully! Waiting for all matches to have ZIP files before activation.', 'success')
return redirect(url_for('main.match_detail', id=match_id))
except Exception as e:
......@@ -907,13 +945,25 @@ def upload_fixture_zip(fixture_id):
match.zip_sha1sum = upload_record.sha1sum
match.zip_upload_status = 'completed'
match.zip_upload_progress = 100.00
# Set match as active (both fixture and ZIP uploaded)
match.set_active()
db.session.commit()
flash(f'ZIP file uploaded successfully for {len(matches_without_zip)} matches! All matches are now active.', 'success')
# Run arbitrage verification since all matches now have ZIP files
from app.utils.sure_bet_analyzer import analyze_fixture_for_sure_bets
analysis_result = analyze_fixture_for_sure_bets(fixture_id)
if analysis_result.get('has_sure_bets'):
# Arbitrage detected - do not activate
flash(f'ZIP file uploaded for {len(matches_without_zip)} matches. ⚠️ Arbitrage protection: {analysis_result.get("total_issues")} issue(s) detected. Please fix odds before activation.', 'warning')
logger.warning(f"Arbitrage detected in fixture {fixture_id} - activation blocked")
else:
# No arbitrage - activate all matches
for match in matches:
match.set_active()
db.session.commit()
flash(f'ZIP file uploaded successfully for {len(matches_without_zip)} matches! All matches passed arbitrage verification and are now active.', 'success')
logger.info(f"Fixture {fixture_id} passed arbitrage verification and activated")
return redirect(request.referrer or url_for('main.fixture_detail', fixture_id=fixture_id))
except Exception as e:
......@@ -1080,13 +1130,34 @@ def finalize_upload():
match.zip_sha1sum = upload_record.sha1sum
match.zip_upload_status = 'completed'
match.zip_upload_progress = 100.00
match.set_active()
db.session.commit()
# Check if all matches in fixture have ZIP files uploaded
all_matches = Match.query.filter_by(fixture_id=match.fixture_id).all()
all_have_zip = all([m.zip_upload_status == 'completed' for m in all_matches])
if all_have_zip:
# Run arbitrage verification
from app.utils.sure_bet_analyzer import analyze_fixture_for_sure_bets
analysis_result = analyze_fixture_for_sure_bets(match.fixture_id)
if analysis_result.get('has_sure_bets'):
# Arbitrage detected - do not activate
flash(f'ZIP file uploaded for Match #{match.match_number}. ⚠️ Arbitrage protection: {analysis_result.get("total_issues")} issue(s) detected. Please fix odds before activation.', 'warning')
logger.warning(f"Arbitrage detected in fixture {match.fixture_id} - activation blocked")
else:
# No arbitrage - activate all matches
for m in all_matches:
m.set_active()
db.session.commit()
flash(f'ZIP file uploaded successfully for Match #{match.match_number}! All matches passed arbitrage verification and are now active.', 'success')
logger.info(f"Fixture {match.fixture_id} passed arbitrage verification and activated")
else:
flash(f'ZIP file uploaded successfully for Match #{match.match_number}. Waiting for all matches to have ZIP files before activation.', 'success')
# Clean up temp files
shutil.rmtree(temp_dir)
flash(f'ZIP file uploaded successfully for Match #{match.match_number}! Match is now active.', 'success')
return jsonify({
'success': True,
'redirect': url_for('main.fixture_detail', fixture_id=match.fixture_id)
......
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