-> Inspiration When a club loses a player to a sale or a long injury, the question is never "who is the best striker alive." It is "who plays the same role, and who can we actually afford." That first pass is slow, manual work: cross-reference the stats, check who fits the system, then check what each one costs. I wanted a tool that does that boring first hour in a few seconds and hands back a shortlist you could defend in a recruitment meeting.
-> What it does Name the player you are replacing, set a budget and a few filters (age, minutes, league), and get a ranked list of stylistically similar players from the 2024/25 big-five European leagues. Every match is scored 0 to 100 and explained: the metrics that make the two players alike, and the places where the candidate falls short. Click any name to open a side-by-side radar against the player you lost.
-> How I built it Two halves. An offline Python pipeline pulls per-90 performance data from FBref (StatsBomb) and market values from Transfermarkt, then reduces every outfield player to a feature vector. Each metric is turned into a z-score within the player's position, so a centre-back is judged against centre-backs and a striker against strikers. The pipeline writes one JSON file.
The front end is plain TypeScript with Vite, no framework. Similarity is cosine distance between two players' position-adjusted vectors, computed in the browser, so the budget and age filters re-rank instantly with no server. The result is a static site that runs on GitHub Pages. The look is a deliberate broadsheet design (Fraunces, Source Serif, JetBrains Mono on warm paper) because a scouting report should read like a report, not a dashboard demo.
-> Challenges I ran into
- Joining two data sources with no shared ID. FBref and Transfermarkt spell names differently and handle accents differently. I matched on a normalized name plus birth year, with a last-name fallback that drops ambiguous duplicates rather than guessing. That gets market values onto 83.8% of the pool; the rest still rank on style and simply drop out when a budget cap is active.
- The free FBref tables do not include expected goals or detailed passing, so I built style features from what is there: shot volume and accuracy, finishing, creation, crossing, and defensive actions like interceptions and tackles won.
- Sample size. A player with 400 minutes can post a flattering per-90, so the pipeline floors at 450 minutes and the slider lets you raise it.
-> What I learned Most of the work in scouting is framing the right question. "Most similar player" is easy. "Most similar, in this position, that we can afford, with the trade-off spelled out" is the part that is actually useful. I also learned that normalizing within position matters more than the choice of similarity metric: comparing a player to his positional peers is what makes the matches sensible in the first place.
-> What's next Expected goals and progressive passing (via Understat) to sharpen midfield matches, goalkeeper scouting, and multi-season form so a one-year spike is easy to tell from a real step up in level.
Built With
- css
- fbref
- html
- kaggle
- pandas
- python
- soccerdata
- statsbomb
- svg
- transfermarkt
- typescript
- vite
Log in or sign up for Devpost to join the conversation.