Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
MBetterd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mbetter
MBetterd
Commits
cc4ddd48
Commit
cc4ddd48
authored
Feb 02, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement MatchReport update for incremental sync
parent
e69c15ea
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
863 additions
and
85 deletions
+863
-85
routes.py
app/api/routes.py
+104
-43
migrations.py
app/database/migrations.py
+114
-1
allow_null_results.py
app/database/migrations/allow_null_results.py
+127
-0
routes.py
app/main/routes.py
+44
-36
models.py
app/models.py
+4
-4
match_detail.html
app/templates/main/match_detail.html
+2
-1
test_matchreport_update.py
test_matchreport_update.py
+294
-0
test_simple_matchreport_update.py
test_simple_matchreport_update.py
+174
-0
No files found.
app/api/routes.py
View file @
cc4ddd48
...
...
@@ -1197,14 +1197,30 @@ def api_reports_sync():
}),
400
# Create bet record with match_id and match_number from first detail
first_detail
=
bet_data
.
get
(
'details'
,
[{}])[
0
]
if
bet_data
.
get
(
'details'
)
else
{}
# Ensure we get a valid match_id from details
details
=
bet_data
.
get
(
'details'
,
[])
first_detail
=
details
[
0
]
if
details
else
{}
# Get match_id from first detail that has it
match_id
=
None
match_number
=
None
for
detail
in
details
:
if
detail
.
get
(
'match_id'
):
match_id
=
detail
.
get
(
'match_id'
)
match_number
=
detail
.
get
(
'match_number'
)
break
if
not
match_id
:
logger
.
warning
(
f
"Bet {bet_data['uuid']} has no match_id in details, skipping"
)
continue
bet
=
Bet
(
uuid
=
bet_data
[
'uuid'
],
sync_id
=
report_sync
.
id
,
client_id
=
client_id
,
fixture_id
=
bet_data
[
'fixture_id'
],
match_id
=
first_detail
.
get
(
'match_id'
)
,
match_number
=
first_detail
.
get
(
'match_number'
)
,
match_id
=
match_id
,
match_number
=
match_number
,
bet_datetime
=
bet_datetime
,
paid
=
bet_data
.
get
(
'paid'
,
False
),
paid_out
=
bet_data
.
get
(
'paid_out'
,
False
),
...
...
@@ -1248,10 +1264,10 @@ def api_reports_sync():
existing_stats
.
fixture_id
=
stats_data
[
'fixture_id'
]
existing_stats
.
match_datetime
=
datetime
.
fromisoformat
(
stats_data
[
'match_datetime'
])
existing_stats
.
total_bets
=
stats_data
[
'total_bets'
]
existing_stats
.
total_amount_collected
=
stats_data
[
'total_amount_collected'
]
existing_stats
.
total_redistributed
=
stats_data
[
'total_redistributed'
]
existing_stats
.
actual_result
=
stats_data
[
'actual_result'
]
existing_stats
.
extraction_result
=
stats_data
[
'extraction_result'
]
existing_stats
.
total_amount_collected
=
stats_data
.
get
(
'total_amount_collected'
,
0.0
)
or
0.0
existing_stats
.
total_redistributed
=
stats_data
.
get
(
'total_redistributed'
,
0.0
)
or
0.0
existing_stats
.
actual_result
=
stats_data
.
get
(
'actual_result'
)
existing_stats
.
extraction_result
=
stats_data
.
get
(
'extraction_result'
)
existing_stats
.
cap_applied
=
stats_data
.
get
(
'cap_applied'
,
False
)
existing_stats
.
cap_percentage
=
stats_data
.
get
(
'cap_percentage'
)
existing_stats
.
accumulated_shortfall
=
stats_data
.
get
(
'accumulated_shortfall'
,
0.00
)
...
...
@@ -1307,10 +1323,10 @@ def api_reports_sync():
fixture_id
=
stats_data
[
'fixture_id'
],
match_datetime
=
match_datetime
,
total_bets
=
stats_data
[
'total_bets'
],
total_amount_collected
=
stats_data
[
'total_amount_collected'
]
,
total_redistributed
=
stats_data
[
'total_redistributed'
]
,
actual_result
=
stats_data
[
'actual_result'
]
,
extraction_result
=
stats_data
[
'extraction_result'
]
,
total_amount_collected
=
stats_data
.
get
(
'total_amount_collected'
,
0.0
)
or
0.0
,
total_redistributed
=
stats_data
.
get
(
'total_redistributed'
,
0.0
)
or
0.0
,
actual_result
=
stats_data
.
get
(
'actual_result'
)
,
extraction_result
=
stats_data
.
get
(
'extraction_result'
)
,
cap_applied
=
stats_data
.
get
(
'cap_applied'
,
False
),
cap_percentage
=
stats_data
.
get
(
'cap_percentage'
),
accumulated_shortfall
=
stats_data
.
get
(
'accumulated_shortfall'
,
0.00
),
...
...
@@ -1363,11 +1379,43 @@ def api_reports_sync():
match_number
=
match_num
# Calculate balance (payin - payout)
total_payin
=
stats_data
[
'total_amount_collected'
]
total_payout
=
stats_data
[
'total_redistributed'
]
total_payin
=
float
(
stats_data
.
get
(
'total_amount_collected'
,
0.0
)
or
0.0
)
total_payout
=
float
(
stats_data
.
get
(
'total_redistributed'
,
0.0
)
or
0.0
)
balance
=
total_payin
-
total_payout
# Create MatchReport record
# Check if MatchReport already exists for this match and client
existing_match_report
=
MatchReport
.
query
.
filter_by
(
client_id
=
client_id
,
match_id
=
match_id
)
.
first
()
if
existing_match_report
:
# Update existing MatchReport with all fields
existing_match_report
.
sync_id
=
report_sync
.
id
existing_match_report
.
match_number
=
match_number
existing_match_report
.
fixture_id
=
stats_data
[
'fixture_id'
]
existing_match_report
.
match_datetime
=
datetime
.
fromisoformat
(
stats_data
[
'match_datetime'
])
existing_match_report
.
total_bets
=
stats_data
[
'total_bets'
]
existing_match_report
.
winning_bets
=
winning_bets
existing_match_report
.
losing_bets
=
losing_bets
existing_match_report
.
pending_bets
=
pending_bets
existing_match_report
.
total_payin
=
total_payin
existing_match_report
.
total_payout
=
total_payout
existing_match_report
.
balance
=
balance
existing_match_report
.
actual_result
=
stats_data
.
get
(
'actual_result'
)
existing_match_report
.
extraction_result
=
stats_data
.
get
(
'extraction_result'
)
existing_match_report
.
cap_applied
=
stats_data
.
get
(
'cap_applied'
,
False
)
existing_match_report
.
cap_percentage
=
stats_data
.
get
(
'cap_percentage'
)
existing_match_report
.
cap_compensation_balance
=
cap_compensation_balance
existing_match_report
.
accumulated_shortfall
=
stats_data
.
get
(
'accumulated_shortfall'
,
0.00
)
existing_match_report
.
under_bets
=
stats_data
.
get
(
'under_bets'
,
0
)
existing_match_report
.
under_amount
=
stats_data
.
get
(
'under_amount'
,
0.00
)
existing_match_report
.
over_bets
=
stats_data
.
get
(
'over_bets'
,
0
)
existing_match_report
.
over_amount
=
stats_data
.
get
(
'over_amount'
,
0.00
)
existing_match_report
.
result_breakdown
=
stats_data
.
get
(
'result_breakdown'
)
logger
.
info
(
f
"Updated existing MatchReport for match {match_id}"
)
else
:
# Create new MatchReport record
match_report
=
MatchReport
(
sync_id
=
report_sync
.
id
,
client_id
=
client_id
,
...
...
@@ -1383,8 +1431,8 @@ def api_reports_sync():
total_payin
=
total_payin
,
total_payout
=
total_payout
,
balance
=
balance
,
actual_result
=
stats_data
[
'actual_result'
]
,
extraction_result
=
stats_data
[
'extraction_result'
]
,
actual_result
=
stats_data
.
get
(
'actual_result'
)
,
extraction_result
=
stats_data
.
get
(
'extraction_result'
)
,
cap_applied
=
stats_data
.
get
(
'cap_applied'
,
False
),
cap_percentage
=
stats_data
.
get
(
'cap_percentage'
),
cap_compensation_balance
=
cap_compensation_balance
,
...
...
@@ -1396,6 +1444,8 @@ def api_reports_sync():
result_breakdown
=
stats_data
.
get
(
'result_breakdown'
)
)
db
.
session
.
add
(
match_report
)
logger
.
info
(
f
"Created new MatchReport for match {match_id}"
)
match_reports_count
+=
1
# If extraction_stats is empty but we have bets, create MatchReport records from bets
...
...
@@ -1422,6 +1472,8 @@ def api_reports_sync():
)
.
group_by
(
Bet
.
match_id
,
Bet
.
match_number
,
Bet
.
fixture_id
)
.
all
()
for
match_id
,
match_number
,
fixture_id
,
match_datetime
,
total_bets
,
total_payin
in
bets_by_match
:
# Convert Decimal to float for calculations
total_payin
=
float
(
total_payin
)
if
total_payin
is
not
None
else
0.0
# Calculate winning/losing/pending bets from bet details
winning_bets
=
0
losing_bets
=
0
...
...
@@ -1441,7 +1493,7 @@ def api_reports_sync():
for
result
,
count
,
total_win
in
bet_details_query
.
all
():
if
result
==
'won'
:
winning_bets
=
count
total_payout
+=
total_win
or
0.0
total_payout
+=
float
(
total_win
)
if
total_win
is
not
None
else
0.0
elif
result
==
'lost'
:
losing_bets
=
count
elif
result
==
'pending'
:
...
...
@@ -1456,8 +1508,11 @@ def api_reports_sync():
)
.
first
()
if
existing_match_report
:
# Update existing MatchReport
# Update existing MatchReport
with all fields
existing_match_report
.
sync_id
=
report_sync
.
id
existing_match_report
.
match_number
=
match_number
existing_match_report
.
fixture_id
=
fixture_id
existing_match_report
.
match_datetime
=
match_datetime
existing_match_report
.
total_bets
=
total_bets
existing_match_report
.
winning_bets
=
winning_bets
existing_match_report
.
losing_bets
=
losing_bets
...
...
@@ -1466,6 +1521,10 @@ def api_reports_sync():
existing_match_report
.
total_payout
=
total_payout
existing_match_report
.
balance
=
balance
existing_match_report
.
cap_compensation_balance
=
cap_compensation_balance
existing_match_report
.
under_bets
=
0
existing_match_report
.
under_amount
=
0.0
existing_match_report
.
over_bets
=
0
existing_match_report
.
over_amount
=
0.0
logger
.
info
(
f
"Updated existing MatchReport for match {match_id}"
)
else
:
# Create new MatchReport
...
...
@@ -1484,6 +1543,8 @@ def api_reports_sync():
total_payin
=
total_payin
,
total_payout
=
total_payout
,
balance
=
balance
,
actual_result
=
None
,
extraction_result
=
None
,
cap_compensation_balance
=
cap_compensation_balance
)
db
.
session
.
add
(
match_report
)
...
...
app/database/migrations.py
View file @
cc4ddd48
...
...
@@ -1121,6 +1121,116 @@ class Migration_014_AddAccumulatedShortfallAndCapPercentage(Migration):
def
can_rollback
(
self
)
->
bool
:
return
True
class
Migration_015_AllowNullResults
(
Migration
):
"""Allow NULL values for actual_result and extraction_result columns in extraction_stats and match_reports tables for incomplete matches"""
def
__init__
(
self
):
super
()
.
__init__
(
"015"
,
"Allow NULL values for actual_result and extraction_result columns in extraction_stats and match_reports tables"
)
def
up
(
self
):
"""Allow NULL values for actual_result and extraction_result columns"""
try
:
inspector
=
inspect
(
db
.
engine
)
# Check extraction_stats columns
extraction_stats_columns
=
{
col
[
'name'
]:
col
[
'nullable'
]
for
col
in
inspector
.
get_columns
(
'extraction_stats'
)}
if
'actual_result'
in
extraction_stats_columns
:
if
extraction_stats_columns
[
'actual_result'
]
is
False
:
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
text
(
"""
ALTER TABLE extraction_stats
MODIFY COLUMN actual_result VARCHAR(50) NULL
"""
))
conn
.
commit
()
logger
.
info
(
"Updated actual_result column to allow NULL in extraction_stats table"
)
else
:
logger
.
info
(
"actual_result column already allows NULL in extraction_stats table"
)
if
'extraction_result'
in
extraction_stats_columns
:
if
extraction_stats_columns
[
'extraction_result'
]
is
False
:
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
text
(
"""
ALTER TABLE extraction_stats
MODIFY COLUMN extraction_result VARCHAR(50) NULL
"""
))
conn
.
commit
()
logger
.
info
(
"Updated extraction_result column to allow NULL in extraction_stats table"
)
else
:
logger
.
info
(
"extraction_result column already allows NULL in extraction_stats table"
)
# Check match_reports columns
match_reports_columns
=
{
col
[
'name'
]:
col
[
'nullable'
]
for
col
in
inspector
.
get_columns
(
'match_reports'
)}
if
'actual_result'
in
match_reports_columns
:
if
match_reports_columns
[
'actual_result'
]
is
False
:
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
text
(
"""
ALTER TABLE match_reports
MODIFY COLUMN actual_result VARCHAR(50) NULL
"""
))
conn
.
commit
()
logger
.
info
(
"Updated actual_result column to allow NULL in match_reports table"
)
else
:
logger
.
info
(
"actual_result column already allows NULL in match_reports table"
)
if
'extraction_result'
in
match_reports_columns
:
if
match_reports_columns
[
'extraction_result'
]
is
False
:
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
text
(
"""
ALTER TABLE match_reports
MODIFY COLUMN extraction_result VARCHAR(50) NULL
"""
))
conn
.
commit
()
logger
.
info
(
"Updated extraction_result column to allow NULL in match_reports table"
)
else
:
logger
.
info
(
"extraction_result column already allows NULL in match_reports table"
)
logger
.
info
(
"Migration 015 completed successfully"
)
return
True
except
Exception
as
e
:
logger
.
error
(
f
"Migration 015 failed: {str(e)}"
)
raise
def
down
(
self
):
"""Revert actual_result and extraction_result columns to NOT NULL"""
try
:
# Revert extraction_stats columns
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
text
(
"""
ALTER TABLE extraction_stats
MODIFY COLUMN actual_result VARCHAR(50) NOT NULL
"""
))
conn
.
execute
(
text
(
"""
ALTER TABLE extraction_stats
MODIFY COLUMN extraction_result VARCHAR(50) NOT NULL
"""
))
conn
.
commit
()
# Revert match_reports columns
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
text
(
"""
ALTER TABLE match_reports
MODIFY COLUMN actual_result VARCHAR(50) NOT NULL
"""
))
conn
.
execute
(
text
(
"""
ALTER TABLE match_reports
MODIFY COLUMN extraction_result VARCHAR(50) NOT NULL
"""
))
conn
.
commit
()
logger
.
info
(
"Migration 015 rolled back successfully"
)
return
True
except
Exception
as
e
:
logger
.
error
(
f
"Rollback of migration 015 failed: {str(e)}"
)
raise
def
can_rollback
(
self
)
->
bool
:
return
True
class
MigrationManager
:
"""Manages database migrations and versioning"""
...
...
@@ -1140,6 +1250,7 @@ class MigrationManager:
Migration_012_AddMatchNumberToBetsAndStats
(),
Migration_013_CreateMatchReportsTable
(),
Migration_014_AddAccumulatedShortfallAndCapPercentage
(),
Migration_015_AllowNullResults
(),
]
def
ensure_version_table
(
self
):
...
...
@@ -1314,3 +1425,5 @@ def run_migrations():
def
get_migration_status
():
"""Get migration status"""
return
migration_manager
.
get_migration_status
()
app/database/migrations/allow_null_results.py
0 → 100644
View file @
cc4ddd48
"""
Migration to allow NULL values for actual_result and extraction_result columns
in extraction_stats and match_reports tables for incomplete matches
"""
from
app
import
db
def
upgrade
():
"""Allow NULL values for actual_result and extraction_result columns"""
try
:
# Check if columns allow NULL in extraction_stats
inspector
=
db
.
inspect
(
db
.
engine
)
extraction_stats_columns
=
{
col
[
'name'
]:
col
[
'nullable'
]
for
col
in
inspector
.
get_columns
(
'extraction_stats'
)}
if
'actual_result'
in
extraction_stats_columns
:
if
extraction_stats_columns
[
'actual_result'
]
is
False
:
# Alter actual_result column to allow NULL
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE extraction_stats
MODIFY COLUMN actual_result VARCHAR(50) NULL
"""
))
db
.
session
.
commit
()
print
(
"✓ Updated actual_result column to allow NULL in extraction_stats table"
)
else
:
print
(
"✓ actual_result column already allows NULL in extraction_stats table"
)
if
'extraction_result'
in
extraction_stats_columns
:
if
extraction_stats_columns
[
'extraction_result'
]
is
False
:
# Alter extraction_result column to allow NULL
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE extraction_stats
MODIFY COLUMN extraction_result VARCHAR(50) NULL
"""
))
db
.
session
.
commit
()
print
(
"✓ Updated extraction_result column to allow NULL in extraction_stats table"
)
else
:
print
(
"✓ extraction_result column already allows NULL in extraction_stats table"
)
# Check if columns allow NULL in match_reports
match_reports_columns
=
{
col
[
'name'
]:
col
[
'nullable'
]
for
col
in
inspector
.
get_columns
(
'match_reports'
)}
if
'actual_result'
in
match_reports_columns
:
if
match_reports_columns
[
'actual_result'
]
is
False
:
# Alter actual_result column to allow NULL
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE match_reports
MODIFY COLUMN actual_result VARCHAR(50) NULL
"""
))
db
.
session
.
commit
()
print
(
"✓ Updated actual_result column to allow NULL in match_reports table"
)
else
:
print
(
"✓ actual_result column already allows NULL in match_reports table"
)
if
'extraction_result'
in
match_reports_columns
:
if
match_reports_columns
[
'extraction_result'
]
is
False
:
# Alter extraction_result column to allow NULL
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE match_reports
MODIFY COLUMN extraction_result VARCHAR(50) NULL
"""
))
db
.
session
.
commit
()
print
(
"✓ Updated extraction_result column to allow NULL in match_reports table"
)
else
:
print
(
"✓ extraction_result column already allows NULL in match_reports table"
)
print
(
"
\n
✓ Migration completed successfully!"
)
except
Exception
as
e
:
db
.
session
.
rollback
()
print
(
f
"✗ Error updating columns: {str(e)}"
)
raise
def
downgrade
():
"""Revert actual_result and extraction_result columns to NOT NULL"""
try
:
# Revert extraction_stats columns
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE extraction_stats
MODIFY COLUMN actual_result VARCHAR(50) NOT NULL
"""
))
db
.
session
.
commit
()
print
(
"✓ Reverted actual_result column to NOT NULL in extraction_stats table"
)
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE extraction_stats
MODIFY COLUMN extraction_result VARCHAR(50) NOT NULL
"""
))
db
.
session
.
commit
()
print
(
"✓ Reverted extraction_result column to NOT NULL in extraction_stats table"
)
# Revert match_reports columns
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE match_reports
MODIFY COLUMN actual_result VARCHAR(50) NOT NULL
"""
))
db
.
session
.
commit
()
print
(
"✓ Reverted actual_result column to NOT NULL in match_reports table"
)
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE match_reports
MODIFY COLUMN extraction_result VARCHAR(50) NOT NULL
"""
))
db
.
session
.
commit
()
print
(
"✓ Reverted extraction_result column to NOT NULL in match_reports table"
)
print
(
"
\n
✓ Downgrade completed successfully!"
)
except
Exception
as
e
:
db
.
session
.
rollback
()
print
(
f
"✗ Error reverting columns: {str(e)}"
)
raise
if
__name__
==
'__main__'
:
from
app
import
create_app
app
=
create_app
()
with
app
.
app_context
():
print
(
"Running migration: Allow NULL values for actual_result and extraction_result"
)
print
(
"="
*
70
)
upgrade
()
print
(
"="
*
70
)
app/main/routes.py
View file @
cc4ddd48
...
...
@@ -200,7 +200,7 @@ def matches():
@
login_required
@
require_active_user
def
fixture_detail
(
fixture_id
):
"""Fixture detail page showing all matches in
the
fixture"""
"""Fixture detail page showing all matches in fixture"""
try
:
from
app.models
import
Match
,
FileUpload
...
...
@@ -224,7 +224,7 @@ def fixture_detail(fixture_id):
'created_by'
:
matches
[
0
]
.
created_by
}
# Get associated uploads for
the
fixture
# Get associated uploads for fixture
match_ids
=
[
m
.
id
for
m
in
matches
]
uploads
=
FileUpload
.
query
.
filter
(
FileUpload
.
match_id
.
in_
(
match_ids
))
.
all
()
if
match_ids
else
[]
...
...
@@ -337,7 +337,7 @@ def update_match_outcomes(match_id):
try
:
from
app.models
import
Match
,
MatchOutcome
# Get
the
match
# Get match
if
current_user
.
is_admin
:
match
=
Match
.
query
.
get_or_404
(
match_id
)
else
:
...
...
@@ -353,7 +353,7 @@ def update_match_outcomes(match_id):
# Update or create outcomes
for
column_name
,
float_value
in
outcomes_data
.
items
():
try
:
# Validate
the
float value
# Validate float value
float_val
=
float
(
float_value
)
# Find existing outcome or create new one
...
...
@@ -406,13 +406,13 @@ def delete_match_outcome(match_id, outcome_id):
try
:
from
app.models
import
Match
,
MatchOutcome
# Get
the
match to verify ownership
# Get match to verify ownership
if
current_user
.
is_admin
:
match
=
Match
.
query
.
get_or_404
(
match_id
)
else
:
match
=
Match
.
query
.
filter_by
(
id
=
match_id
,
created_by
=
current_user
.
id
)
.
first_or_404
()
# Get
the
outcome
# Get outcome
outcome
=
MatchOutcome
.
query
.
filter_by
(
id
=
outcome_id
,
match_id
=
match_id
...
...
@@ -1407,7 +1407,7 @@ def download_zip(match_id):
flash
(
'ZIP file not found on disk'
,
'error'
)
abort
(
404
)
# Log
the
download
# Log download
logger
.
info
(
f
"ZIP file downloaded: {match.zip_filename} by user {current_user.username}"
)
return
send_file
(
zip_path
,
as_attachment
=
True
,
download_name
=
match
.
zip_filename
)
...
...
@@ -1654,7 +1654,7 @@ def reports():
if
export_format
:
return
export_reports
(
query
,
export_format
)
# Aggregate data by client for
the
selected period using MatchReport
# Aggregate data by client for selected period using MatchReport
# Build base query with filters
base_query
=
MatchReport
.
query
...
...
@@ -1717,7 +1717,7 @@ def reports():
client_aggregates
[
client_id
][
'losing_bets'
]
+=
report
.
losing_bets
client_aggregates
[
client_id
][
'pending_bets'
]
+=
report
.
pending_bets
# Use
the
most recent CAP balance and accumulated shortfall for this client
# Use most recent CAP balance and accumulated shortfall for this client
if
report
.
match_datetime
>=
client_aggregates
[
client_id
][
'last_match_timestamp'
]:
client_aggregates
[
client_id
][
'cap_balance'
]
=
float
(
report
.
cap_compensation_balance
)
if
report
.
cap_compensation_balance
else
0.0
client_aggregates
[
client_id
][
'accumulated_shortfall'
]
=
float
(
report
.
accumulated_shortfall
)
if
report
.
accumulated_shortfall
else
0.0
...
...
@@ -1760,7 +1760,6 @@ def reports():
total_balance
=
total_payin
-
total_payout
cap_balance
=
clients_list
[
0
][
'cap_balance'
]
if
clients_list
else
0.0
accumulated_shortfall
=
clients_list
[
0
][
'accumulated_shortfall'
]
if
clients_list
else
0.0
accumulated_shortfall
=
clients_list
[
0
][
'accumulated_shortfall'
]
if
clients_list
else
0.0
# Pagination
total_clients
=
len
(
clients_list
)
...
...
@@ -2129,7 +2128,7 @@ def sync_logs():
if
end_date_filter
:
try
:
end_date
=
datetime
.
strptime
(
end_date_filter
,
'
%
Y-
%
m-
%
d'
)
# Include
the
entire end date
# Include entire end date
end_date
=
end_date
.
replace
(
hour
=
23
,
minute
=
59
,
second
=
59
)
query
=
query
.
filter
(
ReportSyncLog
.
created_at
<=
end_date
)
except
ValueError
:
...
...
@@ -2439,6 +2438,7 @@ def export_sync_logs(export_format):
except
Exception
as
e
:
logger
.
error
(
f
"Export sync logs error: {str(e)}"
)
flash
(
'Error exporting sync logs'
,
'error'
)
return
redirect
(
url_for
(
'main.sync_logs'
))
@
csrf
.
exempt
@
bp
.
route
(
'/client-report/<client_id>'
)
...
...
@@ -2534,23 +2534,34 @@ def client_report_detail(client_id):
if
end_date
:
query
=
query
.
filter
(
MatchReport
.
match_datetime
<=
end_date
)
# Get all matching match reports for this client with pagination
# Get all matching match reports for this client (without pagination for totals)
all_match_reports
=
query
.
all
()
# Get paginated match reports for display
match_reports_pagination
=
query
.
order_by
(
MatchReport
.
match_datetime
.
desc
())
.
paginate
(
page
=
page
,
per_page
=
per_page
,
error_out
=
False
)
match_reports
=
match_reports_pagination
.
items
# Calculate totals from
match reports
total_payin
=
sum
(
float
(
r
.
total_payin
)
for
r
in
match_reports
if
r
.
total_payin
)
total_payout
=
sum
(
float
(
r
.
total_payout
)
for
r
in
match_reports
if
r
.
total_payout
)
# Calculate totals from
ALL match reports (not just paginated)
total_payin
=
sum
(
float
(
r
.
total_payin
)
for
r
in
all_
match_reports
if
r
.
total_payin
)
total_payout
=
sum
(
float
(
r
.
total_payout
)
for
r
in
all_
match_reports
if
r
.
total_payout
)
total_balance
=
total_payin
-
total_payout
total_bets
=
sum
(
r
.
total_bets
for
r
in
match_reports
)
total_matches
=
len
(
match_reports
)
winning_bets
=
sum
(
r
.
winning_bets
for
r
in
match_reports
)
losing_bets
=
sum
(
r
.
losing_bets
for
r
in
match_reports
)
pending_bets
=
sum
(
r
.
pending_bets
for
r
in
match_reports
)
cap_balance
=
float
(
match_reports
[
0
]
.
cap_compensation_balance
)
if
match_reports
and
match_reports
[
0
]
.
cap_compensation_balance
else
0.0
accumulated_shortfall
=
float
(
match_reports
[
0
]
.
accumulated_shortfall
)
if
match_reports
and
match_reports
[
0
]
.
accumulated_shortfall
else
0.0
total_bets
=
sum
(
r
.
total_bets
for
r
in
all_match_reports
)
total_matches
=
len
(
all_match_reports
)
winning_bets
=
sum
(
r
.
winning_bets
for
r
in
all_match_reports
)
losing_bets
=
sum
(
r
.
losing_bets
for
r
in
all_match_reports
)
pending_bets
=
sum
(
r
.
pending_bets
for
r
in
all_match_reports
)
# Use most recent CAP balance and accumulated shortfall from all reports
if
all_match_reports
:
# Sort by match_datetime descending to get most recent
sorted_reports
=
sorted
(
all_match_reports
,
key
=
lambda
x
:
x
.
match_datetime
or
datetime
.
min
,
reverse
=
True
)
cap_balance
=
float
(
sorted_reports
[
0
]
.
cap_compensation_balance
)
if
sorted_reports
[
0
]
.
cap_compensation_balance
else
0.0
accumulated_shortfall
=
float
(
sorted_reports
[
0
]
.
accumulated_shortfall
)
if
sorted_reports
[
0
]
.
accumulated_shortfall
else
0.0
else
:
cap_balance
=
0.0
accumulated_shortfall
=
0.0
# Get client token name
client_activity
=
ClientActivity
.
query
.
filter_by
(
rustdesk_id
=
client_id
)
.
first
()
...
...
@@ -2593,6 +2604,7 @@ def client_report_detail(client_id):
end_date
=
end_date_filter
if
'end_date_filter'
in
locals
()
else
''
,
start_time
=
start_time_filter
if
'start_time_filter'
in
locals
()
else
''
,
end_time
=
end_time_filter
if
'end_time_filter'
in
locals
()
else
''
))
@
csrf
.
exempt
@
bp
.
route
(
'/client-report/<client_id>/match/<int:match_id>'
)
@
login_required
...
...
@@ -2681,13 +2693,7 @@ def match_report_detail(client_id, match_id):
flash
(
'Access denied to this client'
,
'error'
)
return
redirect
(
url_for
(
'main.reports'
))
# Apply date filters
if
start_date
:
query
=
query
.
filter
(
MatchReport
.
match_datetime
>=
start_date
)
if
end_date
:
query
=
query
.
filter
(
MatchReport
.
match_datetime
<=
end_date
)
# Get the match report
# Get match report (don't apply date filters - we want specific match)
match_report
=
query
.
first
()
if
not
match_report
:
...
...
@@ -2731,6 +2737,8 @@ def match_report_detail(client_id, match_id):
except
Exception
as
e
:
logger
.
error
(
f
"Match report detail error: {str(e)}"
)
flash
(
'Error loading match report details'
,
'error'
)
return
redirect
(
url_for
(
'main.match_report_detail'
,
client_id
=
client_id
,
match_id
=
match_id
))
@
csrf
.
exempt
@
bp
.
route
(
'/client-report/<client_id>/match/<int:match_id>/bet/<bet_uuid>'
)
@
login_required
...
...
@@ -2764,7 +2772,7 @@ def bet_detail(client_id, match_id, bet_uuid):
flash
(
'Access denied to this client'
,
'error'
)
return
redirect
(
url_for
(
'main.reports'
))
# Get
the
bet
# Get bet
bet
=
Bet
.
query
.
filter_by
(
client_id
=
client_id
,
match_id
=
match_id
,
uuid
=
bet_uuid
)
.
first
()
if
not
bet
:
...
...
app/models.py
View file @
cc4ddd48
...
...
@@ -980,8 +980,8 @@ class ExtractionStats(db.Model):
total_bets
=
db
.
Column
(
db
.
Integer
,
nullable
=
False
)
total_amount_collected
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
nullable
=
False
)
total_redistributed
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
nullable
=
False
)
actual_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Fals
e
)
extraction_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Fals
e
)
actual_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Tru
e
)
extraction_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Tru
e
)
cap_applied
=
db
.
Column
(
db
.
Boolean
,
default
=
False
)
cap_percentage
=
db
.
Column
(
db
.
Numeric
(
5
,
2
))
accumulated_shortfall
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
...
...
@@ -1126,8 +1126,8 @@ class MatchReport(db.Model):
balance
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
# Match result
actual_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Fals
e
)
extraction_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Fals
e
)
actual_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Tru
e
)
extraction_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Tru
e
)
# CAP information
cap_applied
=
db
.
Column
(
db
.
Boolean
,
default
=
False
)
...
...
app/templates/main/match_detail.html
View file @
cc4ddd48
...
...
@@ -38,7 +38,7 @@
background-color
:
rgba
(
255
,
255
,
255
,
0.1
);
}
.container
{
max-width
:
1
200px
;
max-width
:
1
00%
;
margin
:
2rem
auto
;
padding
:
0
2rem
;
}
...
...
@@ -47,6 +47,7 @@
padding
:
2rem
;
border-radius
:
8px
;
box-shadow
:
0
2px
10px
rgba
(
0
,
0
,
0
,
0.1
);
max-width
:
100%
;
}
.btn
{
padding
:
8px
16px
;
...
...
test_matchreport_update.py
0 → 100644
View file @
cc4ddd48
"""
Test script to verify that incremental reports sync updates existing MatchReport records
"""
import
sys
import
os
sys
.
path
.
insert
(
0
,
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
)))
from
datetime
import
datetime
,
timedelta
import
uuid
from
app
import
create_app
,
db
from
app.models
import
ReportSync
,
Bet
,
BetDetail
,
ExtractionStats
,
MatchReport
,
ClientActivity
,
APIToken
,
User
def
create_test_data
():
"""Create test data to verify the fix"""
app
=
create_app
()
app
.
config
[
'SQLALCHEMY_DATABASE_URI'
]
=
'sqlite:///:memory:'
app
.
config
[
'SQLALCHEMY_TRACK_MODIFICATIONS'
]
=
False
app
.
config
[
'TESTING'
]
=
True
with
app
.
app_context
():
db
.
create_all
()
# Create test user
user
=
User
(
username
=
'testuser'
,
email
=
'test@example.com'
,
is_active
=
True
,
is_admin
=
True
)
user
.
set_password
(
'testpassword'
)
db
.
session
.
add
(
user
)
db
.
session
.
commit
()
# Create test API token
api_token
=
user
.
generate_api_token
(
'Test Token'
)
db
.
session
.
commit
()
# Create test client activity
client_activity
=
ClientActivity
(
api_token_id
=
api_token
.
id
,
rustdesk_id
=
'test_client_001'
,
last_seen
=
datetime
.
utcnow
()
)
db
.
session
.
add
(
client_activity
)
db
.
session
.
commit
()
# Create first sync (full sync)
sync1_data
=
{
'sync_id'
:
f
'sync_test_1_{uuid.uuid4()}'
,
'client_id'
:
'test_client_001'
,
'sync_timestamp'
:
datetime
.
utcnow
()
.
isoformat
(),
'date_range'
:
'all'
,
'start_date'
:
(
datetime
.
utcnow
()
-
timedelta
(
days
=
7
))
.
isoformat
(),
'end_date'
:
datetime
.
utcnow
()
.
isoformat
(),
'bets'
:
[
{
'uuid'
:
str
(
uuid
.
uuid4
()),
'fixture_id'
:
'fixture_test_001'
,
'bet_datetime'
:
(
datetime
.
utcnow
()
-
timedelta
(
hours
=
2
))
.
isoformat
(),
'paid'
:
False
,
'paid_out'
:
False
,
'total_amount'
:
500.00
,
'bet_count'
:
1
,
'details'
:
[
{
'match_id'
:
123
,
'match_number'
:
1
,
'outcome'
:
'WIN1'
,
'amount'
:
500.00
,
'win_amount'
:
0.00
,
'result'
:
'pending'
}
]
}
],
'extraction_stats'
:
[
{
'match_id'
:
123
,
'fixture_id'
:
'fixture_test_001'
,
'match_datetime'
:
(
datetime
.
utcnow
()
-
timedelta
(
hours
=
1
))
.
isoformat
(),
'total_bets'
:
10
,
'total_amount_collected'
:
2000.00
,
'total_redistributed'
:
1500.00
,
'actual_result'
:
'WIN1'
,
'extraction_result'
:
'WIN1'
,
'cap_applied'
:
False
,
'under_bets'
:
5
,
'under_amount'
:
1000.00
,
'over_bets'
:
5
,
'over_amount'
:
1000.00
,
'result_breakdown'
:
{
'WIN1'
:
{
'bets'
:
3
,
'amount'
:
600.00
},
'X1'
:
{
'bets'
:
2
,
'amount'
:
400.00
},
'WIN2'
:
{
'bets'
:
5
,
'amount'
:
1000.00
}
}
}
],
'cap_compensation_balance'
:
0.00
,
'summary'
:
{
'total_payin'
:
2000.00
,
'total_payout'
:
1500.00
,
'net_profit'
:
500.00
,
'total_bets'
:
10
,
'total_matches'
:
1
}
}
return
app
,
user
,
api_token
,
client_activity
,
sync1_data
def
test_matchreport_update
():
"""Test that existing MatchReport records are updated in incremental sync"""
print
(
"="
*
80
)
print
(
"Testing MatchReport Update in Incremental Sync"
)
print
(
"="
*
80
)
app
,
user
,
api_token
,
client_activity
,
sync1_data
=
create_test_data
()
with
app
.
app_context
():
# Process first sync (full sync)
print
(
"
\n
1. Processing first sync (full sync)..."
)
# Create report sync record
sync1
=
ReportSync
(
sync_id
=
sync1_data
[
'sync_id'
],
client_id
=
sync1_data
[
'client_id'
],
sync_timestamp
=
datetime
.
fromisoformat
(
sync1_data
[
'sync_timestamp'
]),
date_range
=
sync1_data
[
'date_range'
],
start_date
=
datetime
.
fromisoformat
(
sync1_data
[
'start_date'
]),
end_date
=
datetime
.
fromisoformat
(
sync1_data
[
'end_date'
]),
total_payin
=
sync1_data
[
'summary'
][
'total_payin'
],
total_payout
=
sync1_data
[
'summary'
][
'total_payout'
],
net_profit
=
sync1_data
[
'summary'
][
'net_profit'
],
total_bets
=
sync1_data
[
'summary'
][
'total_bets'
],
total_matches
=
sync1_data
[
'summary'
][
'total_matches'
],
cap_compensation_balance
=
sync1_data
[
'cap_compensation_balance'
]
)
db
.
session
.
add
(
sync1
)
db
.
session
.
commit
()
# Process bets
for
bet_data
in
sync1_data
[
'bets'
]:
bet
=
Bet
(
uuid
=
bet_data
[
'uuid'
],
sync_id
=
sync1
.
id
,
client_id
=
sync1_data
[
'client_id'
],
fixture_id
=
bet_data
[
'fixture_id'
],
match_id
=
bet_data
[
'details'
][
0
][
'match_id'
],
match_number
=
bet_data
[
'details'
][
0
][
'match_number'
],
bet_datetime
=
datetime
.
fromisoformat
(
bet_data
[
'bet_datetime'
]),
paid
=
bet_data
[
'paid'
],
paid_out
=
bet_data
[
'paid_out'
],
total_amount
=
bet_data
[
'total_amount'
],
bet_count
=
bet_data
[
'bet_count'
]
)
db
.
session
.
add
(
bet
)
db
.
session
.
flush
()
for
detail_data
in
bet_data
[
'details'
]:
bet_detail
=
BetDetail
(
bet_id
=
bet
.
id
,
match_id
=
detail_data
[
'match_id'
],
match_number
=
detail_data
[
'match_number'
],
outcome
=
detail_data
[
'outcome'
],
amount
=
detail_data
[
'amount'
],
win_amount
=
detail_data
[
'win_amount'
],
result
=
detail_data
[
'result'
]
)
db
.
session
.
add
(
bet_detail
)
# Process extraction stats and create MatchReport
for
stats_data
in
sync1_data
[
'extraction_stats'
]:
# Create extraction stats
extraction_stats
=
ExtractionStats
(
sync_id
=
sync1
.
id
,
client_id
=
sync1_data
[
'client_id'
],
match_id
=
stats_data
[
'match_id'
],
match_number
=
sync1_data
[
'bets'
][
0
][
'details'
][
0
][
'match_number'
],
fixture_id
=
stats_data
[
'fixture_id'
],
match_datetime
=
datetime
.
fromisoformat
(
stats_data
[
'match_datetime'
]),
total_bets
=
stats_data
[
'total_bets'
],
total_amount_collected
=
stats_data
[
'total_amount_collected'
],
total_redistributed
=
stats_data
[
'total_redistributed'
],
actual_result
=
stats_data
[
'actual_result'
],
extraction_result
=
stats_data
[
'extraction_result'
],
cap_applied
=
stats_data
[
'cap_applied'
],
under_bets
=
stats_data
[
'under_bets'
],
under_amount
=
stats_data
[
'under_amount'
],
over_bets
=
stats_data
[
'over_bets'
],
over_amount
=
stats_data
[
'over_amount'
],
result_breakdown
=
stats_data
[
'result_breakdown'
]
)
db
.
session
.
add
(
extraction_stats
)
# Create MatchReport (this is what we fixed)
match_report
=
MatchReport
(
sync_id
=
sync1
.
id
,
client_id
=
sync1_data
[
'client_id'
],
client_token_name
=
api_token
.
name
,
match_id
=
stats_data
[
'match_id'
],
match_number
=
sync1_data
[
'bets'
][
0
][
'details'
][
0
][
'match_number'
],
fixture_id
=
stats_data
[
'fixture_id'
],
match_datetime
=
datetime
.
fromisoformat
(
stats_data
[
'match_datetime'
]),
total_bets
=
stats_data
[
'total_bets'
],
winning_bets
=
3
,
losing_bets
=
5
,
pending_bets
=
2
,
total_payin
=
stats_data
[
'total_amount_collected'
],
total_payout
=
stats_data
[
'total_redistributed'
],
balance
=
stats_data
[
'total_amount_collected'
]
-
stats_data
[
'total_redistributed'
],
actual_result
=
stats_data
[
'actual_result'
],
extraction_result
=
stats_data
[
'extraction_result'
],
cap_applied
=
stats_data
[
'cap_applied'
],
cap_compensation_balance
=
sync1_data
[
'cap_compensation_balance'
],
under_bets
=
stats_data
[
'under_bets'
],
under_amount
=
stats_data
[
'under_amount'
],
over_bets
=
stats_data
[
'over_bets'
],
over_amount
=
stats_data
[
'over_amount'
],
result_breakdown
=
stats_data
[
'result_breakdown'
]
)
db
.
session
.
add
(
match_report
)
db
.
session
.
commit
()
# Verify MatchReport was created
match_report_count
=
MatchReport
.
query
.
count
()
print
(
f
" MatchReport count after first sync: {match_report_count}"
)
assert
match_report_count
==
1
,
"Should have created 1 MatchReport"
original_match_report
=
MatchReport
.
query
.
first
()
print
(
f
" Original MatchReport sync_id: {original_match_report.sync_id}"
)
print
(
f
" Original MatchReport total_bets: {original_match_report.total_bets}"
)
print
(
f
" Original MatchReport winning_bets: {original_match_report.winning_bets}"
)
# Create incremental sync with updated data
print
(
"
\n
2. Creating incremental sync with updated data..."
)
sync2_data
=
sync1_data
.
copy
()
sync2_data
[
'sync_id'
]
=
f
'sync_test_2_{uuid.uuid4()}'
sync2_data
[
'sync_timestamp'
]
=
(
datetime
.
utcnow
()
+
timedelta
(
minutes
=
10
))
.
isoformat
()
sync2_data
[
'extraction_stats'
][
0
][
'total_bets'
]
=
15
# Updated value
sync2_data
[
'extraction_stats'
][
0
][
'total_amount_collected'
]
=
2500.00
# Updated value
sync2_data
[
'extraction_stats'
][
0
][
'total_redistributed'
]
=
1800.00
# Updated value
sync2_data
[
'summary'
][
'total_bets'
]
=
15
sync2_data
[
'summary'
][
'total_payin'
]
=
2500.00
sync2_data
[
'summary'
][
'total_payout'
]
=
1800.00
sync2_data
[
'summary'
][
'net_profit'
]
=
700.00
# Now simulate processing the incremental sync through the API endpoint
from
app.api.routes
import
api_reports_sync
from
flask
import
Flask
,
request
,
jsonify
# Create a test client
test_client
=
app
.
test_client
()
# Set up headers with authentication
headers
=
{
'Content-Type'
:
'application/json'
,
'Authorization'
:
f
'Bearer {api_token.plain_token}'
}
# Send the sync request
response
=
test_client
.
post
(
'/api/reports/sync'
,
json
=
sync2_data
,
headers
=
headers
)
print
(
f
" API Response status code: {response.status_code}"
)
assert
response
.
status_code
==
200
,
"Sync should succeed"
# Verify the response
response_json
=
response
.
get_json
()
print
(
f
" Response success: {response_json['success']}"
)
assert
response_json
[
'success'
]
==
True
,
"Sync should be successful"
print
(
f
" Synced count: {response_json['synced_count']}"
)
# Check MatchReport was updated, not duplicated
print
(
"
\n
3. Verifying MatchReport was updated..."
)
match_report_count
=
MatchReport
.
query
.
count
()
print
(
f
" MatchReport count after incremental sync: {match_report_count}"
)
assert
match_report_count
==
1
,
"Should not create duplicate MatchReport"
updated_match_report
=
MatchReport
.
query
.
first
()
print
(
f
" Updated MatchReport sync_id: {updated_match_report.sync_id}"
)
print
(
f
" Updated MatchReport total_bets: {updated_match_report.total_bets}"
)
assert
updated_match_report
.
total_bets
==
15
,
"Should update total_bets"
print
(
f
" Updated MatchReport total_payin: {updated_match_report.total_payin}"
)
assert
updated_match_report
.
total_payin
==
2500.00
,
"Should update total_payin"
print
(
f
" Updated MatchReport total_payout: {updated_match_report.total_payout}"
)
assert
updated_match_report
.
total_payout
==
1800.00
,
"Should update total_payout"
print
(
"
\n
✓ SUCCESS: MatchReport updated correctly in incremental sync!"
)
# Cleanup
db
.
session
.
remove
()
db
.
drop_all
()
if
__name__
==
"__main__"
:
test_matchreport_update
()
\ No newline at end of file
test_simple_matchreport_update.py
0 → 100644
View file @
cc4ddd48
"""
Simple test to verify MatchReport update logic without database connection
This tests the core logic directly without requiring a running MySQL server
"""
from
datetime
import
datetime
,
timedelta
import
uuid
import
sys
import
os
# Add the project root to Python path
sys
.
path
.
insert
(
0
,
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
)))
def
test_matchreport_logic
():
"""Test MatchReport update logic"""
print
(
"="
*
80
)
print
(
"Testing MatchReport Update Logic"
)
print
(
"="
*
80
)
# Simulate what happens in a real sync
print
(
"
\n
1. Simulating first sync (full sync)..."
)
# First, let's simulate existing match reports before incremental sync
existing_match_reports
=
[
{
'id'
:
1
,
'client_id'
:
'test_client_001'
,
'match_id'
:
123
,
'sync_id'
:
'sync_1'
,
'total_bets'
:
10
,
'total_payin'
:
2000.00
,
'total_payout'
:
1500.00
,
'winning_bets'
:
3
,
'losing_bets'
:
5
,
'pending_bets'
:
2
,
'cap_compensation_balance'
:
0.00
}
]
# Print existing state
print
(
f
" Existing MatchReport count: {len(existing_match_reports)}"
)
for
mr
in
existing_match_reports
:
print
(
f
" - Match {mr['match_id']}: {mr['total_bets']} bets, "
f
"{mr['total_payin']:.2f} payin, {mr['total_payout']:.2f} payout"
)
# New sync data (incremental) with updated information
print
(
"
\n
2. Processing incremental sync with updated data..."
)
sync_data
=
{
'sync_id'
:
f
'sync_2_{uuid.uuid4()}'
,
'client_id'
:
'test_client_001'
,
'sync_timestamp'
:
(
datetime
.
utcnow
()
+
timedelta
(
minutes
=
10
))
.
isoformat
(),
'date_range'
:
'all'
,
'start_date'
:
(
datetime
.
utcnow
()
-
timedelta
(
days
=
7
))
.
isoformat
(),
'end_date'
:
datetime
.
utcnow
()
.
isoformat
(),
'bets'
:
[
{
'uuid'
:
str
(
uuid
.
uuid4
()),
'fixture_id'
:
'fixture_test_001'
,
'bet_datetime'
:
(
datetime
.
utcnow
()
-
timedelta
(
hours
=
2
))
.
isoformat
(),
'paid'
:
False
,
'paid_out'
:
False
,
'total_amount'
:
600.00
,
'bet_count'
:
1
,
'details'
:
[
{
'match_id'
:
123
,
'match_number'
:
1
,
'outcome'
:
'WIN1'
,
'amount'
:
600.00
,
'win_amount'
:
0.00
,
'result'
:
'pending'
}
]
}
],
'extraction_stats'
:
[
{
'match_id'
:
123
,
'fixture_id'
:
'fixture_test_001'
,
'match_datetime'
:
(
datetime
.
utcnow
()
-
timedelta
(
hours
=
1
))
.
isoformat
(),
'total_bets'
:
15
,
# Updated
'total_amount_collected'
:
2500.00
,
# Updated
'total_redistributed'
:
1800.00
,
# Updated
'actual_result'
:
'WIN1'
,
'extraction_result'
:
'WIN1'
,
'cap_applied'
:
False
,
'under_bets'
:
7
,
'under_amount'
:
1200.00
,
'over_bets'
:
8
,
'over_amount'
:
1300.00
,
'result_breakdown'
:
{
'WIN1'
:
{
'bets'
:
4
,
'amount'
:
800.00
},
'X1'
:
{
'bets'
:
3
,
'amount'
:
600.00
},
'WIN2'
:
{
'bets'
:
8
,
'amount'
:
1100.00
}
}
}
],
'cap_compensation_balance'
:
100.00
,
'summary'
:
{
'total_payin'
:
2500.00
,
'total_payout'
:
1800.00
,
'net_profit'
:
700.00
,
'total_bets'
:
15
,
'total_matches'
:
1
}
}
# Let's apply our fix logic
print
(
"
\n
3. Applying the fix..."
)
updated_match_reports
=
[]
for
stats_data
in
sync_data
[
'extraction_stats'
]:
match_id
=
stats_data
[
'match_id'
]
client_id
=
sync_data
[
'client_id'
]
# Check if MatchReport exists for this client and match
existing
=
None
for
mr
in
existing_match_reports
:
if
mr
[
'client_id'
]
==
client_id
and
mr
[
'match_id'
]
==
match_id
:
existing
=
mr
break
if
existing
:
print
(
f
" Existing MatchReport found for match {match_id}"
)
print
(
f
" Updating from {existing['sync_id']} to {sync_data['sync_id']}"
)
existing
[
'sync_id'
]
=
sync_data
[
'sync_id'
]
existing
[
'total_bets'
]
=
stats_data
[
'total_bets'
]
existing
[
'total_payin'
]
=
stats_data
[
'total_amount_collected'
]
existing
[
'total_payout'
]
=
stats_data
[
'total_redistributed'
]
existing
[
'cap_compensation_balance'
]
=
sync_data
[
'cap_compensation_balance'
]
# Recalculate balance
existing
[
'balance'
]
=
existing
[
'total_payin'
]
-
existing
[
'total_payout'
]
print
(
f
" Updated: {existing['total_bets']} bets, "
f
"{existing['total_payin']:.2f} payin, {existing['total_payout']:.2f} payout, "
f
"balance: {existing['balance']:.2f}"
)
updated_match_reports
.
append
(
existing
)
else
:
print
(
f
" Creating new MatchReport for match {match_id}"
)
new_mr
=
{
'id'
:
len
(
existing_match_reports
)
+
1
,
'client_id'
:
client_id
,
'match_id'
:
match_id
,
'sync_id'
:
sync_data
[
'sync_id'
],
'total_bets'
:
stats_data
[
'total_bets'
],
'total_payin'
:
stats_data
[
'total_amount_collected'
],
'total_payout'
:
stats_data
[
'total_redistributed'
],
'cap_compensation_balance'
:
sync_data
[
'cap_compensation_balance'
],
'balance'
:
stats_data
[
'total_amount_collected'
]
-
stats_data
[
'total_redistributed'
]
}
updated_match_reports
.
append
(
new_mr
)
# Verify the results
print
(
"
\n
4. Verifying results..."
)
print
(
f
" MatchReport count: {len(updated_match_reports)}"
)
assert
len
(
updated_match_reports
)
==
1
,
"Should not create duplicate"
updated_report
=
updated_match_reports
[
0
]
assert
updated_report
[
'sync_id'
]
==
sync_data
[
'sync_id'
],
"Sync ID should be updated"
assert
updated_report
[
'total_bets'
]
==
15
,
"Total bets should be updated"
assert
updated_report
[
'total_payin'
]
==
2500.00
,
"Total payin should be updated"
assert
updated_report
[
'total_payout'
]
==
1800.00
,
"Total payout should be updated"
assert
updated_report
[
'cap_compensation_balance'
]
==
100.00
,
"Cap compensation balance should be updated"
assert
updated_report
[
'balance'
]
==
700.00
,
"Balance should be recalculated"
print
(
"
\n
✓ SUCCESS: MatchReport logic works correctly!"
)
print
(
f
" - No duplicate MatchReport created"
)
print
(
f
" - Existing record updated with new values"
)
print
(
f
" - All fields properly modified"
)
if
__name__
==
"__main__"
:
test_matchreport_logic
()
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment