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
361fe6c0
Commit
361fe6c0
authored
Feb 02, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor reports
parent
ccb5153f
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
840 additions
and
391 deletions
+840
-391
REPORTS_FINAL_IMPLEMENTATION.md
REPORTS_FINAL_IMPLEMENTATION.md
+395
-0
routes.py
app/api/routes.py
+67
-1
add_match_reports_table.py
app/database/migrations/add_match_reports_table.py
+27
-0
routes.py
app/main/routes.py
+58
-47
models.py
app/models.py
+85
-1
client_report_detail.html
app/templates/main/client_report_detail.html
+145
-243
reports.html
app/templates/main/reports.html
+63
-99
No files found.
REPORTS_FINAL_IMPLEMENTATION.md
0 → 100644
View file @
361fe6c0
This diff is collapsed.
Click to expand it.
app/api/routes.py
View file @
361fe6c0
...
@@ -925,7 +925,7 @@ def api_reports_sync():
...
@@ -925,7 +925,7 @@ def api_reports_sync():
start_time
=
time
.
time
()
start_time
=
time
.
time
()
try
:
try
:
from
app.models
import
ReportSync
,
Bet
,
BetDetail
,
ExtractionStats
,
APIToken
,
ReportSyncLog
from
app.models
import
ReportSync
,
Bet
,
BetDetail
,
ExtractionStats
,
APIToken
,
ReportSyncLog
,
MatchReport
,
ClientActivity
from
app.auth.jwt_utils
import
validate_api_token
,
extract_token_from_request
from
app.auth.jwt_utils
import
validate_api_token
,
extract_token_from_request
from
datetime
import
datetime
from
datetime
import
datetime
import
uuid
as
uuid_lib
import
uuid
as
uuid_lib
...
@@ -1266,6 +1266,72 @@ def api_reports_sync():
...
@@ -1266,6 +1266,72 @@ def api_reports_sync():
stats_count
+=
1
stats_count
+=
1
stats_new
+=
1
stats_new
+=
1
# Create MatchReport records for comprehensive match-level data
match_reports_count
=
0
for
stats_data
in
data
[
'extraction_stats'
]:
# Get client token name
client_activity
=
ClientActivity
.
query
.
filter_by
(
rustdesk_id
=
data
[
'client_id'
])
.
first
()
client_token_name
=
client_activity
.
api_token
.
name
if
client_activity
and
client_activity
.
api_token
else
'Unknown'
# Calculate winning/losing/pending bets from bet details
match_id
=
stats_data
[
'match_id'
]
winning_bets
=
0
losing_bets
=
0
pending_bets
=
0
# Query all bet details for this match
from
sqlalchemy
import
func
bet_details_query
=
db
.
session
.
query
(
BetDetail
.
result
,
func
.
count
(
BetDetail
.
id
)
.
label
(
'count'
)
)
.
join
(
Bet
)
.
filter
(
Bet
.
client_id
==
data
[
'client_id'
],
BetDetail
.
match_id
==
match_id
)
.
group_by
(
BetDetail
.
result
)
for
result
,
count
in
bet_details_query
.
all
():
if
result
==
'won'
:
winning_bets
=
count
elif
result
==
'lost'
:
losing_bets
=
count
elif
result
==
'pending'
:
pending_bets
=
count
# Calculate balance (payin - payout)
total_payin
=
stats_data
[
'total_amount_collected'
]
total_payout
=
stats_data
[
'total_redistributed'
]
balance
=
total_payin
-
total_payout
# Create MatchReport record
match_report
=
MatchReport
(
sync_id
=
report_sync
.
id
,
client_id
=
data
[
'client_id'
],
client_token_name
=
client_token_name
,
match_id
=
match_id
,
match_number
=
stats_data
.
get
(
'match_number'
,
0
),
fixture_id
=
stats_data
[
'fixture_id'
],
match_datetime
=
datetime
.
fromisoformat
(
stats_data
[
'match_datetime'
]),
total_bets
=
stats_data
[
'total_bets'
],
winning_bets
=
winning_bets
,
losing_bets
=
losing_bets
,
pending_bets
=
pending_bets
,
total_payin
=
total_payin
,
total_payout
=
total_payout
,
balance
=
balance
,
actual_result
=
stats_data
[
'actual_result'
],
extraction_result
=
stats_data
[
'extraction_result'
],
cap_applied
=
stats_data
.
get
(
'cap_applied'
,
False
),
cap_percentage
=
stats_data
.
get
(
'cap_percentage'
),
cap_compensation_balance
=
cap_compensation_balance
,
under_bets
=
stats_data
.
get
(
'under_bets'
,
0
),
under_amount
=
stats_data
.
get
(
'under_amount'
,
0.00
),
over_bets
=
stats_data
.
get
(
'over_bets'
,
0
),
over_amount
=
stats_data
.
get
(
'over_amount'
,
0.00
),
result_breakdown
=
stats_data
.
get
(
'result_breakdown'
)
)
db
.
session
.
add
(
match_report
)
match_reports_count
+=
1
# Commit all changes
# Commit all changes
db
.
session
.
commit
()
db
.
session
.
commit
()
...
...
app/database/migrations/add_match_reports_table.py
0 → 100644
View file @
361fe6c0
"""
Migration script to add match_reports table for comprehensive match-level reporting
"""
def
upgrade
():
"""Add match_reports table"""
from
app
import
db
from
app.models
import
MatchReport
# Create the table
db
.
create_all
()
print
(
"✓ match_reports table created successfully"
)
def
downgrade
():
"""Remove match_reports table"""
from
app
import
db
from
sqlalchemy
import
text
# Drop the table
db
.
session
.
execute
(
text
(
"DROP TABLE IF EXISTS match_reports"
))
db
.
session
.
commit
()
print
(
"✓ match_reports table dropped successfully"
)
if
__name__
==
'__main__'
:
upgrade
()
\ No newline at end of file
app/main/routes.py
View file @
361fe6c0
This diff is collapsed.
Click to expand it.
app/models.py
View file @
361fe6c0
...
@@ -1089,4 +1089,88 @@ class ReportSyncLog(db.Model):
...
@@ -1089,4 +1089,88 @@ class ReportSyncLog(db.Model):
}
}
def
__repr__
(
self
):
def
__repr__
(
self
):
return
f
'<ReportSyncLog {self.sync_id} {self.operation_type} {self.status}>'
return
f
'<ReportSyncLog {self.sync_id} {self.operation_type} {self.status}>'
\ No newline at end of file
class
MatchReport
(
db
.
Model
):
"""Comprehensive match-level report data"""
__tablename__
=
'match_reports'
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
sync_id
=
db
.
Column
(
db
.
Integer
,
db
.
ForeignKey
(
'report_syncs.id'
),
nullable
=
False
,
index
=
True
)
client_id
=
db
.
Column
(
db
.
String
(
255
),
nullable
=
False
,
index
=
True
)
client_token_name
=
db
.
Column
(
db
.
String
(
255
),
nullable
=
False
,
index
=
True
)
# Match identification
match_id
=
db
.
Column
(
db
.
Integer
,
nullable
=
False
,
index
=
True
)
match_number
=
db
.
Column
(
db
.
Integer
,
nullable
=
False
)
fixture_id
=
db
.
Column
(
db
.
String
(
255
),
nullable
=
False
,
index
=
True
)
match_datetime
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
index
=
True
)
# Betting statistics
total_bets
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
winning_bets
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
losing_bets
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
pending_bets
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
# Financial data
total_payin
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
total_payout
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
balance
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
# Match result
actual_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
False
)
extraction_result
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
False
)
# CAP information
cap_applied
=
db
.
Column
(
db
.
Boolean
,
default
=
False
)
cap_percentage
=
db
.
Column
(
db
.
Numeric
(
5
,
2
))
cap_compensation_balance
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
# Detailed breakdown
under_bets
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
under_amount
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
over_bets
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
over_amount
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
result_breakdown
=
db
.
Column
(
db
.
JSON
)
# Metadata
created_at
=
db
.
Column
(
db
.
DateTime
,
default
=
datetime
.
utcnow
)
updated_at
=
db
.
Column
(
db
.
DateTime
,
default
=
datetime
.
utcnow
,
onupdate
=
datetime
.
utcnow
)
# Relationships
sync
=
db
.
relationship
(
'ReportSync'
,
backref
=
'match_reports'
,
lazy
=
'select'
)
def
to_dict
(
self
):
"""Convert to dictionary for JSON serialization"""
return
{
'id'
:
self
.
id
,
'sync_id'
:
self
.
sync_id
,
'client_id'
:
self
.
client_id
,
'client_token_name'
:
self
.
client_token_name
,
'match_id'
:
self
.
match_id
,
'match_number'
:
self
.
match_number
,
'fixture_id'
:
self
.
fixture_id
,
'match_datetime'
:
self
.
match_datetime
.
isoformat
()
if
self
.
match_datetime
else
None
,
'total_bets'
:
self
.
total_bets
,
'winning_bets'
:
self
.
winning_bets
,
'losing_bets'
:
self
.
losing_bets
,
'pending_bets'
:
self
.
pending_bets
,
'total_payin'
:
float
(
self
.
total_payin
)
if
self
.
total_payin
else
0.0
,
'total_payout'
:
float
(
self
.
total_payout
)
if
self
.
total_payout
else
0.0
,
'balance'
:
float
(
self
.
balance
)
if
self
.
balance
else
0.0
,
'actual_result'
:
self
.
actual_result
,
'extraction_result'
:
self
.
extraction_result
,
'cap_applied'
:
self
.
cap_applied
,
'cap_percentage'
:
float
(
self
.
cap_percentage
)
if
self
.
cap_percentage
else
None
,
'cap_compensation_balance'
:
float
(
self
.
cap_compensation_balance
)
if
self
.
cap_compensation_balance
else
0.0
,
'under_bets'
:
self
.
under_bets
,
'under_amount'
:
float
(
self
.
under_amount
)
if
self
.
under_amount
else
0.0
,
'over_bets'
:
self
.
over_bets
,
'over_amount'
:
float
(
self
.
over_amount
)
if
self
.
over_amount
else
0.0
,
'result_breakdown'
:
self
.
result_breakdown
,
'created_at'
:
self
.
created_at
.
isoformat
()
if
self
.
created_at
else
None
,
'updated_at'
:
self
.
updated_at
.
isoformat
()
if
self
.
updated_at
else
None
}
def
__repr__
(
self
):
return
f
'<MatchReport {self.client_token_name} match={self.match_id} balance={self.balance}>'
\ No newline at end of file
app/templates/main/client_report_detail.html
View file @
361fe6c0
This diff is collapsed.
Click to expand it.
app/templates/main/reports.html
View file @
361fe6c0
This diff is collapsed.
Click to expand it.
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