Upcoming football matches with honest, team-specific probabilities — a per-team Dixon-Coles model blended with a LightGBM ensemble, served with bookmaker odds comparison and an anytime-goalscorer view.
SciKick is an analytical instrument, not a betting tool. It answers "who wins this weekend, and is the bookmaker price fair?" in plain fan language, with analyst detail one toggle away.
Live: https://scikick.pages.dev — daily static export served from Cloudflare (Pages + read-only Worker), retrained weekly by CI.
- Every upcoming match, 6 leagues (Premier, La Liga, Bundesliga, Serie A, Ligue 1, Ecuador Liga Pro) — from today on, nearest first, 12 per page, with a toggle for past dates.
- One story per match: verdict first ("Arsenal wins 7 in 10"), then the evidence across Mercados / Contexto / Valor tabs. Verdict, charts and context stay in plain language; the probable scoreline only appears when it agrees with the verdict.
- Full-market explorer: accordions open on the active market, the chart updates live side by side; half-time scenarios one at a time.
- Value check that names its source ("best European odds", possibly split across shops) with plain-words stakes; edges above +100% are treated as bad data. Model combo labeled as a rough guide.
- Followed page, scorer search, shareable
/partido/:idroutes, ES/EN switch, light/dark pine theme.
- Per-team Dixon-Coles (
app/models/): attack/defence strengths per club with a shrinkage prior (k=8games) for promoted teams; LightGBM ensemble + isotonic calibration. Walk-forward Brier (Sep 2026):
| E0 | SP1 | D1 | I1 | F1 | EC1 |
|---|---|---|---|---|---|
| 0.596 | 0.527 | 0.470 | 0.626 | 0.603 | 0.570 |
EC1 runs the same pipeline on ESPN data (no odds/xG sources there yet, so no value badges, corners/cards or scorer xG for now).
Small test sets (27–37 matches) — judge trends over cycles, see docs/retrain.md. Scorer: Understat xG90 with position shrinkage and a minutes gate that scales early season. Served predictions carry the evaluated blend (blend_applied), never Dixon-Coles alone.
football-data.org ──▶ fixtures ──┐
football-data.co.uk ─▶ history ───┤──▶ sync ──▶ features ──▶ models ──▶ API ──▶ UI
Understat ──────────▶ player xG ──┤ Elo + form + xG ──▶ per-team DC + LightGBM
The Odds API ───────▶ odds ───────┘ (1 credit/league/day, best EU price stored)
ESPN (ecu.1) ──▶ EC1 fixtures + history (no odds/xG yet)
FastAPI + SQLite (GET /fixtures with upcoming filter, /predict/{id}, POST /value with source, /context, /predict/scorer/{id}, stats, token-authed /refresh + /resolve). React + TypeScript UI (Radix, Recharts, WCAG-AA audited palette). VITE_API_URL points at any backend.
- Tested: 374 backend tests / 54 files (pytest) + 226 frontend tests / 33 files (vitest);
oxlint+vite buildgreen on every PR via split CI. - Reproducible: every train persists params, strengths and metrics (
data/runs/<league>/); monthly ops indocs/retrain.md. CPU-only, free-tier sources.
python -m venv .venv; .\.venv\Scripts\Activate.ps1
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
Copy-Item .env.example .env # fill in: FOOTBALL_DATA_ORG_KEY (required), ODDS_API_KEY (optional), SERVICE_TOKEN (/refresh + /resolve)
.\.venv\Scripts\python.exe -m app.cli sync --league E0,SP1,D1,I1,F1 --seasons 3
.\.venv\Scripts\python.exe -m app.cli train --league E0 # ~10 min CPU, repeat per league
.\.venv\Scripts\python.exe -m app.cli predict --league E0
.\.venv\Scripts\python.exe -m uvicorn app.api.main:app --port 8000
cd frontend; npm ci; npm run dev # http://localhost:5173app/ — config, db + migrations, ingestion, features, models, api, players
frontend/src/ — api clients, feed/match components, ui primitives, i18n EN/ES
migrations/ — numbered SQL (001–009) via PRAGMA user_version
docs/ — retrain.md (monthly ops), screenshots/
Production is Cloudflare-only (free tier): the frontend ships to Pages, the
API is a read-only Worker over a static JSON bundle on a second Pages project,
and .github/workflows/export-cloudflare.yml rebuilds it — daily data refresh
(06:30 UTC), full retrain Sundays (07:00 UTC), deploy gated on a non-empty
bundle that keeps predictions. API: https://scikick-api.pablodo004.workers.dev.
Copy-Item .env.example .env # set SERVICE_TOKEN + API keys
docker compose up --build -d # local API on :8000, SQLite persisted in ./dataThe frontend deploys anywhere static (Vercel-ready) with VITE_API_URL pointing at the API.
.\.venv\Scripts\python.exe -m pytest tests/ -m "not slow" # ENV=test via conftest
cd frontend; npm run lint; npm test; npm run build- 2025/26 history: 2526 CSVs unpublished (empty placeholders in
data/raw/); trains on 22/23–24/25 meanwhile. - No confirmed lineups (needs API-Football Pro) — scorer projects from minutes and says so; no transfers, no in-play.
- Early-season noise: edges above +100% treated as bad data; calibration needs 30+ resolved predictions.
- Stored odds are best-per-outcome, possibly split across shops — a +EV set may not be buyable in one place.
- Display data: canonicals render with accents mapped (
Espanol→Español); crests backfill from the next sync.
MIT © 2026 Pablo Domínguez Aguilera


