How everything is calculated
All the numbers you see on this site come from Flashscore / eredmenyek.com. Nothing is hand-typed (except the league tier table and the team colours). Below is a casual tour of every score and how the sausage gets made. Mostly so future-me knows what I did.
The five player metrics
Each player has the same five axes on their radar. Every metric is normalised so 50 is roughly average for a World Cup squad pool, and 100 is elite. You'll see them as Form Play Exp Level Quality everywhere.
Form — what shape are they in right now
Take their last 35 club + national-team matches and average the Flashscore rating, but weighted toward the recent end —
the most recent 5 games count 1.00×, the next 10 count 0.70×, the prior 20 count 0.45×.
That way a hot streak shows quickly without an injury layoff wrecking a long-term star's score.
Unrated or missed games count as a flat 5.5 (a mild penalty so a benched player can't have a great form score on three good games).
The rating range 4.8–8.0 maps to 0–100; so a 6.4 average rating ≈ 50.
Playing time — are they actually getting minutes
Look at the last 90 days. For every game on their list, add min(minutes / 90, 1) — a full 90 counts 1.0,
a 60-minute sub-off counts 0.67, a 10-minute cameo counts 0.11, an absence counts 0. Sum it all up and divide by 18
game-equivalents (≈ a heavy two-games-per-week month). So a regular starter who finishes most matches hits ~80,
and Kimmich-types who never come off can cap at 100.
Experience — age + caps
Half of it is age (linear from 18 → 0 to 40 → 100), the other half is national-team caps capped at 60 (so 60 caps already gets you the full 50 "caps half"). Modric/Neuer/Ronaldo type 40-year-old legends with 100+ caps land at 100. 18-year-old debutants get single digits.
Level — how strong is the league they currently play in
80% current league tier + 20% historical top-5 appearances. Tiers are hand-tuned in
scraper/metrics.py — Premier League and LaLiga are 1.00 / 0.97, Bundesliga / Serie A 0.93, Ligue 1 0.90,
Eredivisie / Liga Portugal / Brasileirão 0.78, MLS 0.50, K-League 0.50, Saudi Pro League 0.50, smaller European top
flights 0.40-0.55, etc. The historical part means a Messi-at-Inter-Miami still ranks above a journeyman MLSer
because of his 500+ LaLiga apps, but he's nowhere near 100 either because MLS is what's actually happening today.
Quality — Transfermarkt value, adjusted for age and position
Start with market value divided by years-to-40 (so older players' MVs aren't watered down by a long career runway — a 41-year-old gets divided by 2 instead of 1, otherwise Ronaldo would silly-cap at 100). Take the log10, then run it through a three-segment piecewise scale:
- log10 = 2.5 → 0 (the floor — €1K/year-of-career)
- log10 = 3.5 → 2
- log10 = 4.5 → 15 (the kink — used to be where "0" sat)
- log10 = 6.9 → 100 (top anchor)
The bottom segment is intentionally shallow so very-low-MV players (smaller-league journeymen, low-tier youth) get single-digit scores instead of all collapsing to 0 — that helps the club squads pages look like something other than a sea of zeros.
The top is unclipped on purpose. Mbappé / Haaland / Saliba and a handful of others land at 105–110, and that's the point — it separates the genuinely elite from the merely top-tier. The radar chart's r-axis still maxes at 100, so they visually spill over the edge, which is the intended cue.
A position bonus is then added because top GKs cap at ~€20M in market value while top forwards reach €200M+ — that asymmetry would otherwise bury keepers and defenders:
- Goalkeeper: +18 (full)
- Defender: +10 (full)
- Midfielder: 0
- Forward: 0
The bonus scales with the base score — full at base ≥ 30, dropping off as (base/30)2 below
that. So an elite WC keeper at base 88 gets the full +18, but a Latvian club GK at base 3.6 only gets ~+0.3 instead
of being inflated to ~22. Without that scaling, every club GK/DEF would inherit the full bonus and stop looking
sub-tier.
Players with no listed market value used to be hard-zeroed; they're now floored at €1 so the formula still computes and they land around 1, which keeps them out of the way in rankings without wrecking their Overall.
Overall
Weighted average of the five metrics — Quality counts triple, the others count once each. (I tested it without the triple weight and the rankings just looked off — Quality is by far the strongest single signal of who is actually good, so giving it more weight made the top of the list look like the top of the list.)
That raw weighted mean is then stretched with a one-sided lift: anything below ~61 passes through unchanged, anything above gets pulled up toward 100. So Messi reaches 97-100, but a fringe 18-year-old still scores ~12 instead of 0. The point is to use the full 0–100 range on the high end while not crushing the bottom.
Team scores
For each team there are two views — Full Squad (every scraped player, averaged) and Best XI (an algorithm picks the 11 highest-Quality players who fit one of five candidate formations — 4-4-2, 4-3-3, 4-5-1, 3-4-3 or 3-5-2 — and uses those). Whichever formation produces the highest total Quality from the squad wins, so teams short on central defenders naturally end up in a 3-back shape and teams with a forward glut end up in a 4-3-3 or 3-4-3. Same five metrics, just averaged across the chosen eleven.
The zone overalls (GK / DEF / MID / OFF) on team and match pages are the same average but split by position bucket. So you can read "Argentina's offence is a 95 but their defence is a 78" at a glance.
Coach scores
Flashscore doesn't store a managerial career, so the coach radar has only two axes:
- Experience — age proxy: a 25-year-old scores 0, a 60-year-old scores 100.
- Level of competition — hand-tuned by national team (top contenders ~0.95, smaller pools ~0.35).
Match comparison
On each match page, the big middle radar overlays both teams (home in their national colour, away in theirs).
Below it are the per-axis chips — the winning side's value is tinted (red > blue when home wins,
red < blue when away wins, = on a tie).
The pitch tiles on either side show each team's zone overalls so you can spot where the actual mismatch is.
Match data
- Before kickoff: if no lineup is published yet, both teams' full squads are listed below the form section.
- From ~1 hour before kickoff: the lineup feed appears and you'll see the actual Starting XI, bench and missing.
- After full-time: minute-by-minute ratings, goals, assists, cards and substitutions are pulled in.
Refresh schedule
A Windows Scheduled Task runs every hour, 24/7, while the WC is on. Each hour it checks for any group-stage match within −60 minutes of kickoff or +150 minutes after. If anything qualifies it scrapes what changed, rebuilds the site and pushes to GitHub. GitHub Actions then redeploys to GitHub Pages — usually within a minute.
What's not auto-calculated
- League tier weights — hand-tuned in
scraper/metrics.py. PRs welcome. - National-team colours — picked from FIFA crests / shirts. Yellows (Brazil, Ecuador, Colombia) were swapped for darker secondaries because pure yellow lines disappear on white radars.
- Coach's Level of competition — hand-tuned weight per national team, sitting in
scraper/metrics.pyright next to the league table.
Source code
Everything's on GitHub. The scoring logic is in
scraper/metrics.py; the templates are in builder/templates/; the scraper is polite
(2.5–4 s between network requests, hard abort on 429/403, every URL cached forever locally).