Write-up · 19 of 22 sections written · revised 3 Sep 2026
Gaffer IQ.The write-up.
A Fantasy Premier League tool that stops treating fixture difficulty as a property of a team, and starts treating it as a property of a pairing.
- Role
- Design, engine, code
- Stack
- Vanilla JS, ES modules
- Backend
- One Vercel function
- Status
- Live, iterating
In short
Five views over one scoring engine – Matchups, Fixtures, Ranker, Dashboard and Planner – replacing FPL’s 1–5 difficulty rating with a weighted composite of five sub-metrics, scored relative to the opponent so both sides of a fixture sum to 100.
Contents
Start anywhere. Each section stands on its own.
Overview
1 sectionDesign philosophy
4 sections- 02ApproachPull as much data as possible, process it into a new perspective, and keep the platform modular enough that the data can be parsed globally.
- 03The dataTwo sources, one serverless function, and everything else running in the browser – plus a frank list of what the FPL API does not give you.
- 04The engineAll the number-crunching in one place, the settings that control it in another, and a confidence level attached to every output.
- 05The interfaceSplitting dense data so it does not overwhelm, and offering more than one route to the same information.
Matchups
5 sections- 06Idea and approachThe front page – a comprehensive view of the matchups ahead, with the deep dive on any single fixture sitting inside it.
- 07DesignWhy the page ended up graphical rather than number-heavy – several containers, each answering a different question about the season ahead.
- 08The toolsThe four areas the page is built from – the central breakdown, the two side columns, and the season strip along the bottom.
- 09Scoring philosophyNot “is this fixture hard” but “is this fixture hard for this player, doing this job, right now” – and how much to believe the answer.
- 10CalculationsFive weighted sub-metrics, each with its own maturity curve, combined into one CompositeScore and mapped to five colour bands.
Fixtures
6 sections- 11Idea and approachTrue historical data, exactly as fetched – a manual, less-processed way to read the game for anyone who doesn’t fully trust the engine’s guessing.
- 12DesignSubtabs over one long page, and numbers over graphics – a deliberate contrast with the Matchups page this section pairs against.
- 13GameweekMinimal before kickoff, then a full graphical breakdown once the match has been played.
- 14TableThe least functional page on the site, and still worth including – a straight pull from Understat, sorted like the real thing.
- 15By teamOne club’s wins, draws and losses across the season so far, and what’s still to come – styled to match, but on par with official sources.
- 16Head-to-headWins, draws and losses for one pairing, and the patterns that surface in them – the same record that feeds the H2H sub-metric on Matchups.
Ranker
2 sections- 17Idea and approachThe bridge between team-level fixture scores and player-level decisions.
- 18DesignHow the ranked list is laid out, and why every column sorts on the value it actually shows.Planned
Dashboard
1 sectionPlanner
3 sections- 20Idea and approachThe most horizon-aware view: every candidate transfer evaluated for its change in projected score, with the four-point hit modelled rather than ignored.
- 21DesignHow a ranked list of transfers is presented so the trade-off in each one stays visible.Planned
- 22EngineWhat the Planner actually computes, and how a four-point hit is priced rather than ignored.Planned
Planned sections aren’t written yet. They’re listed so you can see what’s coming.
Glossary
The terms the write-up leans on.
- App shapeStatic
- The app’s files (HTML/CSS/JS) are served exactly as written, with nothing generated per-request.
- Explained in Approach
- App shapeFrontend-built
- Nearly all the logic runs in the browser, not on a server. This is the app’s core design choice.
- Explained in Approach
- App shapeSingle-page application (SPA)
- One HTML page, JavaScript swaps content in and out instead of loading new pages.
- Explained in The interface
- Build approachVanilla JS
- No framework (no React/Vue). Plain JavaScript, run as-is.
- Explained in Approach
- Build approachES modules
- The browser-native way files import each other (import/export), no bundler needed.
- Explained in Approach
- Build approachNo build step
- The code that runs is exactly the code you wrote, nothing compiles or transforms it first.
- Explained in Approach
- ServersCORS
- The browser security rule that blocks a webpage from calling a different site’s API directly. It’s why the proxy exists.
- Explained in The data
- ServersServerless
- Backend code that runs on-demand in short bursts, with no server you keep running yourself. The proxy is this.
- Explained in The data
- ServersProxy
- The serverless function’s job: forward requests, don’t process them.
- Explained in The data
- ServersAllowlist
- The proxy only forwards requests matching pre-approved URL patterns, rejecting everything else.
- Explained in The data
- RuntimeClient-side rendering
- The browser builds and updates the page with JavaScript, rather than receiving a finished page from a server.
- Explained in The interface
- RuntimePure functions
- The scoring engine’s code style: same input always gives same output, no side effects.
- Explained in The engine
- RuntimeState management / pub-sub
- How the app tracks data and notifies its parts when new data arrives.
- Explained in The engine
- RuntimeCaching / TTL
- Storing fetched data temporarily instead of re-fetching, with an expiry rule for staleness.
- Explained in The data
- Design problemData density
- There is more data here than a page can show at once. The interface answers it with clear containers, colour, spacing and separated views rather than by showing less.
- Explained in The interface
- ArchitectureModular
- Data is parsed once and made available globally across the site rather than re-fetched per view, which is what allows a new view to be added without a new data path.
- Explained in Approach
- SourceFPL API
- Supplies the spine – squads, prices, positions, fixtures, per-player histories and live gameweek scoring. It has no standings endpoint and no access to any season but the current one.
- Explained in The data
- SourceUnderstat
- Not an API, but it fills the gaps the FPL feed leaves: expected goals, six seasons of fixture lists, chronological match timelines, and rosters with position codes and substitution linkage.
- Explained in The data
- BackendOne Vercel function
- The single serverless function exists only because the FPL API sends no CORS headers. It fetches from an allowlist and forwards. It holds no state, no keys, and no logic of its own.
- Explained in The data
- ModelCompositeScore
- The single number every fixture is reduced to. Five weighted sub-metrics are scored separately and combined into it, and it is what drives the colour band shown anywhere in the app.
- Explained in Calculations
- Sub-metricCounter-matchup
- The heaviest calculation. Rather than asking how strong a team is, it asks where each side is strongest and weakest, and whether those shapes counter each other. Matures over 10 fixtures to a maximum 0.25 weight.
- Explained in Calculations
- RegionMatchup breakdown
- The central element of the Matchups page – the deep dive on one fixture, showing decision weight, confidence, playstyle counters and the overall prediction score.
- Explained in The tools
- RegionFull season strip
- The bottom strip of the Matchups page, and the view with the longest horizon. It marks the gameweeks worth planning around rather than every fixture equally.
- Explained in The tools
- FPL termDouble gameweeks
- A gameweek in which a club plays twice, usually because an earlier fixture was postponed and rescheduled into it. Highlighted on the season strip because it changes captaincy and chip decisions.
- Explained in The tools
- Sub-metricHead-to-head
- The real cross-season record between two clubs. Deliberately capped at a 0.1 weight – testing showed anything larger made predictions worse, because a rivalry from three managers ago is weak evidence.
- Explained in Head-to-head
- Sub-metricBase difficulty
- FPL’s own 1–5 rating, rescaled to 10/30/50/70/90 and used only as a starting point. It is preseason-seeded, which is why team form exists to correct it in-season.
- Explained in Calculations
- Sub-metricTeam form
- Recent trajectory, adjusted for the strength of the opponents it was earned against. Matures over 5 fixtures to a maximum 0.2 weight, so two good games in August cannot read as perfect form.
- Explained in Calculations
- Sub-metricHome and away
- How much venue matters to each side, measured as their home-versus-away points gap relative to the rest of the league, then applied as a boost to the home team and an equal penalty to the away team.
- Explained in Calculations
- ControlMaturity curve
- A per-sub-metric ramp that holds a signal’s weight down until enough fixtures have been played to trust it, rising linearly to its cap. It is what stops wild early-season data driving the score.
- Explained in Calculations
- ControlStacking penalty
- Applied across the sub-metrics so several weak secondary signals can tip a fixture without any single one doing it alone. It and the base-difficulty weight are a matched pair; neither is tuned alone.
- Explained in Calculations
- PatternSubtabs
- The Fixtures page is split into four sub-views rather than one long scroll, chosen because the volume of historical statistics would otherwise bury whatever the reader came looking for.
- Explained in Design
- DataStarting XI
- A real teamsheet with formation, derived from Understat’s rosters via their position codes and substitution linkage. The FPL API exposes no teamsheet of any kind.
- Explained in Gameweek
Other write-upFC26 Feel CalculatorThe card says 87 pace. The player does not feel like 87 pace. This works out the difference.
→