surfsync
서핑 학교가 지역·레벨·요일별 강사를 찾아 수업을 요청하고, 강사는 수강 여부를 결정
저장소 정보는 아직 수집하지 않았습니다.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Fantasy Premier League squad checker and optimiser: point predictions, transfer and captain suggestions, and a data-driven optimal squad builder.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A Fantasy Premier League (FPL) squad checker and optimiser. Load your squad, see point predictions for every player, get transfer and captain suggestions, or generate a data-driven "optimal" squad for any gameweek.
Live app: https://fplchecker.co.uk
/api/as-of sums FPL's per-gameweek stats for the earlier weeks; prices and set-piece takers stay at today's values, which FPL doesn't publish historically).Each player's predicted points blend several signals, all in src/lib/predictions.js:
For squad-building decisions, values are shrunk toward the position average to counter the "winner's curse" (picking players whose predictions are inflated by noise).
The prediction logic is a set of pure functions shared by the browser and the server cron job, so the two can never disagree.
api/public/tesseract/ (copied there by scripts/copy-tesseract.mjs before dev/build)api/
auth.js Login, signup, session (rate-limited)
teams.js Saved squads for logged-in users
fpl.js Cached proxy for the FPL endpoints the app uses (allow-listed)
optimal-squad.js Serves the latest optimal-squad snapshot
refresh-optimal.js Cron job: builds and saves the snapshot and this gameweek's predictions (needs CRON_SECRET)
accuracy.js Last gameweek's predictions vs. actual points
as-of.js Player stats from before a past gameweek's deadline
screenshot-report.js Stores opt-in screenshot reports in Blob
odds.js Serves cached bookmaker odds
player-history.js Serves imported historical player data
src/
App.jsx App shell: state, data loading, which screen is shown
styles.css App-wide styles
screens/ One file per screen (intro, screenshot import, results, squad builder, hindsight, accounts)
components/common.jsx Header, loading/error states, player search, fixture chips
lib/
predictions.js Prediction and squad-building logic (shared with the server)
screenshotOcr.js Screenshot OCR, player/club/armband detection
squadLogic.js Formations, captaincy, transfer suggestions, chip timing
format.js Name matching and number formatting
fplClient.js Browser-side data loading
accuracy.js, calendar.js, shareCard.js, fplProxy.js, rateLimit.js
playerHistory.js, oddsAdjustment.js, auth.js, redis.js, teams.js
test/ node:test suites (npm test) and fixtures, incl. a real screenshot's OCR output
scripts/
import-player-history.mjs One-time import of 10 seasons of history into Blob
calibrate-weights.mjs Grid-search of formula weights via walk-forward backtest
copy-tesseract.mjs Copies the OCR engine into public/tesseract (runs before dev/build)
download-screenshot-reports.mjs Downloads screenshots people reported as misread
.github/workflows/
ci.yml Lint, test and build on every pull request
refresh-after-deploy.yml Rebuilds the optimal squad after each push to main
npm install
npm run dev # local app (the /api functions need `vercel dev` and env vars)
npm test # unit tests: screenshot reading, predictions, login limits, proxy rules, …
npm run lint
npm run build
test/fixtures/README.md explains how to turn a screenshot that was read wrongly into a regression test.
Install and run locally
npm install
npm run dev
Deploy to Vercel and add these environment variables:
| Variable | Purpose |
|---|---|
BLOB_READ_WRITE_TOKEN | Injected automatically when you create a Vercel Blob store |
CRON_SECRET | Bearer token protecting /api/refresh-optimal (Vercel generates one for the cron) |
REDIS_URL | Redis connection string for accounts and saved squads |
JWT_SECRET | Secret used to sign session cookies |
ODDS_API_KEY | Optional. Free key from the-odds-api.com; without it the odds adjustment is skipped |
| SMTP_USER | Optional. Address that sends password-reset emails, e.g. a Gmail address. Without it and SMTP_PASS, "Forgot password?" says resets aren't set up |
| SMTP_PASS | Optional. For Gmail, an app password (needs 2-Step Verification), not the account password |
| SMTP_HOST / SMTP_PORT | Optional. Default smtp.gmail.com / 465; set these to use another email provider |
| EMAIL_FROM | Optional. From line on emails; default FPL Checker <SMTP_USER> |
| APP_URL | Optional. Site address used in reset links; default https://fplchecker.co.uk |
| VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY | Optional. Turn on alerts pushed with the app closed; make a pair with npx web-push generate-vapid-keys. Sent every 20 minutes by .github/workflows/transfer-snapshots.yml (needs the CRON_SECRET repository secret). Without them, notifications come only while the app is open |
| BLUESKY_HANDLE / BLUESKY_APP_PASSWORD | Optional. Turn on the Bluesky bot (src/lib/social.js): up to four posts a gameweek (last gameweek's result, a differential, the preview, captain picks), each linking to the site. Use an app password (Bluesky: Settings, Privacy and security, App passwords), not the account password. Sent from .github/workflows/transfer-snapshots.yml (needs CRON_SECRET) |
| SOCIAL_DRAFTS_EMAIL | Optional. Where the same posts are emailed as X drafts, with a button that opens X with the post filled in (X's API charges per post; its share link is free). Needs the SMTP_* settings. The admin page's Social tab lists them too, and has a box for one-off posts (also the Social post workflow in the Actions tab) |
Guide pages for search engines: /fpl-points-predictor, /fpl-captain-picks, /fpl-transfer-suggestions and /rate-my-fpl-team are plain HTML written to public/ by node scripts/make-seo-pages.mjs from src/lib/seoPage.js (which also writes sitemap.xml, sitemap-pages.xml and copies the fonts to public/fonts/). The pages made from live data are rendered on request by api/optimal-squad.js?page=… (see the rewrites in vercel.json) from src/lib/seoDynamic.js: /fpl-predictions (this gameweek), /fpl-players (every player ranked), /player/<name>, /team/<club>, /gw/<n> (past gameweeks with what was scored, and the next four), /fpl-tips (this gameweek's tips written out from the predictions), /embed/captains (the captain-picks widget other sites embed, see /fpl-widget), /league/<id> (a mini-league's standings with captains and predicted points, shared from the app; not indexed), /images/best-xi.png (the best XI as a picture, drawn by src/lib/socialImage.js with satori and resvg, used as the predictions and tips pages' preview image and attached to the Bluesky preview post), and /sitemap-fpl.xml, which lists them. They read the predictions the refresh job saves (predictions-gw<N>.json, with the next five gameweeks in byGwById).
node scripts/import-player-history.mjs to upload past seasons to Blob. Without it, predictions fall back to the position-average baseline.The optimal squad is computed once, server-side, and shared by every visitor via Vercel Blob rather than being recomputed in each browser.
api/refresh-optimal.js fetches live FPL data, builds the optimal squad, and saves a small JSON snapshot to Blob. It also fetches and caches bookmaker odds.api/optimal-squad.js is what the frontend reads. It returns 404 if nothing has been built yet, and the app falls back to building locally.vercel.json refreshes the snapshot at 06:00 UTC (Vercel's Hobby plan only allows once-daily crons)..github/workflows/refresh-after-deploy.yml also triggers a refresh about 90 seconds after every push to main. It needs a CRON_SECRET repository secret in GitHub Actions, matching the value in Vercel.Everything happens in the browser; the screenshot never leaves the device.
/api/fpl only forwards the FPL endpoints the app uses, and caches successful responses at Vercel's edge (5 minutes for player and fixture data), so most visitors never wait on FPL's servers.vercel.json sets a Content-Security-Policy (with 'wasm-unsafe-eval' so the OCR engine's WebAssembly can run), X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Permissions-Policy on every route.