How this build was made, and what is wrong with it

Everything on this page is read from build.json, written by the pipeline at build time. Nothing here is typed by hand.

commit 408448bdc7 · generated 2026-09-10T19:17:54Z · 5,146 players

Archive era — FBref

FBref, via the frozen worldfootballR archive (data/fb/*.rds) · 2017/18 – 2023/24 · 13,828 player-seasons
Central midfield 2,968Wide forward / AM 2,924Centre-back 2,672Full-back 2,028Striker / CF 1,870Goalkeeper 960

FBref removed every xG and StatsBomb-derived statistic site-wide in 2026. These eight .rds files are the only surviving copy and cannot be re-derived.

Live era — FotMob

FotMob player API (data/fm/all/*.csv) · 2020/21 – 2025/26 · 9,579 player-seasons
Wide forward / AM 2,184Central midfield 2,078Centre-back 2,021Full-back 1,365Striker / CF 1,228Goalkeeper 703

Coverage gate

median 90s ratio vs standard table >= 0.90 over players with >=10 90s, every table
7 seasons shipped
SeasonPlayers ≥10 90sWorst table ratioShipped?
2009/101,5980.00blocked
2010/111,5720.00blocked
2011/121,6030.00blocked
2012/131,6070.00blocked
2013/141,6080.00blocked
2014/151,6280.00blocked
2015/161,6130.00blocked
2016/171,6140.00blocked
2017/181,6261.00ship
2018/191,6031.00ship
2019/201,5561.00ship
2020/211,6351.00ship
2021/221,6411.00ship
2022/231,6311.00ship
2023/241,6071.00ship
2024/251,5730.20blocked

The gate is data, not a comment. 2010/11–2016/17 are blocked because the advanced tables do not exist for them; 2024/25 is blocked at 0.20 because FBref's possession, defence and misc assets were frozen on 2024-10-17, eleven months before the rest of the archive — roughly a fifth of a season. Shipping it would have reproduced the error this gate exists to prevent.

Identity bridge — match rate per source per league-season

SPEC §5.13 bands: <0.70 out_of_scope | 0.70-0.85 flagged | >0.85 normal
SourceSource idsMatched to TransfermarktRateWorst league-season
fbref4,6514,47896.3%La Liga 2023 — 92.6% normal
fotmob3,6803,67599.9%Ligue 1 2020 — 99.1% normal
understat8,1177,54693.0%La Liga 2018 — 90.0% normal
Every league-season (115 rows)
SourceLeagueSeasonnMatchedRateBand
fbrefLa Liga201741438893.7%normal
fbrefLa Liga201840838393.9%normal
fbrefLa Liga201940338194.5%normal
fbrefLa Liga202042739793.0%normal
fbrefLa Liga202141939093.1%normal
fbrefLa Liga202241839694.7%normal
fbrefLa Liga202342139092.6%normal
fbrefLigue 1201739739098.2%normal
fbrefLigue 1201839638697.5%normal
fbrefLigue 1201936936298.1%normal
fbrefLigue 1202040539497.3%normal
fbrefLigue 1202141640797.8%normal
fbrefLigue 1202241240498.1%normal
fbrefLigue 1202335534196.1%normal
fbrefPremier League201738638098.4%normal
fbrefPremier League201839338698.2%normal
fbrefPremier League201939539199.0%normal
fbrefPremier League202039839499.0%normal
fbrefPremier League202139638697.5%normal
fbrefPremier League202240339497.8%normal
fbrefPremier League202340239197.3%normal
fbrefSerie A201740539697.8%normal
fbrefSerie A201840639898.0%normal
fbrefSerie A201941740897.8%normal
fbrefSerie A202041340497.8%normal
fbrefSerie A202142841897.7%normal
fbrefSerie A202241941198.1%normal
fbrefSerie A202342542199.1%normal
fbrefBundesliga201734634499.4%normal
fbrefBundesliga201835734997.8%normal
fbrefBundesliga201935935097.5%normal
fbrefBundesliga202035734797.2%normal
fbrefBundesliga202135133896.3%normal
fbrefBundesliga202235934896.9%normal
fbrefBundesliga202335334497.5%normal
fotmobLa Liga2020340340100.0%normal
fotmobLa Liga202134334299.7%normal
fotmobLa Liga2022359359100.0%normal
fotmobLa Liga202334134099.7%normal
fotmobLa Liga202434133999.4%normal
fotmobLa Liga202530330299.7%normal
fotmobLigue 1202033633399.1%normal
fotmobLigue 1202133933899.7%normal
fotmobLigue 1202232732699.7%normal
fotmobLigue 1202329229199.7%normal
fotmobLigue 1202428828799.7%normal
fotmobLigue 12025277277100.0%normal
fotmobPremier League2020328328100.0%normal
fotmobPremier League202134134099.7%normal
fotmobPremier League2022331331100.0%normal
fotmobPremier League2023339339100.0%normal
fotmobPremier League2024321321100.0%normal
fotmobPremier League202534033999.7%normal
fotmobSerie A2020341341100.0%normal
fotmobSerie A2021335335100.0%normal
fotmobSerie A2022337337100.0%normal
fotmobSerie A2023343343100.0%normal
fotmobSerie A2024345345100.0%normal
fotmobSerie A2025337337100.0%normal
fotmobBundesliga2020286286100.0%normal
fotmobBundesliga2021277277100.0%normal
fotmobBundesliga2022279279100.0%normal
fotmobBundesliga2023294294100.0%normal
fotmobBundesliga2024278278100.0%normal
fotmobBundesliga202528628599.7%normal
understatLa Liga201755750891.2%normal
understatLa Liga201853147890.0%normal
understatLa Liga201955650490.6%normal
understatLa Liga202057051890.9%normal
understatLa Liga202160355592.0%normal
understatLa Liga202258454292.8%normal
understatLa Liga202359855392.5%normal
understatLa Liga202459054492.2%normal
understatLa Liga202560056193.5%normal
understatLa Liga202638200.0%no TM pool yet
understatLigue 1201754052096.3%normal
understatLigue 1201854852796.2%normal
understatLigue 1201953151296.4%normal
understatLigue 1202057555596.5%normal
understatLigue 1202158957196.9%normal
understatLigue 1202258557097.4%normal
understatLigue 1202352550496.0%normal
understatLigue 1202454251695.2%normal
understatLigue 1202555352494.8%normal
understatLigue 1202635700.0%no TM pool yet
understatPremier League201751550497.9%normal
understatPremier League201850549698.2%normal
understatPremier League201951549996.9%normal
understatPremier League202052450896.9%normal
understatPremier League202153752297.2%normal
understatPremier League202255454398.0%normal
understatPremier League202357055697.5%normal
understatPremier League202456254797.3%normal
understatPremier League202553751696.1%normal
understatPremier League202635800.0%no TM pool yet
understatSerie A201753352598.5%normal
understatSerie A201854353498.3%normal
understatSerie A201957256097.9%normal
understatSerie A202059457897.3%normal
understatSerie A202160859497.7%normal
understatSerie A202257756497.7%normal
understatSerie A202359057797.8%normal
understatSerie A202459958697.8%normal
understatSerie A202558657197.4%normal
understatSerie A202636100.0%no TM pool yet
understatBundesliga201747246899.2%normal
understatBundesliga201846846298.7%normal
understatBundesliga201948747898.2%normal
understatBundesliga202049648497.6%normal
understatBundesliga202151150198.0%normal
understatBundesliga202250649698.0%normal
understatBundesliga202349448197.4%normal
understatBundesliga202448147498.5%normal
understatBundesliga202549949098.2%normal
understatBundesliga202628500.0%no TM pool yet

Unmatched rows are kept, never dropped — a player can exist in one era only. A player-season with no Transfermarkt lineup match has no position family and therefore no radar; it still appears in search and keeps its own source's numbers.

Marcel — walk-forward, on seasons that have already happened

Understat big-five snapshots · Marcel 2/1/1 exposure weighting · regression constant 480 minutes · Caley age curves
descriptive only
Target seasonnMarcel RMSE&ldquo;Last season&rdquo; RMSEPositional-mean RMSE
2015/161,3760.07280.07720.0985
2016/171,3590.06960.07650.1009
2017/181,3680.06890.08120.0972
2018/191,4180.07160.07990.1
2019/201,3760.07310.07990.1033
2020/211,4290.07020.08210.1
2021/221,4240.0710.0780.0989
2022/231,3920.07220.08360.0995
2023/241,3540.08830.09120.1163
2024/251,3240.08620.09630.1149
2025/261,3450.07650.0910.1073
overall15,1650.07470.08350.1034

descriptive (Marcel); P-1 found no out-of-sample edge over the market — see the P-1 page

Acceptance checks (SPEC-UI §8)

Run by scripts/site/acceptance.py against this build. Reported whether they pass or fail.
CheckResultVerdict
Coverage gatemedian 90s ratio vs standard table >= 0.90 over players with >=10 90s, every table7 seasons shipped (2017/18–2023/24), 9 blockedpass
Match rate per source per league-seasonSPEC §5.13: <70% out of scope, 70–85% flagged, >85% normal. Every league-season is listed on the build page; none is silently below the floor.115 league-seasons · 0 below 70% · 0 flaggedpass
Size budgetSPEC-UI §2: index.json < 1.5 MB, each player file < 60 KB.index.json 0.69 MB · largest player file 48 KB (median 13 KB over 5,146)pass
Radar percentiles vs FotMob's own percentileRankSPEC-UI §8 asks for ±3 points on a 200-player sample; this runs the whole live era (82,150 player-axis pairs, >=900 min). This build ships big-5 pooled populations, MAE 9.67; the other scope scores 8.12. FotMob computes its percentiles per league-season inside its own position labels, so a pooled build cannot match it exactly by construction and the closer number is not the better choice — the residual is a population-definition difference, not an arithmetic one. Where the two populations do coincide the agreement is close: for centre-backs, whose FotMob label maps one-to-one onto ours, 8 of 11 axes land under 5 points (median 4.2, best 3.0). For wide forwards it is 12-20, because FotMob ranks wingers and attacking midfielders separately and this site pools them into one family. FotMob also inverts the sign on stats where fewer is better (fouls, dispossessions); that is detected per axis from the correlation and corrected before comparing, which alone moved MAE from 13.0 to 8.1.MAE 9.67 pts · median 6.38 · 26% within ±3 · 68% within ±10reported
Archive comps vs the published arch2.R list (Vlahović 2023/24)arch2.R pools every FBref "FW" player-season into ONE space (497 of them, strikers and wingers together) over sim2.R's 12-metric set. The site follows SPEC-UI §3 instead: separate CF and wide templates, separate populations (198 strikers in the big five, pooled that season). Different space, so an exact match is not the correct expectation — the overlap is the finding. arch2.R: Victor Osimhen, Luis Javier Suárez, Folarin Balogun, Takuma Asano, Isaac Bernal. Site: Luis Javier Suárez, Victor Osimhen, Akor Adams, Cyle Larin, Maximilian Beier.5/10 of the top ten sharedreported
Live comps vs the published FotMob list (Kolo Muani 2025/26)scripts/fm/chart_data.R pools every FotMob attacker-season into one space over the FotMob style set. The site uses the CF template inside the big five, pooled (200 strikers). Published: Lyle Foster, Dominic Solanke, Nick Woltemade, Dan Ndoye, Jarrod Bowen. Site: Mathys Tel, Georginio Rutter, Dominic Solanke, Lukas Nmecha, Brian Brobbey.2/10 of the top ten sharedreported
Profile assignments vs the committed taxonomy CSVSPEC-UI §8: the site's archive attacker profiles must match experiments/attacker-taxonomy-2018-2024.csv row for row. They are read from it, not re-derived, so any mismatch would be a join fault. Rows not reachable on the site are player-seasons whose Transfermarkt bridge failed, which have no radar and no profile.3,139/3,533 matched (88.8%); 394 not reachable on the sitepass

Known limitations of this build, stated up front

  • Team context is not residualised. Every similarity distance and every radar percentile carries the player's team into it. A striker in a possession-heavy side looks different from the same striker in a counter-attacking one. This is the first post-launch fix.
  • Defensive axes are not possession-adjusted. Tackles, interceptions and pressing are raw per 90. A possession proxy (the player's on-pitch team xG share) is computed and stored for the archive era but is not applied, because applying it would also break the requirement that these comps reproduce the published archive figures.
  • The archive era has no goalkeeper data at all. FBref's keeper tables were never in the archived release and cannot be re-scraped.
  • xGBuildup is not on any radar. It is an Understat metric with its own population, and no chart here mixes two sources. It appears in the projection block under an Understat stamp instead.
  • Market value is the latest value, not the season's value, wherever it appears next to a comp row.
  • Feeder leagues are not in this build. Both eras are big-5 only. Feeder-league players will be added in the live era with their own league-group populations, never against big-5 ones.

Input fingerprints

SHA-256 prefixes of every file this build read.
FileSHA-256 (16)
data/fb/big5_player_defense.rds8bc9fd8218c1dc07
data/fb/big5_player_misc.rds744708cd039a2154
data/fb/big5_player_passing.rds886126b93b1c3b48
data/fb/big5_player_passing_types.rds9a3914051fba1c0e
data/fb/big5_player_playing_time.rds11f2e3eed64e2fd1
data/fb/big5_player_possession.rdsa41db96bcf4376dc
data/fb/big5_player_shooting.rds1bac446d04a7e992
data/fb/big5_player_standard.rdsa299c971c5f1a1a5
data/fb/taxonomy.rds34654420b8965c71
data/fm/all/fotmob_all_2020-2021.csv9c5ba4f969ff0747
data/fm/all/fotmob_all_2021-2022.csvbb96b57b2dfc1221
data/fm/all/fotmob_all_2022-2023.csvd7ae9cd84f8c02f6
data/fm/all/fotmob_all_2023-2024.csvb4708396cbbcb33d
data/fm/all/fotmob_all_2024-2025.csv47dc789739825b02
data/fm/all/fotmob_all_2025-2026.csv9800c19cf1c32a6b
data/site/tm_clubs_seasons.parquet3966ba16e0969cab
data/site/tm_players.parquet13925a6ca53790c6
data/site/tm_positions.parquet56b762fbd523f7e0
data/site/tm_transfers.parquetd1389454ff504b7e
data/site/tm_valuations.parquet390b77955b4a1c93