Sports technology ·Sep 2026 → ongoing ·live
EBL
A basketball league's season as a public site and an iPhone app on the App Store, built unasked on the league's own games and adopted the day after the league first saw it. Each game is read from its broadcast in the cloud and from the courtside stat sheets, and held against the league's own report.
- Timeframe
- Sep 2026 → ongoing
- Tags
- Sports technology · information system · data pipeline · mobile apps · postgresql
- Credits
- Xern Mottley — Engineer
- Links
- Visit the live site ↗
- The kind of build
- Venture Build one like it →
Where it started
The Elite Basketball League streams its games on YouTube, and it already published a lot: a website with the season's figures, game reports, busy social accounts. What a fan could not do was follow one game from its film to its box score to the players in it, the way the NBA's app allows.
Nobody asked us for an app. We built one anyway, in two days, on the league's own season, then wrote to the league: a short, formal letter that left the prototype to do the talking. The league wrote back the next day, and the work was agreed the same day.
A month from the idea to the App Store, in Port of Spain dates. The marked days are the league's yes and the iPhone app's release.
The league's footage, in every form we make of it, belongs to the league under the letter both sides signed, and it stays off our portfolio. The pictures on this page are drawn; the cover is the league's own mark.
What was done
A pipeline in the cloud reads each game's broadcast the way a statistician watches one, a source at a time: the score graphic, the clock, the commentary, the sound. It starts from the game's YouTube link and reads it as one careful viewer would, signed in, one request at a time, never disguised. What it reads is held against the league's report and the stat sheets from the table.
Drawn, not filmed: the league's footage stays the league's. A broadcast carries four things worth reading, each read on its own, then held against the league's report and the table's stat sheets.
Every play becomes one row of an event set in FIBA's own terms, each source's account kept in its own columns beside it. Each game is then published as one versioned payload into the league's PostgreSQL database, and the site and the apps read it through an API. Run twice on the same readings, every stage gives the same bytes, and a test in CI proves it.
Each layer is kept apart and rebuilt from the one before it. The event set is the hinge: a row a play in FIBA LiveStats' vocabulary, every source's evidence in its own columns, under a name that survives a re-run.
When a stream fails or never begins, the night is read from the league's courtside stat sheets, photographed at the table. Every line is read by eye and held to its own shooting, and each team to its totals, quarter by quarter.
One SvelteKit build runs as the public site and, through Capacitor, as the iPhone and Android apps. Fans get the season: results, box scores, every team and player, each game's film and its highlights. The films play from our own copies, because an iPhone will not play YouTube's player inside an app.
The apps read the season from the server, so a game night needs no new build. The league's staff get the tools that keep it right, in the same app.
Underneath it all: TypeScript and Svelte for the app; a Node API with Kysely on PostgreSQL; Python with DuckDB and dbt for the pipeline, which runs on AWS; PyTorch for the picture, on rented GPUs. CI runs the checks, the tests and the builds on every change to main.
One build in three homes, one API behind them. The staff's tools live in the same app as the fans' screens, behind a staff sign-in.
The decision that mattered
On the second day, a reel of one player's baskets turned out to include misses. Nobody had measured how often the system was right, and the question that day was whether it had one source of truth or a pile of fixes. If the figures are not accurate, nothing built on them counts.
So accuracy was measured, and the measures set the limits. The score graphic glitches, so a change in it is evidence, never proof. We tried following the ball itself with open vision models, and stopped on 5 October, when the detector found fewer than a fifth of the made shots in the league's soft stream.
The record stands on what can be checked against something else, and the league's report is one of those accounts. It can be wrong too: the report for game 20 printed its assists, steals and blocks one column out of place, and the stat sheets proved it on 58 of 63 cells. So the pipeline keeps every source's account, and a doubtful part of a game is marked rather than holding the game back.
A disagreement a fan would see becomes a check for the league's staff. The ledger only grows: the latest row stands, and the league's own report outranks an answer given before it arrived.
Where two accounts still disagree on a figure fans see, the league's own staff decide, with one tap. Each answer is kept as a new row, never edited: what was chosen, what it stood on, who chose it and when. An undo is a row too. If the league's report later says otherwise, the earlier choice steps aside and the question opens again.
Fans see one figure, and the league can see how every figure was reached.
Where it stands
The iPhone app has been on the App Store since 28 October. It was first sent to Apple on 7 October, sent again after we found faults on our own phone during the review, and then waited eleven days in Apple's queue. The Android app is in Google Play's closed test, which opens to the public once twelve testers have used it for fourteen days.
The public site, at ebl.opinternational.dev, carries the season as it is played: thirty games by 28 October. Round Three runs to 29 November, and the knockouts begin on 2 December, with a bracket built to fill itself from the results.
The work goes on with the league, a game night at a time, and a night's release is now one command.
- 22
- Games read from their broadcasts and published, to 29 October
- 775
- Highlights cut from those broadcasts, to 29 October
Next — Pure Hoops →