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
Expand all
Hide 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
This diff is collapsed.
Click to expand it.
app/database/migrations.py
View file @
cc4ddd48
...
@@ -1121,6 +1121,116 @@ class Migration_014_AddAccumulatedShortfallAndCapPercentage(Migration):
...
@@ -1121,6 +1121,116 @@ class Migration_014_AddAccumulatedShortfallAndCapPercentage(Migration):
def
can_rollback
(
self
)
->
bool
:
def
can_rollback
(
self
)
->
bool
:
return
True
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
:
class
MigrationManager
:
"""Manages database migrations and versioning"""
"""Manages database migrations and versioning"""
...
@@ -1140,6 +1250,7 @@ class MigrationManager:
...
@@ -1140,6 +1250,7 @@ class MigrationManager:
Migration_012_AddMatchNumberToBetsAndStats
(),
Migration_012_AddMatchNumberToBetsAndStats
(),
Migration_013_CreateMatchReportsTable
(),
Migration_013_CreateMatchReportsTable
(),
Migration_014_AddAccumulatedShortfallAndCapPercentage
(),
Migration_014_AddAccumulatedShortfallAndCapPercentage
(),
Migration_015_AllowNullResults
(),
]
]
def
ensure_version_table
(
self
):
def
ensure_version_table
(
self
):
...
@@ -1313,4 +1424,6 @@ def run_migrations():
...
@@ -1313,4 +1424,6 @@ def run_migrations():
def
get_migration_status
():
def
get_migration_status
():
"""Get migration status"""
"""Get migration status"""
return
migration_manager
.
get_migration_status
()
return
migration_manager
.
get_migration_status
()
\ No newline at end of file
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
This diff is collapsed.
Click to expand it.
app/models.py
View file @
cc4ddd48
...
@@ -980,8 +980,8 @@ class ExtractionStats(db.Model):
...
@@ -980,8 +980,8 @@ class ExtractionStats(db.Model):
total_bets
=
db
.
Column
(
db
.
Integer
,
nullable
=
False
)
total_bets
=
db
.
Column
(
db
.
Integer
,
nullable
=
False
)
total_amount_collected
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
nullable
=
False
)
total_amount_collected
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
nullable
=
False
)
total_redistributed
=
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
)
actual_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Tru
e
)
extraction_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Fals
e
)
extraction_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Tru
e
)
cap_applied
=
db
.
Column
(
db
.
Boolean
,
default
=
False
)
cap_applied
=
db
.
Column
(
db
.
Boolean
,
default
=
False
)
cap_percentage
=
db
.
Column
(
db
.
Numeric
(
5
,
2
))
cap_percentage
=
db
.
Column
(
db
.
Numeric
(
5
,
2
))
accumulated_shortfall
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
accumulated_shortfall
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
...
@@ -1126,8 +1126,8 @@ class MatchReport(db.Model):
...
@@ -1126,8 +1126,8 @@ class MatchReport(db.Model):
balance
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
balance
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
# Match result
# Match result
actual_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
=
Fals
e
)
extraction_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
Tru
e
)
# CAP information
# CAP information
cap_applied
=
db
.
Column
(
db
.
Boolean
,
default
=
False
)
cap_applied
=
db
.
Column
(
db
.
Boolean
,
default
=
False
)
...
...
app/templates/main/match_detail.html
View file @
cc4ddd48
...
@@ -38,7 +38,7 @@
...
@@ -38,7 +38,7 @@
background-color
:
rgba
(
255
,
255
,
255
,
0.1
);
background-color
:
rgba
(
255
,
255
,
255
,
0.1
);
}
}
.container
{
.container
{
max-width
:
1
200px
;
max-width
:
1
00%
;
margin
:
2rem
auto
;
margin
:
2rem
auto
;
padding
:
0
2rem
;
padding
:
0
2rem
;
}
}
...
@@ -47,6 +47,7 @@
...
@@ -47,6 +47,7 @@
padding
:
2rem
;
padding
:
2rem
;
border-radius
:
8px
;
border-radius
:
8px
;
box-shadow
:
0
2px
10px
rgba
(
0
,
0
,
0
,
0.1
);
box-shadow
:
0
2px
10px
rgba
(
0
,
0
,
0
,
0.1
);
max-width
:
100%
;
}
}
.btn
{
.btn
{
padding
:
8px
16px
;
padding
:
8px
16px
;
...
...
test_matchreport_update.py
0 → 100644
View file @
cc4ddd48
This diff is collapsed.
Click to expand it.
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