Add two configurable leaderboard settings per league, replacing the global constants with per-row DB columns: - eligible_min_sessions (default 3): minimum sessions before a player ranks on the main leaderboard rather than showing as provisional - break_even_cents (default 100 = $1.00): sessions within ±this of $0 net are classified as break-even rather than a win or loss Changes: - Migration 0003 adds both columns with server defaults for existing leagues - League DB model gains eligible_min_sessions and break_even_cents columns - net_result_bucket() and build_leaderboard() accept optional tolerance param - summarize_player_runs() passes tolerance to net_result_bucket for streaks - League leaderboard route reads values from the league row instead of config - League settings form exposes both fields with validation (1-100 sessions, $0.00-$100.00 threshold)
Poker Portal
A small Flask app I put together for tracking our low-stakes hold'em nights without having to keep a spreadsheet open all the time.
The idea is pretty simple: public pages for stats and session history, plus a small admin area for recording buy-ins, gross cashout results, actual paid-out cash, and notes. The data sits in a CSV audit log, so everything is append-only and easy to follow later.
What it does
- all-time leaderboard
- per-session pages
- per-player stat pages
- admin login for recording events
- open / closed session tracking
- actual cash payout tracking with
paid_outevents - debt repayments and write-offs with
debt_repayment/writeoffevents - session and player charts
- CSV import / export from the admin page
- append-only
entries.csvledger instead of overwriting old rows
Stack
Built with a pretty lightweight setup:
- Python
- Flask
- Jinja templates
- Chart.js
- plain CSS
- CSV event log for storage
Ledger model
The app treats data/entries.csv as the source of truth.
Each row is an event, not a final snapshot. Instead of editing an old row, I append another one. That keeps rebuys, corrections, payouts, and session state changes visible in the log instead of hiding them behind edits.
Current event types:
buyinfrontdebt_repaymentwriteoffcashoutpaid_outrollover_inpayout_carry_inrollover_outnotesession_opensession_close
A few examples:
- another
buyinfor a rebuy - another
cashoutif chip counts are corrected later - a
paid_outevent when someone is actually settled up - a
debt_repaymentevent when a front is repaid outside poker - a
writeoffevent when a front will not be collected - a
noteevent for bookkeeping context session_open/session_closeto mark whether a game night is still live
Accounting in the app keeps poker results separate from banker cashflow:
- poker investment is
buyin + front + rollover_in - poker net is
cashout - poker investment - real cash in is
buyin + debt_repayment - real cash out is
paid_out rollover_outsettles the source session without counting as cash outrollover_inenters play in the destination session without counting as cash inpayout_carry_inrecords prior-session value carried into a later payout; it increases the destination payout due without counting as poker investment or cash inwriteoffresolves a receivable without counting as cash in
Older ledgers may still contain paid, front_collected, or front_writeoff.
The app reads those historical names as aliases for paid_out,
debt_repayment, and writeoff.
It is still just a small side project, but I wanted the event model to stay clean enough that the numbers are easy to trust and the history is easy to read back through.
CSV format
Main file:
data/entries.csv
Header:
id,created_at,session_id,session_date,player_name,event_type,amount_cents,note,actor
Amounts are stored in cents to avoid floating-point issues.
Running it locally
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
python app.py
Then open:
http://127.0.0.1:8000
Environment values
The app reads these from .env:
SECRET_KEYADMIN_USERNAMEADMIN_PASSWORD
Example:
SECRET_KEY=change-this
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change-me
Notes
A few choices here were deliberate:
- no database for now
- no user accounts, just one admin login
- public-facing stats pages, admin-only controls
- CSV backup before importing a replacement ledger
If I ever decide to take it further, the first real upgrade would probably be moving the storage layer to SQLite while keeping the rest of the app roughly the same.