player win/loss streaks
This commit is contained in:
3 files changed
+176
-1
No files matched your search
@@ -828,6 +828,10 @@ td a:hover {
|
|||||||
background: rgba(255, 255, 255, 0.07);
|
background: rgba(255, 255, 255, 0.07);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-frame-small {
|
||||||
|
min-height: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 980px) {
|
@media (max-width: 980px) {
|
||||||
.site-shell {
|
.site-shell {
|
||||||
width: min(100% - 24px, 100%);
|
width: min(100% - 24px, 100%);
|
||||||
|
|||||||
@@ -76,6 +76,14 @@ class PlayerStats:
|
|||||||
total_cash_out_cents: int
|
total_cash_out_cents: int
|
||||||
total_net_cents: int
|
total_net_cents: int
|
||||||
roi_pct: float
|
roi_pct: float
|
||||||
|
current_win_streak: int
|
||||||
|
current_loss_streak: int
|
||||||
|
longest_win_streak: int
|
||||||
|
longest_loss_streak: int
|
||||||
|
best_session_date: str | None
|
||||||
|
best_session_net_cents: int
|
||||||
|
worst_session_date: str | None
|
||||||
|
worst_session_net_cents: int
|
||||||
rank_change: int = 0
|
rank_change: int = 0
|
||||||
|
|
||||||
|
|
||||||
@@ -189,6 +197,68 @@ def build_session_summaries(events: list[EventRow]) -> list[SessionSummary]:
|
|||||||
return sessions
|
return sessions
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_player_runs(entries: list[SessionEntry]) -> dict[str, int | str | None]:
|
||||||
|
ordered_entries = sorted(entries, key=lambda entry: entry.session_date)
|
||||||
|
|
||||||
|
longest_win_streak = 0
|
||||||
|
longest_loss_streak = 0
|
||||||
|
current_run_wins = 0
|
||||||
|
current_run_losses = 0
|
||||||
|
|
||||||
|
best_entry: SessionEntry | None = None
|
||||||
|
worst_entry: SessionEntry | None = None
|
||||||
|
|
||||||
|
for entry in ordered_entries:
|
||||||
|
net = entry.net_cents
|
||||||
|
|
||||||
|
if best_entry is None or net > best_entry.net_cents:
|
||||||
|
best_entry = entry
|
||||||
|
|
||||||
|
if worst_entry is None or net < worst_entry.net_cents:
|
||||||
|
worst_entry = entry
|
||||||
|
|
||||||
|
if net > 0:
|
||||||
|
current_run_wins += 1
|
||||||
|
current_run_losses = 0
|
||||||
|
elif net < 0:
|
||||||
|
current_run_losses += 1
|
||||||
|
current_run_wins = 0
|
||||||
|
else:
|
||||||
|
current_run_wins = 0
|
||||||
|
current_run_losses = 0
|
||||||
|
|
||||||
|
longest_win_streak = max(longest_win_streak, current_run_wins)
|
||||||
|
longest_loss_streak = max(longest_loss_streak, current_run_losses)
|
||||||
|
|
||||||
|
current_win_streak = 0
|
||||||
|
current_loss_streak = 0
|
||||||
|
|
||||||
|
for entry in reversed(ordered_entries):
|
||||||
|
net = entry.net_cents
|
||||||
|
|
||||||
|
if net > 0:
|
||||||
|
if current_loss_streak > 0:
|
||||||
|
break
|
||||||
|
current_win_streak += 1
|
||||||
|
elif net < 0:
|
||||||
|
if current_win_streak > 0:
|
||||||
|
break
|
||||||
|
current_loss_streak += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return {
|
||||||
|
"current_win_streak": current_win_streak,
|
||||||
|
"current_loss_streak": current_loss_streak,
|
||||||
|
"longest_win_streak": longest_win_streak,
|
||||||
|
"longest_loss_streak": longest_loss_streak,
|
||||||
|
"best_session_date": best_entry.session_date if best_entry else None,
|
||||||
|
"best_session_net_cents": best_entry.net_cents if best_entry else 0,
|
||||||
|
"worst_session_date": worst_entry.session_date if worst_entry else None,
|
||||||
|
"worst_session_net_cents": worst_entry.net_cents if worst_entry else 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]:
|
def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]:
|
||||||
player_entries: dict[str, list[SessionEntry]] = defaultdict(list)
|
player_entries: dict[str, list[SessionEntry]] = defaultdict(list)
|
||||||
for session in sessions:
|
for session in sessions:
|
||||||
@@ -198,6 +268,7 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]:
|
|||||||
leaderboard: list[PlayerStats] = []
|
leaderboard: list[PlayerStats] = []
|
||||||
for player_name, entries in player_entries.items():
|
for player_name, entries in player_entries.items():
|
||||||
nets = [entry.net_cents for entry in entries]
|
nets = [entry.net_cents for entry in entries]
|
||||||
|
run_summary = summarize_player_runs(entries)
|
||||||
wins = [value for value in nets if value > 0]
|
wins = [value for value in nets if value > 0]
|
||||||
losses = [value for value in nets if value < 0]
|
losses = [value for value in nets if value < 0]
|
||||||
total_buy_in = sum(entry.buy_in_cents for entry in entries)
|
total_buy_in = sum(entry.buy_in_cents for entry in entries)
|
||||||
@@ -232,6 +303,14 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]:
|
|||||||
total_cash_out_cents=total_cash_out,
|
total_cash_out_cents=total_cash_out,
|
||||||
total_net_cents=total_net,
|
total_net_cents=total_net,
|
||||||
roi_pct=roi_pct,
|
roi_pct=roi_pct,
|
||||||
|
current_win_streak=run_summary["current_win_streak"],
|
||||||
|
current_loss_streak=run_summary["current_loss_streak"],
|
||||||
|
longest_win_streak=run_summary["longest_win_streak"],
|
||||||
|
longest_loss_streak=run_summary["longest_loss_streak"],
|
||||||
|
best_session_date=run_summary["best_session_date"],
|
||||||
|
best_session_net_cents=run_summary["best_session_net_cents"],
|
||||||
|
worst_session_date=run_summary["worst_session_date"],
|
||||||
|
worst_session_net_cents=run_summary["worst_session_net_cents"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,24 @@
|
|||||||
<article class="panel stat-card"><span>Buy-ins</span><strong>{{ player.total_buy_in_cents | money }}</strong></article>
|
<article class="panel stat-card"><span>Buy-ins</span><strong>{{ player.total_buy_in_cents | money }}</strong></article>
|
||||||
<article class="panel stat-card"><span>Cash-outs</span><strong>{{ player.total_cash_out_cents | money }}</strong></article>
|
<article class="panel stat-card"><span>Cash-outs</span><strong>{{ player.total_cash_out_cents | money }}</strong></article>
|
||||||
<article class="panel stat-card"><span>Sessions</span><strong>{{ player.sessions_played }}</strong></article>
|
<article class="panel stat-card"><span>Sessions</span><strong>{{ player.sessions_played }}</strong></article>
|
||||||
|
<article class="panel stat-card">
|
||||||
|
<span>Current Streak</span>
|
||||||
|
<strong>
|
||||||
|
{% if player.current_win_streak > 0 %}
|
||||||
|
W{{ player.current_win_streak }}
|
||||||
|
{% elif player.current_loss_streak > 0 %}
|
||||||
|
L{{ player.current_loss_streak }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</strong>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="panel stat-card"><span>Longest win streak</span><strong>{{ player.longest_win_streak }}</strong></article>
|
||||||
|
<article class="panel stat-card"><span>Longest loss streak</span><strong>{{ player.longest_loss_streak }}</strong></article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="grid two-col">
|
<section class="grid one-col">
|
||||||
<div class="panel chart-panel">
|
<div class="panel chart-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div>
|
<div>
|
||||||
@@ -30,6 +45,9 @@
|
|||||||
<canvas id="playerCumulativeChart"></canvas>
|
<canvas id="playerCumulativeChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="grid two-col">
|
||||||
<div class="panel chart-panel">
|
<div class="panel chart-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div>
|
<div>
|
||||||
@@ -41,6 +59,17 @@
|
|||||||
<canvas id="playerSessionChart"></canvas>
|
<canvas id="playerSessionChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel chart-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Breakdown</p>
|
||||||
|
<h2>Result mix</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-frame chart-frame-small">
|
||||||
|
<canvas id="playerBreakdownChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
@@ -60,6 +89,28 @@
|
|||||||
<tr><th>Average Loss</th><td>{{ player.avg_loss_cents | money }}</td></tr>
|
<tr><th>Average Loss</th><td>{{ player.avg_loss_cents | money }}</td></tr>
|
||||||
<tr><th>Biggest Win</th><td>{{ player.biggest_win_cents | money }}</td></tr>
|
<tr><th>Biggest Win</th><td>{{ player.biggest_win_cents | money }}</td></tr>
|
||||||
<tr><th>Biggest Loss</th><td>{{ player.biggest_loss_cents | money }}</td></tr>
|
<tr><th>Biggest Loss</th><td>{{ player.biggest_loss_cents | money }}</td></tr>
|
||||||
|
<tr><th>Current Win Streak</th><td>{{ player.current_win_streak }}</td></tr>
|
||||||
|
<tr><th>Current Loss Streak</th><td>{{ player.current_loss_streak }}</td></tr>
|
||||||
|
<tr><th>Longest Win Streak</th><td>{{ player.longest_win_streak }}</td></tr>
|
||||||
|
<tr><th>Longest Loss Streak</th><td>{{ player.longest_loss_streak }}</td></tr>
|
||||||
|
<tr>
|
||||||
|
<th>Best Session</th>
|
||||||
|
<td>
|
||||||
|
{{ player.best_session_net_cents | money }}
|
||||||
|
{% if player.best_session_date %}
|
||||||
|
<span class="muted-text small">on {{ player.best_session_date | pretty_date }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Worst Session</th>
|
||||||
|
<td>
|
||||||
|
{{ player.worst_session_net_cents | money }}
|
||||||
|
{% if player.worst_session_date %}
|
||||||
|
<span class="muted-text small">on {{ player.worst_session_date | pretty_date }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,4 +189,45 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const breakdownCtx = document.getElementById('playerBreakdownChart');
|
||||||
|
if (breakdownCtx) {
|
||||||
|
new Chart(breakdownCtx, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: ['Wins', 'Losses', 'Break-even'],
|
||||||
|
datasets: [{
|
||||||
|
data: [
|
||||||
|
{{ player.winning_sessions }},
|
||||||
|
{{ player.losing_sessions }},
|
||||||
|
{{ player.break_even_sessions }}
|
||||||
|
],
|
||||||
|
backgroundColor: [
|
||||||
|
'#59d98e',
|
||||||
|
'#f06c72',
|
||||||
|
'#e4bf65'
|
||||||
|
],
|
||||||
|
borderWidth: 0,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
color: textMuted,
|
||||||
|
usePointStyle: true,
|
||||||
|
boxWidth: 10,
|
||||||
|
boxHeight: 10,
|
||||||
|
padding: 18,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in new issue
Block a user