- Python 75.1%
- CSS 13.1%
- JavaScript 10.8%
- HTML 0.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Reported: "viser du NT på anytime goalscorer nå og?" — we did, 240 of 264 rows,
and the panel said otherwise.
screen Šeško 47% "5 books" under a header reading BOOK CONSENSUS
data source NT_ODDSEN, nt_price 2.00 -> 47%
consensus 43.3%, from those 5 books
The 47% was Norsk Tipping's; "5 books" described the consensus's 43.3%, which
appeared nowhere. Three faults, one class — a label written by hand beside data
that moved:
* `scorerBook()` read the source of the FIRST row and the header applied it
to all 264. The board sorts by price, Haaland leads, and his is the one
fixture NT never covered (it kicked off before the pull), so a single
consensus row relabelled a panel that was 240/264 NT.
* `n_books` printed on every row, including NT ones. It is deliberately kept
from the consensus by `merge_market` — `_will_play` needs it — but beside
an NT price it reads as describing that price.
* the hint said "Ordered by the bookmakers' price", true the day it was typed.
Header, row label and hint are all derived from `p_score_source` now, and a
mixed panel says so rather than crediting one book.
ALSO, AND THIS ONE WAS BACKWARDS IN CLAUDE.md: NT quoting fewer players was
filed as a cost. Measured against realised GW2 minutes over the nine fixtures
NT covered (CRY v MCI excluded — never asked, kicked off before the pull):
quoted by both 275 47.9 min 51% played 60+
consensus only, NT declined 63 2.4 min 2%
NT only 23 37.1 min 35%
neither 140 11.5 min 12%
A 45.5-minute gap, and not lineup-reading: NT's declines averaged 2.0 min under
24h out and 3.2 min beyond 24h, when no team news existed. Issue #16's finding
with NT's silence four times sharper than the consensus's.
Standing instruction recorded with it: show Norsk Tipping until the opposite is
PROVEN, several rounds before arguing against it. `backend/score_history.py`
makes that mechanical rather than a promise — the scorer printed and persisted
nothing, so each week's evidence died in scrollback. Rounds are banked; the
verdict has a floor (4 rounds, 0.010 log loss, and a split record stays
undetermined at any size) but calls a source that loses to the control at once.
Plus: the depth badge now names who is out, derived from `depth_blocked_by`,
which was computed, cached, served and read nowhere; `depth_blocked_by` is set
on every player so the golden master's direct subscripts hold; and its frozen
field list and fixture were updated — the gate caught the new field on a
rebuild, hours after the code that added it was green in CI.
Full suite green (PYTEST_EXIT=0).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzuxHvACMHg8u1AhdK6k5
|
||
| .claude/agents | ||
| .forgejo/workflows | ||
| backend | ||
| data | ||
| docs | ||
| frontend | ||
| scripts | ||
| tests | ||
| .gitignore | ||
| build-to-gw1.md | ||
| CLAUDE.md | ||
| constraints.txt | ||
| pyproject.toml | ||
| README.md | ||
| run.bat | ||
| run.ps1 | ||
FPL squad optimiser
An evidence-conscious Fantasy Premier League projection and squad-planning app. It combines official FPL data, historical match and player data, simulations, availability estimates, optional market data, and FPL squad rules to produce a rolling six-gameweek view.
The application includes:
- player and fixture projections;
- best-XI, captain and vice-captain analysis;
- squad building and automatic squad optimisation;
- transfer, ownership and fixture views;
- explicit provenance and refusal states for weak or missing inputs;
- backtesting and calibration utilities;
- point-in-time snapshots for auditable decisions.
The project is intended primarily as a local decision-support tool. It is not a hosted multi-user service, and its write/rebuild endpoints currently have no authentication.
Repository map
| Path | Purpose |
|---|---|
backend/data/ |
Official FPL client, typed data models, SQLite store and sanity checks |
backend/model/ |
Minutes, team strength, finishing, lineup and suspension models |
backend/scoring/ |
FPL scoring, BPS and defensive-contribution rules |
backend/simulator/ |
Match and fixture Monte Carlo simulation |
backend/odds/ |
Odds ingestion, name matching and market comparison |
backend/web/ |
Projection pipeline, optimiser, cache and FastAPI application |
frontend/ |
Static HTML, CSS and JavaScript client |
scripts/ |
Cache builds, data acquisition, diagnostics and backtests |
tests/ |
Unit, integration, API and browser tests |
data/ |
Committed inputs and evidence artifacts; generated local state is ignored |
docs/ |
Designs, methodology notes and maintainer documentation |
See Architecture, Development, and API reference for more detail. Contributors should read CLAUDE.md before changing model or data-flow code; it records the failure modes and invariants that shape this repository.
Quick start
Python 3.11 or newer is required. One command creates an environment and runs the suite from a clean checkout:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[test]" -c constraints.txt
constraints.txt records the versions this suite has actually been run
against. It is not a lockfile - it does not pin transitive dependencies - but it
makes a failure attributable rather than a guess, given that pyproject.toml
declares only lower bounds.
Build the projection cache, then start the app from the repository root:
python scripts/build_cache.py
python -m uvicorn backend.web.main:app --host 127.0.0.1 --port 8000
Open http://127.0.0.1:8000/. Keep the server on loopback: administrative routes can rebuild projections and write local state.
On Windows, use the included launcher:
run.bat
run.bat -Restart
run.bat -Port 8123
The server can start in cache-only mode if live dependencies cannot be loaded.
In that state, read-only cached views remain available, while operations that
need the complete dependency graph return 503.
Common workflows
Rebuild projections after model or source-data changes:
python scripts/build_cache.py
Verify that a change moved no projections (the golden master):
python scripts/golden_master.py
It exits 0 when every frozen field matches, 1 when something moved, and
2 when the cache predates the current code — in which case rebuild first,
because a comparison against a stale cache proves nothing.
Refresh only live prices, ownership and availability while the server runs:
curl -X POST http://127.0.0.1:8000/api/refresh-live
Take an immutable point-in-time snapshot before a deadline:
python scripts/snapshot_pit.py
Ingest a finished gameweek so the model learns from the current season:
python scripts/fetch_current_season.py
Run the principal walk-forward evaluations:
python scripts/backtest_rps.py
python scripts/backtest_minutes.py
Run tests after installing the test dependencies:
python -m pytest
Browser tests use Playwright and may be skipped when it is unavailable. Install it separately when working on rendered frontend behavior:
python -m pip install playwright
python -m playwright install chromium
python -m pytest tests/web
Generated local state
The application writes these ignored files:
data/cache/projections.json— projection cache;data/fpl.db— SQLite store, rebuildable withscripts/import_archive.py;data/squad.json— current squad selection;data/overrides.json— explicit start-probability overrides;data/pit/— point-in-time snapshots.
data/golden/baseline.json is committed rather than ignored: it is the
reference the golden master compares against, and it is only useful if it
travels with the code it describes.
Projection artifacts carry build time and source-code provenance. Missing or invalid provenance is intentionally treated as an error rather than silently replaced with a plausible default.
Data sources
The official FPL API is the primary live source. The repository also contains
historical archive data and optional pipelines for Transfermarkt, Understat,
Solio and bookmaker player props. Some acquisition scripts may require external
credentials, metered APIs or pre-registered run scopes. Read their help and the
data-source rules in CLAUDE.md before running them.
Current maintenance notes
The repository review is tracked in Forgejo issues. Known improvement areas include clean-checkout dependency locking and CI, package discovery, persistence concurrency, API request models and module decomposition.
HTML interpolation (issue #1) is partially addressed: the frontend's esc()
helper was a JavaScript-string escaper carrying an HTML escaper's name, and now
escapes &, <, >, " and ' in a single pass. Remaining interpolation
sites have not been audited.
This documentation describes the current implementation rather than implying those items are resolved.