BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2,或任何藍牙控制器搭配 MyWhoosh、Rouvy、Zwift 等。可於 iOS、Android、Windows 與 macOS 免費試用。
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
A free, installable web app for Fantasy Premier League: deadline countdowns, alerts about your players, transfer ideas, and a mini-league page built for banter with friends.
Built so far: the database, a scheduled job that pulls FPL data into it, sign-in, a My gameweek page, push notifications on an installable app, a mini-league page, an expected-points model, and a transfer planner built on it.
Everything runs on free tiers: Vercel (web app), Supabase (database and sign-in), GitHub Actions (scheduled jobs).檢視完整 README ↗A free, installable web app for Fantasy Premier League: deadline countdowns, alerts about your players, transfer ideas, and a mini-league page built for banter with friends.
Built: the database and a scheduled job that pulls FPL data into it, sign-in, My gameweek, push notifications on an installable app, a mini-league page with AI-written weekly roasts, live scores, a Scout page, an expected-points model, and a transfer and chip planner built on it, backtested against a real season.
FPL-Raptor/
├── app/ Next.js pages: / (welcome + deadline), /login, /onboarding, /account, /me (Team), /live, /planner, /scout, /league, /notifications
├── components/ Pitch, shirts, player card, countdown, squad list, header and tab bar, install help
├── public/ sw.js (shows notifications) and app icons
├── lib/ Supabase clients, FPL helpers, squad, league, live scoring, Scout, pitch and player-card logic (+ Vitest tests)
├── proxy.ts Refreshes the sign-in session on every request
├── jobs/ Python: the scheduled FPL -> Supabase fetch
│ ├── raptor/ fpl.py (API + parsing), db.py, changes.py, run.py (fetch), alerts.py (push), recaps.py (AI recaps)
│ ├── raptor/model/ expected-points model: history.py, features.py, train.py, predict.py
│ ├── raptor/optimizer/ transfer planner: solve.py (the integer program), chips.py, inputs.py, run.py
│ ├── raptor/backtest/ season replay: season.py, actuals.py (last season's real results), report.py
│ ├── backtest/ replay results and REPORT.md
│ ├── model/ the trained model (xpts.joblib), its metadata, and REPORT.md
│ └── tests/ pytest, using small made-up FPL responses
├── supabase/migrations/ SQL that creates every table and its access rules
└── .github/workflows/ fetch.yml (every 3 hours), alerts.yml (every 15 minutes), ci.yml (tests + build)
Everything runs on free tiers: Vercel (web app), Supabase (database and sign-in), GitHub Actions (scheduled jobs).
Create a project at supabase.com (sign in with GitHub, pick a US East region).
Open SQL Editor → New query, paste all of supabase/migrations/20261003000000_init.sql, and click Run. Then do the same with every later file in supabase/migrations, in order (each one is a new query). Today that's 20261003000001_player_gameweeks.sql, 20261003000002_transfer_plans.sql, 20261003000003_scout_and_recaps.sql, 20261003000004_planner_drafts.sql, 20261003000005_recap_cards.sql and 20261005000000_schedule_via_supabase.sql (that last one needs a GitHub token; see step 5).
From Project Settings → API Keys (or the Connect button), note three values:
https://abcd1234.supabase.cosb_publishable_...): for the web app; safe to exposesb_secret_...): for the jobs only; never put it in the web app or in gitOlder projects show anon and service_role keys instead. They work the same way here, but Supabase is retiring them by the end of 2026.
Go to Authentication → Sign In / Providers → Email and turn off "Confirm email", then save. Supabase's built-in mailer only delivers to members of your Supabase team (2 emails an hour), so friends would never get a confirmation link.
Under Authentication → URL Configuration, set Site URL to your Vercel address, e.g. https://fpl-raptor.vercel.app.
SUPABASE_URLSUPABASE_SECRET_KEYplayers should have every PL player, and job_runs should show a row with status ok.From then on it runs every 3 hours on its own. To follow more leagues, add their ids to FPL_LEAGUE_IDS in .github/workflows/fetch.yml (comma-separated). A league id is the number in its standings URL.
NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY.Google sign-in (optional): configure the Google provider and callback URLs in Supabase/Google Cloud, then set GOOGLE_AUTH_ENABLED=true and APP_URL to this app's origin. Follow docs/AUTH_SETUP.md for setup, existing-account linking, and the checks required before rollout. Google sign-in helps new users find and confirm their team in followed leagues; it does not supply an FPL team ID. Email sign-in and the Points-link fallback remain available.
Pushes are signed with a VAPID key pair. The public half is in lib/pushConfig.ts; the private half is a secret that goes in two places:
VAPID_PRIVATE_KEY (same place as the Supabase secrets). The Send alerts workflow uses it.VAPID_PRIVATE_KEY with the same value, then redeploy. The Send a test notification button uses it.To make a new pair: npx web-push generate-vapid-keys. Put the private key in both places above and set NEXT_PUBLIC_VAPID_PUBLIC_KEY in Vercel to the new public key. Everyone then needs to turn notifications on again.
GitHub starts scheduled workflows late when it's busy, often by hours, so a "1 hour to the deadline" reminder can arrive after the deadline. Workflows started on demand aren't held back, so Supabase's scheduler starts them instead: alerts every 15 minutes, the fetch every 3 hours. GitHub's own schedules stay on as a backup.
Make a GitHub token. GitHub → your picture → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token. Name it FPL Raptor scheduler, set an expiry (up to a year), under Repository access choose Only select repositories and pick this repo, and under Permissions → Repository permissions set Actions to Read and write. Generate it and copy it (it starts github_pat_).
Run the SQL. In Supabase's SQL Editor, run supabase/migrations/20261005000000_schedule_via_supabase.sql. It turns on pg_cron and pg_net and adds the three scheduled jobs.
Store the token in Vault. In a new SQL Editor query, run this with your token between the quotes:
select vault.create_secret('github_pat_...', 'github_dispatch_token');
It's stored encrypted. Don't commit it or paste it anywhere else. To replace it later (when it expires): select vault.update_secret((select id from vault.secrets where name = 'github_dispatch_token'), 'new token');
To check it's working, look at Actions: runs started this way are labelled workflow_dispatch. Or, in the SQL Editor:
select jobname, status, return_message, start_time from cron.job_run_details
join cron.job using (jobid) order by start_time desc limit 5;
select status_code, content, created from net._http_response order by created desc limit 5;
status_code 204 means GitHub queued the run; 401 means the token is wrong or expired; 403 or 404 means it lacks Actions: Read and write on this repo.
/entry/ on your Points page) or the whole Points link.A new user's team is picked up by the next scheduled fetch. Until then, My gameweek loads their squad straight from FPL.
What gets sent (every 15 minutes, each alert once):
| Alert | When |
|---|---|
| Deadline reminder | Under 24 hours to the deadline, and again under 75 minutes. Lists flagged players in your team, your captain, the model's pick and the planner's transfer |
| Team news | A player in your squad gets flagged, their chance of playing or news changes, or they're available again |
| Price change | A player in your squad rises or falls in price |
| League captains | Within 48 hours of a deadline, once most of your league's squads are in: everyone's captain picks, yours, and your rival's |
| League awards | Once FPL confirms the gameweek's points: top score, wooden spoon, captain hero and fail, bench of shame, and where you finished |
| League recap | When the AI recap is written (after the gameweek's points are final): its headline and first lines |
Team news and prices come from the fetch, so they arrive within about 3 hours of FPL changing them.
You need Node.js 22 (LTS) and uv.
Web app
npm install
cp .env.example .env.local # then fill in the two values
npm test # unit tests, no network needed
npm run dev # http://localhost:3000
Jobs
cd jobs
uv sync
uv run pytest # tests, no network needed
cp .env.example .env # then fill in the values
uv run --env-file .env python -m raptor.run
Predicts every player's points for each of the next five gameweeks. Gradient-boosted trees (scikit-learn) trained on four seasons of FPL history (2022/23 to 2025/26, about 113,000 player-fixtures) from the public vaastav/Fantasy-Premier-League dataset, using recent form, xG/xA, minutes, bonus, defensive contributions, price, position, home/away, fixture difficulty and both teams' recent goals and xG.
In backtests on matches it never saw, it beats recent form and FPL's own pre-match xP on accuracy and on ranking players. jobs/model/REPORT.md has the numbers, how they were measured, and the limits.
Live: after every fetch, raptor.model.predict rebuilds this season's history from FPL's
live data, predicts the next five gameweeks, scales by FPL's chance of playing, and saves to
predictions. My gameweek shows each player's xP, the model's captain pick and your XI's
expected total; deadline reminders include the pick.
Retrain (e.g. each summer, once the dataset has the new season):
cd jobs
uv run python -m raptor.model.train # downloads history, backtests, saves jobs/model/
Commit the updated jobs/model/ files; the next fetch uses them.
After the predictions, raptor.optimizer.run plans transfers for every signed-up manager over
the next four gameweeks. It's an integer program (PuLP with the free CBC solver) that chooses,
for each week, the squad, the starting XI, the captain and the transfers, to maximise expected
points under FPL's rules:
Later weeks count a little less (×0.85 a week) because predictions get less reliable, and the bench counts for a tenth of its expected points as cover. Free transfers, bank and purchase prices come from your public FPL history and transfers. The same model with transfers switched off gives the "keeping your team" baseline; a plan is only suggested when it beats that by at least a point.
Because the model's predictions flatter the players it picks (it picks whoever it rates highest, so its errors lean upwards), every transfer must gain a point more than it costs, and a hit must gain 6 rather than 4. Both were tuned on a replay of a past season (see the backtest below).
Chips. chips.py works out which chips you still have from your FPL history and the chip
windows FPL publishes (since 2025/26, each chip once per half-season), re-solves the plan with each
chip in each planned week, and places them (one a week) only where the gain beats a bar: 18 expected
points for a Wildcard, 15 for a Free Hit or Bench Boost, 13.5 for a Triple Captain. Ordinary weeks
don't clear those; doubles and big swings do. A chip about to expire is played in its best week
rather than lost.
It considers your squad plus the best-predicted players in each position and some cheap budget picks (about 70), which solves in a second or two per plan. My gameweek shows the first week's move, Planner shows every week and the chip table, and deadline reminders include it.
Limits: it plans from your squad at the last deadline (FPL doesn't share transfers until the next deadline passes), and it is only as good as the model's predictions.
raptor.backtest.season replays a whole past season with a bot making every decision exactly as
the live app would: retrain the model on everything before each deadline, predict, plan transfers
and chips, make the moves, and score them with the real points (automatic subs, vice-captain,
chips, hits). Over 2025/26 it scored 2,107 (about rank 2.2 million), against 2,264 (rank
360,298) for JP Morgan: decent, not elite. jobs/backtest/REPORT.md has the details:
where it lost points, how the settings were tuned on 2024/25, and the limits (no injury news in the
history data, one season is a small sample).
cd jobs
uv run python -m raptor.backtest.season --season 2025-26 --tag tuned # about 10 minutes
uv run python -m raptor.backtest.report
The Season backtest data workflow (manual) fetches what the report compares against: a league's results last season, or a sample of (points, rank) pairs across the game.
Once a gameweek's points are final, raptor.recaps (after each fetch) works out the week's award
cards from the numbers (Top Dog, Rocket, Armband Hero and Disaster, Lone Wolf, Chip Watch, Bench
Warmer, Hit and Miss, Freefall, Wooden Spoon), gathers each followed
league's facts (scores, captains, hits, chips, benches, movement, players only one manager had) and
asks Google's Gemini for a headline, a one-line roast per card and a short write-up in the voice of
a football pundit. Every manager gets a cartoon raptor, the same one each week (drawn from their team id). The League page
shows it and the alerts job pushes it.
GEMINI_API_KEY
repository secret (same place as the Supabase secrets). Without it, recaps are skipped..github/workflows/fetch.yml).The chat on Scout (app/api/ask/route.ts) sends your question, with the recent turns of the
conversation, to Gemini along with a brief on your squad (each player's selling price and expected
points, bank, free transfers, chips left, the bot's plan). Gemini can call four tools that run on our
server over our own data: search_players, player_details, fixture_run and what_if (the Build
your own engine, so hits, free transfers, bank and club limits all count). It's told to quote only
numbers from those, and the app streams what it's doing ("Checking Saka, Palmer...") while it works.
GEMINI_API_KEY to Vercel (Settings → Environment Variables, all
environments) and redeploy. It's only read on the server; browsers never see it. Without it the
tab says Ask Raptor isn't switched on.The Live page calls FPL's event/{gw}/live/ and fixtures/?event={gw} from the web server (cached
for a minute) and works out what FPL adds later: provisional bonus (3/2/1 by BPS in every started
match whose bonus isn't confirmed, ties shared), automatic substitutions (first bench player who
has played, keeping a valid formation; goalkeepers only for goalkeepers) and the vice-captain
taking the armband when the captain can't play. Squads come from the database, or straight from FPL
if the fetch hasn't stored them yet.
| Table | What's in it | Written |
|---|---|---|
teams, players, gameweeks, fixtures | Current FPL state | Every run, overwritten |
player_changes | Price, status, news and chance-of-playing changes | Every run, only what changed |
leagues, league_members | Standings of followed leagues | Every run |
entry_gameweeks, picks | Each tracked manager's squad, captain, chip, points, bank | Current and previous gameweek, until FPL confirms final points |
player_gameweeks | Each player's points, minutes and bonus per gameweek | Current and previous gameweek, until FPL confirms final points |
predictions | Expected points per player for each of the next five gameweeks | After every fetch, by the model |
transfer_plans | Each user's four-week transfer and chip plan and the no-transfer baseline (only they can read it) | After every fetch, by the planner |
player_stats | Season and last-six-gameweek stats and the model's next 1 and 5 gameweeks, for Scout | After every fetch, by the prediction job |
recaps | Each league's recap per gameweek: headline, award cards with captions, write-up | Once per gameweek, after its points are final |
recap_reactions | Reactions to recap cards (one per person per card; only league members can react) | By the app, on the League page |
planner_drafts | Saved do-it-yourself plans: a list of moves (only the owner can see them) | By the app, from Build your own |
profiles | Each user's FPL team id and name | By the app, at onboarding |
notification_prefs, push_subscriptions | Which alerts each user wants; their devices | By the app, on the Notifications page |
notifications_sent | Every alert sent, so none goes out twice | By the alerts job |
Things worth knowing about FPL's data:
now_cost = 65 means £6.5m and bank = 3 means £0.3m. Use formatPrice in lib/fpl.ts (or format_price in jobs/raptor/fpl.py).a available, d doubtful, i injured, s suspended, u unavailable, n not in squad.jobs/raptor/fpl.py, so if the unofficial API changes, that's the one file to fix.Who can read what: football data and job status are readable by anyone with the publishable key. League standings and squads need a signed-in user. Each user can only see and change their own profile and alert settings. The jobs use the secret key, which bypasses these rules.
401 Invalid API key: check the SUPABASE_SECRET_KEY secret is the secret key, not the publishable one.relation ... does not exist: the SQL in supabase/migrations hasn't been run yet.supabase/migrations/20261003000001_player_gameweeks.sql in the SQL Editor. Captain awards appear after the next fetch.supabase/migrations/20261003000002_transfer_plans.sql in the SQL Editor. Plans appear after the next fetch.supabase/migrations/20261003000003_scout_and_recaps.sql in the SQL Editor.GEMINI_API_KEY secret is wrong.FPL_LEAGUE_IDS in .github/workflows/fetch.yml.skipped: FPL was down or mid-update (common around deadlines). The next run picks it up.VAPID_PRIVATE_KEY is set in Vercel (Setup, step 4), then redeploy after correcting it.VAPID_PRIVATE_KEY repository secret on GitHub.The five main tabs are Team, Live, Planner, Scout and League. Account contains team linking, sign-in options, notifications and install help.
The UI work and its evidence are tracked in docs/UI_REFACTOR_PLAN.md. The final feature inventory and release review maps preserved features to their destinations. Local browser checks use synthetic service stand-ins. Physical iPhone/PWA, real native sharing/push delivery and remaining provider/account-policy edge cases must be reviewed separately before release.
Run the app checks with:
npm run lint
npm test
npm run build
This development environment has a known Turbopack worker/socket permission failure. The production fallback used for local verification is npm run build -- --webpack; it includes TypeScript and page generation.
The manual planner already supports transfer, captain, swap and chip scenarios. Future model work could add injury news to backtests and reduce the optimism in selected players. Keep those changes separate from this UI refactor and validate them with the season replay.
job_runs | One row per fetch, prediction, plan, recap or alerts run, with status and a summary | Every run |