Resitribution daily correction

parent 3b50b307
......@@ -12,7 +12,7 @@ from typing import Optional, Dict, Any, List
from .thread_manager import ThreadedComponent
from .message_bus import MessageBus, Message, MessageType, MessageBuilder
from ..database.manager import DatabaseManager
from ..database.models import MatchModel, MatchStatus, BetDetailModel, MatchOutcomeModel, GameConfigModel, ExtractionAssociationModel
from ..database.models import MatchModel, MatchStatus, BetDetailModel, MatchOutcomeModel, GameConfigModel, ExtractionAssociationModel, DailyRedistributionShortfallModel
logger = logging.getLogger(__name__)
......@@ -1880,6 +1880,62 @@ class GamesThread(ThreadedComponent):
logger.error(f"Failed to get redistribution CAP: {e}")
return 70.0
def _get_daily_shortfall(self, date, session) -> float:
"""Get accumulated shortfall for a specific date"""
try:
shortfall_record = session.query(DailyRedistributionShortfallModel).filter_by(
date=date
).first()
if shortfall_record:
logger.debug(f"Found shortfall record for {date}: {shortfall_record.accumulated_shortfall}")
return shortfall_record.accumulated_shortfall
else:
logger.debug(f"No shortfall record found for {date}, returning 0.0")
return 0.0
except Exception as e:
logger.error(f"Failed to get daily shortfall for {date}: {e}")
return 0.0
def _update_daily_shortfall(self, date, payin_amount, redistributed_amount, cap_percentage, session):
"""Update daily shortfall tracking after extraction"""
try:
# Calculate the shortfall for this extraction
expected_redistribution = payin_amount * (cap_percentage / 100.0)
shortfall = max(0, expected_redistribution - redistributed_amount)
logger.info(f"💰 [SHORTFALL DEBUG] Payin: {payin_amount:.2f}, Expected: {expected_redistribution:.2f}, Redistributed: {redistributed_amount:.2f}, Shortfall: {shortfall:.2f}")
# Get or create the daily record
shortfall_record = session.query(DailyRedistributionShortfallModel).filter_by(
date=date
).first()
if not shortfall_record:
shortfall_record = DailyRedistributionShortfallModel(
date=date,
accumulated_shortfall=shortfall,
total_payin=payin_amount,
total_redistributed=redistributed_amount,
cap_percentage=cap_percentage
)
session.add(shortfall_record)
logger.info(f"Created new shortfall record for {date} with shortfall {shortfall:.2f}")
else:
# Update existing record
shortfall_record.accumulated_shortfall += shortfall
shortfall_record.total_payin += payin_amount
shortfall_record.total_redistributed += redistributed_amount
shortfall_record.cap_percentage = cap_percentage # Update to latest
logger.info(f"Updated shortfall record for {date}, new accumulated shortfall: {shortfall_record.accumulated_shortfall:.2f}")
session.commit()
except Exception as e:
logger.error(f"Failed to update daily shortfall for {date}: {e}")
session.rollback()
def _weighted_random_selection(self, under_coeff: float, over_coeff: float) -> str:
"""Weighted random selection based on inverse coefficients"""
try:
......@@ -2308,8 +2364,17 @@ class GamesThread(ThreadedComponent):
# Step 5: Get redistribution CAP
logger.info(f"🎯 [EXTRACTION DEBUG] Step 5: Retrieving redistribution CAP")
cap_percentage = self._get_redistribution_cap()
cap_threshold = total_bet_amount * (cap_percentage / 100.0)
logger.info(f"🎯 [EXTRACTION DEBUG] CAP percentage: {cap_percentage}%, threshold: {cap_threshold:.2f}")
# Step 5.1: 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()
accumulated_shortfall = self._get_daily_shortfall(today, session)
logger.info(f"🎯 [EXTRACTION DEBUG] Accumulated shortfall: {accumulated_shortfall:.2f}")
# Step 5.2: Calculate CAP threshold including shortfall
base_cap_threshold = total_bet_amount * (cap_percentage / 100.0)
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}")
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}")
......@@ -2345,6 +2410,11 @@ class GamesThread(ThreadedComponent):
logger.info(f"📈 [EXTRACTION DEBUG] Step 9: Collecting match statistics")
self._collect_match_statistics(match_id, fixture_id, selected_result, session)
# Step 10: Update daily shortfall tracking
logger.info(f"💰 [EXTRACTION DEBUG] Step 10: Updating daily shortfall tracking")
today = datetime.now().date()
self._update_daily_shortfall(today, total_bet_amount, payouts[selected_result], cap_percentage, session)
logger.info(f"✅ [EXTRACTION DEBUG] Result extraction completed successfully: selected {selected_result}")
# DEBUG: Final check - ensure result is not None
......
......@@ -2677,6 +2677,60 @@ class Migration_034_AddDefaultLicenseText(DatabaseMigration):
return False
class Migration_035_AddDailyRedistributionShortfallTable(DatabaseMigration):
"""Add daily_redistribution_shortfall table for tracking CAP shortfalls across matches"""
def __init__(self):
super().__init__("035", "Add daily_redistribution_shortfall table for tracking CAP shortfalls")
def up(self, db_manager) -> bool:
"""Create daily_redistribution_shortfall table"""
try:
with db_manager.engine.connect() as conn:
# Create daily_redistribution_shortfall table
conn.execute(text("""
CREATE TABLE IF NOT EXISTS daily_redistribution_shortfall (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL UNIQUE,
accumulated_shortfall REAL DEFAULT 0.0 NOT NULL,
total_payin REAL DEFAULT 0.0 NOT NULL,
total_redistributed REAL DEFAULT 0.0 NOT NULL,
cap_percentage REAL DEFAULT 70.0 NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
)
"""))
# Create indexes for daily_redistribution_shortfall table
indexes = [
"CREATE INDEX IF NOT EXISTS ix_daily_redistribution_shortfall_date ON daily_redistribution_shortfall(date)",
]
for index_sql in indexes:
conn.execute(text(index_sql))
conn.commit()
logger.info("Daily redistribution shortfall table created successfully")
return True
except Exception as e:
logger.error(f"Failed to create daily_redistribution_shortfall table: {e}")
return False
def down(self, db_manager) -> bool:
"""Drop daily_redistribution_shortfall table"""
try:
with db_manager.engine.connect() as conn:
conn.execute(text("DROP TABLE IF EXISTS daily_redistribution_shortfall"))
conn.commit()
logger.info("Daily redistribution shortfall table dropped")
return True
except Exception as e:
logger.error(f"Failed to drop daily_redistribution_shortfall table: {e}")
return False
# Registry of all migrations in order
# Registry of all migrations in order
......@@ -2715,6 +2769,7 @@ MIGRATIONS: List[DatabaseMigration] = [
Migration_032_FixExtractionAssociationDefaults(),
Migration_033_AddBarcodeFieldsToBets(),
Migration_034_AddDefaultLicenseText(),
Migration_035_AddDailyRedistributionShortfallTable(),
]
......
......@@ -7,7 +7,7 @@ import hashlib
from datetime import datetime, timedelta, timezone
from typing import Dict, Any, Optional, List
from sqlalchemy import (
Column, Integer, String, Text, DateTime, Boolean, Float,
Column, Integer, String, Text, DateTime, Date, Boolean, Float,
JSON, ForeignKey, UniqueConstraint, Index, create_engine, Enum
)
from sqlalchemy.ext.declarative import declarative_base
......@@ -946,3 +946,21 @@ class ExtractionStatsModel(BaseModel):
def __repr__(self):
return f'<ExtractionStats Match {self.match_id}: {self.total_bets} bets, {self.total_amount_collected:.2f} collected, {self.actual_result}>'
class DailyRedistributionShortfallModel(BaseModel):
"""Daily redistribution shortfall tracking for CAP adjustments"""
__tablename__ = 'daily_redistribution_shortfall'
__table_args__ = (
Index('ix_daily_redistribution_shortfall_date', 'date'),
UniqueConstraint('date', name='uq_daily_redistribution_shortfall_date'),
)
date = Column(Date, nullable=False, unique=True, comment='Date for shortfall tracking')
accumulated_shortfall = Column(Float(precision=2), default=0.0, nullable=False, comment='Accumulated shortfall from previous extractions')
total_payin = Column(Float(precision=2), default=0.0, nullable=False, comment='Total payin for the day')
total_redistributed = Column(Float(precision=2), default=0.0, nullable=False, comment='Total redistributed for the day')
cap_percentage = Column(Float(precision=2), default=70.0, nullable=False, comment='CAP percentage used for calculations')
def __repr__(self):
return f'<DailyRedistributionShortfall {self.date}: shortfall={self.accumulated_shortfall:.2f}, payin={self.total_payin:.2f}, redistributed={self.total_redistributed:.2f}>'
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