OpenAthlete
AGPLv3 endurance training platform: EU-oriented hosting, transparent CTL/ATL/TSB logic in code, self-hosting, and full export. Built in Grenoble.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Local FPL engine. No web app — everything runs from the CLI and outputs to terminal + JSON. The differentiator is mini-league intelligence: it optimises against your rivals, not just for raw points.
| Module | What it does |
|---|---|
src/rivalr/fetch.py | FPL API client, disk cache (6h bootstrap, 1h standings), 429 backoff, cache-miss logging |
src/rivalr/rivals.py | rival squads, chips, banks, transfer history; mini-league effective ownership; SHIELD/SWORD/NEUTRAL classification; per-rival overlap and differentials → rivals_report.json |
src/rivalr/model.py | OpenFPL trained-model inference (no retraining), 228 rolling-window features from FPL + Understat |
src/rivalr/understat.py | Understat JSON API client (league + player endpoints), cross-season history, loud failure logging |
src/rivalr/minutes.py | v0 expected-minutes: rolling starts, minutes trend, availability flag, congestion; news-based v1 hook |
src/rivalr/optimise.py | wraps the FPL-Optimization-Tools HiGHS MILP with our projections; three objectives: points / chase / defend |
src/rivalr/report.py | one-command gameweek brief (Telegram-friendly plain text) |
src/rivalr/ledger.py + score.py | append-only prediction ledger, post-GW RMSE/MAE by outcome bucket + transfer counterfactual |
Requires uv and git.
uv sync
uv run python scripts/setup_vendors.py # clones OpenFPL (~750MB models) + FPL-Optimization-Tools into vendor/
The gameweek brief (the main command):
uv run python -m rivalr.report --team <your_entry_id> --league <classic_league_id> --mode chase --target <rival_entry_id>
--mode points — plain xPts maximisation (no target needed)--mode chase --target <id> — attack a rival above you: differentials + variance--mode defend --target <id> — protect a lead: cover the chaser's squad, prefer shieldsAll three plans are always solved and shown side by side with honest (unweighted) expected points and a next-GW swing-vs-target estimate.
Score a finished gameweek against the ledger:
uv run python -m rivalr.score --gw 5
Smoke tests (no team/league needed):
uv run python scripts/smoke.py # fetch + model + minutes
uv run python scripts/smoke_optimise.py # full MILP solve
Tests:
uv run pytest
OpenFPL (vendor/OpenFPL, daniegr/OpenFPL): 250 joblib models
(xgboost 3.0.2 / sklearn 1.5.2-era pickles; deps pinned in our
pyproject to their plug.txt). Input is a 228-feature vector per
(player, fixture): rolling means of FPL + Understat stats over the
previous {1,3,5,10,38} gameweeks, min-max scaled by models/xscaler.save,
sliced per position via models/features.save, predicted by 50 models
per position (5 CV folds x 10 candidates), inverse-scaled
(points = y*33 - 7), median-ensembled. Predicts one GW at a time; we
re-run per future fixture with the opponent block swapped.
FPL-Optimization-Tools (vendor/FPL-Optimization-Tools,
sertalpbilal): HiGHS MILP via highspy. We bypass its CLI and call
dev.solver.prep_data + solve_multi_period_fpl directly, writing our
projections into vendor/.../data/rivalr_<mode>.csv in its fplreview
format: ID,Name,Pos,Value,Team,{gw}_Pts,{gw}_xMins,... (ID = FPL
element id; Pos in G/D/M/F; the solver re-derives prices/teams from
bootstrap-static). Options come from its comprehensive_settings.json
plus our overrides (see SOLVER_OVERRIDES in optimise.py).
uv sync --extra api
uv run uvicorn rivalr.api:app --port 8000
GET /health — status, gameweek, deadline, last snapshot timeGET /brief?team_id&league_id&mode&target — the brief as JSON
(squad with base/defcon/flags, captain, transfers with per-rival
swings, rivals block, warnings). Slow solves return {job_id} (202);
poll GET /brief/status?job_id=.GET /league?league_id — standings + entry ids for a target picker.Caching: Postgres via DATABASE_URL (in-memory fallback without it),
keyed (team, league, mode, target, gw), TTL 1h, always bypassed inside
the 4h pre-deadline window. Rate limit 30 req/min/IP. CORS allows
localhost dev ports plus RIVALR_CORS_ORIGINS (comma-separated).
Run a SINGLE uvicorn worker process — jobs and cache-bypass state are
in-process.
A rivals failure degrades to rivals: null plus a warning; squad and
transfers still return.
The Dockerfile serves both processes: web (default CMD, uvicorn) and
worker (python -m rivalr.worker — the same hourly snapshot code path
as the local Task Scheduler entry). Model artifacts (~750MB of OpenFPL
joblibs) are NOT in the image: the entrypoint clones both vendor repos
into RIVALR_VENDOR_DIR on first boot — mount a Railway volume at
/data per service so it happens once. Env per service:
RIVALR_TEAM_ID, RIVALR_LEAGUE_ID, RIVALR_TELEGRAM_BOT_TOKEN,
RIVALR_TELEGRAM_CHAT_ID, RIVALR_CORS_ORIGINS (web),
DATABASE_URL (web; from the Railway Postgres plugin). Once the
Railway worker is verified, disable the local Task Scheduler entry
(schtasks /Change /TN rivalr-snapshot /DISABLE) so one snapshot
source is canonical.
This is the beginning of the README. Read the rest at the original. Read the full README ↗