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.
Archive era — FBref
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
Coverage gate
| Season | Players ≥10 90s | Worst table ratio | Shipped? |
|---|---|---|---|
| 2009/10 | 1,598 | 0.00 | blocked |
| 2010/11 | 1,572 | 0.00 | blocked |
| 2011/12 | 1,603 | 0.00 | blocked |
| 2012/13 | 1,607 | 0.00 | blocked |
| 2013/14 | 1,608 | 0.00 | blocked |
| 2014/15 | 1,628 | 0.00 | blocked |
| 2015/16 | 1,613 | 0.00 | blocked |
| 2016/17 | 1,614 | 0.00 | blocked |
| 2017/18 | 1,626 | 1.00 | ship |
| 2018/19 | 1,603 | 1.00 | ship |
| 2019/20 | 1,556 | 1.00 | ship |
| 2020/21 | 1,635 | 1.00 | ship |
| 2021/22 | 1,641 | 1.00 | ship |
| 2022/23 | 1,631 | 1.00 | ship |
| 2023/24 | 1,607 | 1.00 | ship |
| 2024/25 | 1,573 | 0.20 | blocked |
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
| Source | Source ids | Matched to Transfermarkt | Rate | Worst league-season |
|---|---|---|---|---|
| fbref | 4,651 | 4,478 | 96.3% | La Liga 2023 — 92.6% normal |
| fotmob | 3,680 | 3,675 | 99.9% | Ligue 1 2020 — 99.1% normal |
| understat | 8,117 | 7,546 | 93.0% | La Liga 2018 — 90.0% normal |
Every league-season (115 rows)
| Source | League | Season | n | Matched | Rate | Band |
|---|---|---|---|---|---|---|
| fbref | La Liga | 2017 | 414 | 388 | 93.7% | normal |
| fbref | La Liga | 2018 | 408 | 383 | 93.9% | normal |
| fbref | La Liga | 2019 | 403 | 381 | 94.5% | normal |
| fbref | La Liga | 2020 | 427 | 397 | 93.0% | normal |
| fbref | La Liga | 2021 | 419 | 390 | 93.1% | normal |
| fbref | La Liga | 2022 | 418 | 396 | 94.7% | normal |
| fbref | La Liga | 2023 | 421 | 390 | 92.6% | normal |
| fbref | Ligue 1 | 2017 | 397 | 390 | 98.2% | normal |
| fbref | Ligue 1 | 2018 | 396 | 386 | 97.5% | normal |
| fbref | Ligue 1 | 2019 | 369 | 362 | 98.1% | normal |
| fbref | Ligue 1 | 2020 | 405 | 394 | 97.3% | normal |
| fbref | Ligue 1 | 2021 | 416 | 407 | 97.8% | normal |
| fbref | Ligue 1 | 2022 | 412 | 404 | 98.1% | normal |
| fbref | Ligue 1 | 2023 | 355 | 341 | 96.1% | normal |
| fbref | Premier League | 2017 | 386 | 380 | 98.4% | normal |
| fbref | Premier League | 2018 | 393 | 386 | 98.2% | normal |
| fbref | Premier League | 2019 | 395 | 391 | 99.0% | normal |
| fbref | Premier League | 2020 | 398 | 394 | 99.0% | normal |
| fbref | Premier League | 2021 | 396 | 386 | 97.5% | normal |
| fbref | Premier League | 2022 | 403 | 394 | 97.8% | normal |
| fbref | Premier League | 2023 | 402 | 391 | 97.3% | normal |
| fbref | Serie A | 2017 | 405 | 396 | 97.8% | normal |
| fbref | Serie A | 2018 | 406 | 398 | 98.0% | normal |
| fbref | Serie A | 2019 | 417 | 408 | 97.8% | normal |
| fbref | Serie A | 2020 | 413 | 404 | 97.8% | normal |
| fbref | Serie A | 2021 | 428 | 418 | 97.7% | normal |
| fbref | Serie A | 2022 | 419 | 411 | 98.1% | normal |
| fbref | Serie A | 2023 | 425 | 421 | 99.1% | normal |
| fbref | Bundesliga | 2017 | 346 | 344 | 99.4% | normal |
| fbref | Bundesliga | 2018 | 357 | 349 | 97.8% | normal |
| fbref | Bundesliga | 2019 | 359 | 350 | 97.5% | normal |
| fbref | Bundesliga | 2020 | 357 | 347 | 97.2% | normal |
| fbref | Bundesliga | 2021 | 351 | 338 | 96.3% | normal |
| fbref | Bundesliga | 2022 | 359 | 348 | 96.9% | normal |
| fbref | Bundesliga | 2023 | 353 | 344 | 97.5% | normal |
| fotmob | La Liga | 2020 | 340 | 340 | 100.0% | normal |
| fotmob | La Liga | 2021 | 343 | 342 | 99.7% | normal |
| fotmob | La Liga | 2022 | 359 | 359 | 100.0% | normal |
| fotmob | La Liga | 2023 | 341 | 340 | 99.7% | normal |
| fotmob | La Liga | 2024 | 341 | 339 | 99.4% | normal |
| fotmob | La Liga | 2025 | 303 | 302 | 99.7% | normal |
| fotmob | Ligue 1 | 2020 | 336 | 333 | 99.1% | normal |
| fotmob | Ligue 1 | 2021 | 339 | 338 | 99.7% | normal |
| fotmob | Ligue 1 | 2022 | 327 | 326 | 99.7% | normal |
| fotmob | Ligue 1 | 2023 | 292 | 291 | 99.7% | normal |
| fotmob | Ligue 1 | 2024 | 288 | 287 | 99.7% | normal |
| fotmob | Ligue 1 | 2025 | 277 | 277 | 100.0% | normal |
| fotmob | Premier League | 2020 | 328 | 328 | 100.0% | normal |
| fotmob | Premier League | 2021 | 341 | 340 | 99.7% | normal |
| fotmob | Premier League | 2022 | 331 | 331 | 100.0% | normal |
| fotmob | Premier League | 2023 | 339 | 339 | 100.0% | normal |
| fotmob | Premier League | 2024 | 321 | 321 | 100.0% | normal |
| fotmob | Premier League | 2025 | 340 | 339 | 99.7% | normal |
| fotmob | Serie A | 2020 | 341 | 341 | 100.0% | normal |
| fotmob | Serie A | 2021 | 335 | 335 | 100.0% | normal |
| fotmob | Serie A | 2022 | 337 | 337 | 100.0% | normal |
| fotmob | Serie A | 2023 | 343 | 343 | 100.0% | normal |
| fotmob | Serie A | 2024 | 345 | 345 | 100.0% | normal |
| fotmob | Serie A | 2025 | 337 | 337 | 100.0% | normal |
| fotmob | Bundesliga | 2020 | 286 | 286 | 100.0% | normal |
| fotmob | Bundesliga | 2021 | 277 | 277 | 100.0% | normal |
| fotmob | Bundesliga | 2022 | 279 | 279 | 100.0% | normal |
| fotmob | Bundesliga | 2023 | 294 | 294 | 100.0% | normal |
| fotmob | Bundesliga | 2024 | 278 | 278 | 100.0% | normal |
| fotmob | Bundesliga | 2025 | 286 | 285 | 99.7% | normal |
| understat | La Liga | 2017 | 557 | 508 | 91.2% | normal |
| understat | La Liga | 2018 | 531 | 478 | 90.0% | normal |
| understat | La Liga | 2019 | 556 | 504 | 90.6% | normal |
| understat | La Liga | 2020 | 570 | 518 | 90.9% | normal |
| understat | La Liga | 2021 | 603 | 555 | 92.0% | normal |
| understat | La Liga | 2022 | 584 | 542 | 92.8% | normal |
| understat | La Liga | 2023 | 598 | 553 | 92.5% | normal |
| understat | La Liga | 2024 | 590 | 544 | 92.2% | normal |
| understat | La Liga | 2025 | 600 | 561 | 93.5% | normal |
| understat | La Liga | 2026 | 382 | 0 | 0.0% | no TM pool yet |
| understat | Ligue 1 | 2017 | 540 | 520 | 96.3% | normal |
| understat | Ligue 1 | 2018 | 548 | 527 | 96.2% | normal |
| understat | Ligue 1 | 2019 | 531 | 512 | 96.4% | normal |
| understat | Ligue 1 | 2020 | 575 | 555 | 96.5% | normal |
| understat | Ligue 1 | 2021 | 589 | 571 | 96.9% | normal |
| understat | Ligue 1 | 2022 | 585 | 570 | 97.4% | normal |
| understat | Ligue 1 | 2023 | 525 | 504 | 96.0% | normal |
| understat | Ligue 1 | 2024 | 542 | 516 | 95.2% | normal |
| understat | Ligue 1 | 2025 | 553 | 524 | 94.8% | normal |
| understat | Ligue 1 | 2026 | 357 | 0 | 0.0% | no TM pool yet |
| understat | Premier League | 2017 | 515 | 504 | 97.9% | normal |
| understat | Premier League | 2018 | 505 | 496 | 98.2% | normal |
| understat | Premier League | 2019 | 515 | 499 | 96.9% | normal |
| understat | Premier League | 2020 | 524 | 508 | 96.9% | normal |
| understat | Premier League | 2021 | 537 | 522 | 97.2% | normal |
| understat | Premier League | 2022 | 554 | 543 | 98.0% | normal |
| understat | Premier League | 2023 | 570 | 556 | 97.5% | normal |
| understat | Premier League | 2024 | 562 | 547 | 97.3% | normal |
| understat | Premier League | 2025 | 537 | 516 | 96.1% | normal |
| understat | Premier League | 2026 | 358 | 0 | 0.0% | no TM pool yet |
| understat | Serie A | 2017 | 533 | 525 | 98.5% | normal |
| understat | Serie A | 2018 | 543 | 534 | 98.3% | normal |
| understat | Serie A | 2019 | 572 | 560 | 97.9% | normal |
| understat | Serie A | 2020 | 594 | 578 | 97.3% | normal |
| understat | Serie A | 2021 | 608 | 594 | 97.7% | normal |
| understat | Serie A | 2022 | 577 | 564 | 97.7% | normal |
| understat | Serie A | 2023 | 590 | 577 | 97.8% | normal |
| understat | Serie A | 2024 | 599 | 586 | 97.8% | normal |
| understat | Serie A | 2025 | 586 | 571 | 97.4% | normal |
| understat | Serie A | 2026 | 361 | 0 | 0.0% | no TM pool yet |
| understat | Bundesliga | 2017 | 472 | 468 | 99.2% | normal |
| understat | Bundesliga | 2018 | 468 | 462 | 98.7% | normal |
| understat | Bundesliga | 2019 | 487 | 478 | 98.2% | normal |
| understat | Bundesliga | 2020 | 496 | 484 | 97.6% | normal |
| understat | Bundesliga | 2021 | 511 | 501 | 98.0% | normal |
| understat | Bundesliga | 2022 | 506 | 496 | 98.0% | normal |
| understat | Bundesliga | 2023 | 494 | 481 | 97.4% | normal |
| understat | Bundesliga | 2024 | 481 | 474 | 98.5% | normal |
| understat | Bundesliga | 2025 | 499 | 490 | 98.2% | normal |
| understat | Bundesliga | 2026 | 285 | 0 | 0.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
| Target season | n | Marcel RMSE | “Last season” RMSE | Positional-mean RMSE |
|---|---|---|---|---|
| 2015/16 | 1,376 | 0.0728 | 0.0772 | 0.0985 |
| 2016/17 | 1,359 | 0.0696 | 0.0765 | 0.1009 |
| 2017/18 | 1,368 | 0.0689 | 0.0812 | 0.0972 |
| 2018/19 | 1,418 | 0.0716 | 0.0799 | 0.1 |
| 2019/20 | 1,376 | 0.0731 | 0.0799 | 0.1033 |
| 2020/21 | 1,429 | 0.0702 | 0.0821 | 0.1 |
| 2021/22 | 1,424 | 0.071 | 0.078 | 0.0989 |
| 2022/23 | 1,392 | 0.0722 | 0.0836 | 0.0995 |
| 2023/24 | 1,354 | 0.0883 | 0.0912 | 0.1163 |
| 2024/25 | 1,324 | 0.0862 | 0.0963 | 0.1149 |
| 2025/26 | 1,345 | 0.0765 | 0.091 | 0.1073 |
| overall | 15,165 | 0.0747 | 0.0835 | 0.1034 |
descriptive (Marcel); P-1 found no out-of-sample edge over the market — see the P-1 page
Acceptance checks (SPEC-UI §8)
| Check | Result | Verdict |
|---|---|---|
| Coverage gatemedian 90s ratio vs standard table >= 0.90 over players with >=10 90s, every table | 7 seasons shipped (2017/18–2023/24), 9 blocked | pass |
| 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 flagged | pass |
| 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 ±10 | reported |
| 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 shared | reported |
| 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 shared | reported |
| 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 site | pass |
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
| File | SHA-256 (16) |
|---|---|
data/fb/big5_player_defense.rds | 8bc9fd8218c1dc07 |
data/fb/big5_player_misc.rds | 744708cd039a2154 |
data/fb/big5_player_passing.rds | 886126b93b1c3b48 |
data/fb/big5_player_passing_types.rds | 9a3914051fba1c0e |
data/fb/big5_player_playing_time.rds | 11f2e3eed64e2fd1 |
data/fb/big5_player_possession.rds | a41db96bcf4376dc |
data/fb/big5_player_shooting.rds | 1bac446d04a7e992 |
data/fb/big5_player_standard.rds | a299c971c5f1a1a5 |
data/fb/taxonomy.rds | 34654420b8965c71 |
data/fm/all/fotmob_all_2020-2021.csv | 9c5ba4f969ff0747 |
data/fm/all/fotmob_all_2021-2022.csv | bb96b57b2dfc1221 |
data/fm/all/fotmob_all_2022-2023.csv | d7ae9cd84f8c02f6 |
data/fm/all/fotmob_all_2023-2024.csv | b4708396cbbcb33d |
data/fm/all/fotmob_all_2024-2025.csv | 47dc789739825b02 |
data/fm/all/fotmob_all_2025-2026.csv | 9800c19cf1c32a6b |
data/site/tm_clubs_seasons.parquet | 3966ba16e0969cab |
data/site/tm_players.parquet | 13925a6ca53790c6 |
data/site/tm_positions.parquet | 56b762fbd523f7e0 |
data/site/tm_transfers.parquet | d1389454ff504b7e |
data/site/tm_valuations.parquet | 390b77955b4a1c93 |