Bolão de Futebol
Predictions, scoring and ranking
- My role
- Full stack developer
- Client
- Catsuc Labs
- Sector
- Consumer product · web
- Status
- In production
Proprietary code. Private client system — the repository isn’t public.
Numbers
apito
prediction lock
Deadline checked on the server — never on the client clock.
100%
recomputable scoring
Scoring is a pure function of the result, so a correction is a reprocess.
Context
Pools among friends have always lived in spreadsheets and group chats. The problem was never the maths — it was trust: who predicted before kickoff, who changed it afterwards, and why the leaderboard shifted overnight.
The problem
Three things that look like details and aren’t: the prediction has to lock at kickoff, non-negotiably; the scoring rule varies per tournament and can’t be scattered through the code; and official results can be corrected — so scoring has to be re-runnable without turning into a mess.
The approach
The deadline belongs to the server. The client hides the form as a courtesy, but the route is what rejects a late prediction — a client clock is a suggestion, not an authority. Scoring became a pure function of (prediction, result, tournament rule), so re-scoring is safe by construction: if a result is corrected, reprocess and the leaderboard rebuilds with no dirty state left behind.
Modules
Campeonatos
Tournament structure, with the scoring rule attached to the tournament instead of hardcoded.
Palpites
Entry and editing until kickoff. After that the route refuses — and the entrant sees exactly why.
Apuração
Runs over the official result and can run again. A corrected result is a reprocess, not a patch.
Ranking
Standings with explicit tie-break criteria, so two tied entrants get a stable order.
The hard parts
The client clock lies
Hiding the form at kickoff is UX. Refusing a late prediction is integrity — and only the server can do that. In a pool, one prediction accepted after the whistle isn’t an edge-case bug: it’s the reason nobody trusts the leaderboard again.
Scoring rule as data
Every tournament scores differently. Scattering that through conditionals guarantees drift at the first change. Treating the rule as tournament configuration and scoring as a pure function made recomputation trivial — which is what saved us when an official result was corrected.
What’s mine
- Modeling tournaments, rounds, matches and predictions
- Server-side deadline enforcement and the messages explaining a refusal
- A deterministic, re-runnable scoring engine
- Standings with tie-breaks, and the participation screens
Outcome
The pool moved off the spreadsheet and the screenshot in the group chat. The deadline is the same for everyone, the leaderboard explains itself, and a corrected result stopped being an argument and became a reprocess.
What I took from it
- 01A deadline is a server rule. Anything validated only on the client is not validated.
- 02Deterministic, re-runnable computation beats fast computation.
- 03In a competitive product the leaderboard must be explainable — otherwise the argument becomes support.