OverPowered International

OverPowered International

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.

[ COVER ]
Deployment: live Work: ongoing
Timeframe
Sep 2026 → ongoing
Tags
Sports technology · information system · data pipeline · mobile apps · postgresql
Credits
The kind of build

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.

[ TIMELINE ]
From an idea to the App Store, in a month 29 September: the idea, and an app begun on the league's own season. 30 September: the app on AWS, and a letter to the league. 1 October: the league says yes. 3 October: a letter both sides signed, the footage staying the league's and the technology ours. 5 October: the staff's Checks. 7 October: the public site at its own address, and the first build sent to Apple. 11 October: the apps read each night from the server, so a game night needs no new build. 28 October: the iPhone app on the App Store. A MONTH · PORT OF SPAIN DATESFrom an idea to the App Store29 SEPthe idea, and an app begun on theleague's own season30 SEPthe app on AWS, and a letter to theleague1 OCTthe league says yes3 OCTa letter both sides signed: thefootage stays the league's, thetechnology ours THEN5 OCTthe staff's Checks: every answer kept7 OCTthe public site at its own address;the first build sent to Apple11 OCTthe apps read each night from theserver: no new build a game night28 OCTthe iPhone app on the App Store

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.

[ BROADCAST ]
What the pipeline reads from one game's broadcast A drawn broadcast frame, not footage: the score graphic, the commentary's captions, the sound and the court. Each is read on its own: the score graphic gives every basket, its value and its second; the commentary names who scored, assisted or rebounded; the whistle and the crowd give stoppages, fouls and the second a basket drops; the picture maps the court in metres and finds the players, and following the ball was tried and set aside, because the stream loses it in the air. All of it is held against the league's own report and the table's stat sheets, and becomes one event set. ONE BROADCAST · FOUR THINGS TO READWhat the pipeline reads from a gamewhistlecrowd1234 WHAT EACH ONE YIELDS1The score graphic, second by secondevery basket, its value and its second; each risejudged before it is trusted2The commentary's wordswho scored, assisted or rebounded, when thecommentator says it3The whistle and the crowdstoppages and fouls; the second a basket drops4The picturethe court mapped to metres and the players found;the ball tried and set aside, because the streamloses it in the airHELD AGAINSTThe league's own reportthe official box score and the finalThe table's stat sheetsphotographed courtside, read, then settled by aperson→ one event set: a row a play, each source'saccount kept beside it

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.

[ RECORD ]
How a game becomes a record Six layers from top to bottom: the film and every reading kept as read; the readings, one table per reader by game and second; the event set, a row a play in FIBA's terms with every source kept; served tables by game, player and team; one versioned payload a game, loaded into the league's database; and the API the site and the apps ask. What holds it together: the same inputs give the same bytes, tested in CI; a basket is named by its game, team and the score after it, and every other play by an id minted once; and a stage with nothing new to read skips itself. ONE GAME · FROM READINGS TO THE APPHow a game becomes a recordKEPT AS READthe film and every reading, never editedadapt: a table per readerREADINGSby game and second, each reader apartreconcileTHE EVENT SETa row a play, in FIBA's terms; every source keptaggregateSERVED TABLESby game, player and team; each rate defined oncepublishONE PAYLOAD A GAMEversioned, then loaded into the league's databaseserveTHE APIthe site and the apps ask it, a screen at a time WHAT HOLDS IT TOGETHERSame inputs, same bytesrun 1run 2= byte for byterun it again on the same readings and every filecomes out identical; a test in CI proves itNames that outlive a re-rungame · team · score afternames a basketan id minted once, keptevery other playa refinement may move a play's second, never itsname, so a shared link keeps opening its momentNothing new, nothing runinputs · code · settingssame: skipchanged: runeach stage keeps a fingerprint of what it read, itsown code and its settings

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.

[ APPS ]
What the site and the apps offer One build runs in three homes: the public site, the Android app and the iPhone app, with one API behind them; a screen seen once opens again with no signal. For fans: results and box scores; every game, team and player; the statistics drawn; each game's own film with its quarters marked; highlights and a Reels feed; live play-by-play beside the stream; alerts on game night. For the league's staff: the Checks where two sources disagree; the stat sheets photographed in; the night's graphics ready to post; a courtside scorer that needs no signal; comments held until approved. ONE BUILD · THREE HOMESWhat the site and the apps offerone buildAndroidiPhonethe public sitewebCapacitorthe same screens in all three, one API behind them; ascreen seen once opens again with no signal FOR FANSresults and box scoresevery game, team and playerthe statistics, drawneach game's own film, its quarters markedhighlights, and a Reels feedlive play-by-play beside the streamalerts on game nightFOR THE LEAGUE'S STAFFthe Checks, where two sources disagreethe stat sheets, photographed inthe night's graphics, ready to posta courtside scorer that needs no signalcomments held until approved

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.

[ CHECKS ]
Who decides what a fan sees The broadcast as the pipeline read it and the league's official report are compared for every figure a fan sees. Where they agree, the figure is shown as it stands. Where they differ, a check goes to the league's staff, who answer with one tap; the answer is kept as a new row in a ledger whose rows are added and never edited: the latest row stands, an undo is a row of its own, and each keeps what it stood on, who chose and when. Fans see one reconciled figure. If the league's report arrives later and differs, the earlier choice steps aside and its check opens again. A doubtful part is hidden or marked, never the whole game held; once the league posts its final, the app's matches it. TWO ACCOUNTS OF ONE GAMEWho decides what a fan seesTHE BROADCASTas the pipeline read itTHE LEAGUE'S REPORTthe official box scorethe same figure?for every figure a fan seesyesnoSHOWNas it standsA CHECKfor the league's staffone tapTHE ANSWER, KEPTa new row, with whoFANS SEE ITone figure, reconciledA doubtful part is hidden or marked, never the wholegame held; once the league posts its final, the app'smatches it. EVERY ANSWER KEPTRows are added, never editedACTIONSETSSTOOD ONWHO, WHENchooseundochoosestandsthe latest row stands; an undo is a row of its own;each keeps what it stood on, who chose and whenthe league's report arrives,and it differsthe earlier choicesteps asideits checkopens again

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 →

Running a league on streams, sheets and reports that never meet?