Case study
Football scouting data platform
Collects player data at scale and presents it as a filterable scouting grid, so a shortlist is built by narrowing attributes rather than by reading profiles.
Overview
Scouting starts as a filtering problem. There are tens of thousands of players and a scout wants the handful who match a set of attributes, a position, an age band and a budget. Reading profiles one at a time does not get there.
This collects the player data and puts a grid over it, so the shortlist is produced by narrowing rather than by reading. Built for the same direct client as the monitoring platform, two systems later.
What I own
Collection, the data model, the upload pipeline and the interface.
Complexities tackled
Attribute data is only useful when it is complete. A grid with gaps is worse than no grid, because a filter silently drops every player whose value is missing rather than telling you it did. The collector walks the full population rather than sampling, and the load step validates the shape of every record before it is accepted, so a partial scrape fails loudly instead of quietly producing a shorter shortlist.
Separating collection from serving. The scrape is slow, occasionally blocked and rerun often. The interface has to be fast and always available. Those two things do not belong in one process, so collection writes into a store and the interface only ever reads from it. Rerunning a scrape cannot take the product down.
Comparison is the actual feature. A table of numbers does not answer “is this player better than that one”. The interface pairs the grid with per-player detail and attribute bars, so a value is read against its distribution rather than in isolation. That framing is what turns a dataset into a scouting tool.
Server state and form state are different problems. Scouting data is fetched, cached and refetched; the filters that query it are local and validated. Those are handled by two different tools rather than one pile of component state, which is what stops a filter change from triggering a refetch of something that has not gone stale.
Filters are a schema, not a set of inputs. A scouting interface lives on comparison, so the query shape is declared and validated before it is sent rather than assembled from whatever the inputs happened to contain. An invalid combination fails at the form rather than returning a confusing empty result.
The front end was scaffolded with an AI app builder, then taken over. The initial React shell came out of Lovable rather than being hand written, which is how it got a full set of accessible primitives for dialogs, comboboxes, sliders and tooltips on day one. The work after that is what matters: wiring it to a real collection pipeline, deciding what the server state layer should own, and making the filters a validated schema rather than the loose component state a generator leaves behind. Starting from generated scaffolding is a choice about where to spend the time, and it is worth being plain about.
Stack
Playwright and Node.js for collection, MongoDB for storage with a separate schema and upload step, and a React and TypeScript front end built with Vite and Tailwind CSS.
Working on something similar?
Tell me what you are building and what is in the way. I will tell you honestly whether I am the right person for it.