SportCache
Timing and results software for athletics, used at meets in Ghana and starting to reach cycling. Registration through photo-finish ingestion to published results.

- Meets used at
- 5
- Services live
- 4
- Athlete records recovered
- 632
Case study
A track meet produces its most valuable data in about ten seconds, and then everyone has to agree on what happened. Who won, by how much, whether that time is a national record. Get it wrong in public and you're issuing corrections for a week.
SportCache is what I've been building to handle that: registration, scheduling, live timing off the photo-finish hardware, results publishing, and an API underneath it all.
It's been used at meets in Ghana and has started to reach cycling. Adoption is gradual rather than finished, but it's held up with real results on the line, which is the only test that counts.
Getting results out of the hardware
The interesting problem isn't the web app. It's the fifteen metres between a photo-finish camera and a database.
SportCache reads from industry-standard timing systems two ways: a live push over the network, and a watcher that picks up result files as the timing operator writes them. Both land in the same normalisation layer, so nothing downstream cares which one fired. Most of the difficulty was below the application, in the networking between a timing laptop and everything else.
One idea took a full meet to properly internalise: elapsed time from the start trigger is the source of truth, not wall clock. Wall clock drifts, gets corrected, and disagrees between machines. The gun is the only clock that matters.
Encoding the rulebook
This is where officiating and engineering stop being separate jobs.
A technical delegate reading an API response should recognise every field without translating it first. So the schema follows World Athletics and UCI convention rather than inventing its own vocabulary, and result status is modelled as a real state rather than a missing value. Those conventions solved the modelling problem decades ago; ignoring them would only have made the data harder for the people who actually use it.
The same applies to the rules themselves. Medal tables count finals only, which sounds obvious until someone asks where the heats went. Grouping changes depending on whether a meet is international or domestic. Combined events score against the official formula, constants table and all. Each of those is something a person can get wrong on the day, and encoding them properly means the software doesn't need someone to catch it.
Two products, one API
Athletics and cycling are separate products under the same brand, not one system with a sport toggle.
| Service | What it does | Stack |
|---|---|---|
athletics.sportcache.com |
Track and field: heats, lanes, photo finish, field events | Next.js, Node, Postgres |
cycling.sportcache.com |
Road racing: stages, classifications, time gaps | Next.js, Node, Postgres |
api.sportcache.com |
Public REST API, sport-namespaced and versioned | FastAPI, PostgreSQL |
docs.sportcache.com |
OpenAPI 3.1 spec, rendered with Scalar | YAML, Scalar |
Splitting them wasn't the obvious call at the time. Athletics results are a finishing order over a distance, settled in one race. Cycling results accumulate across stages, with time gaps, bunch finishes and several classifications running at once. One interface serving both would have needed a rulebook of exceptions and felt wrong to officials in either sport.
What they share is the API underneath, sport-namespaced, so the data model stays honest to each code while there's still only one thing to maintain.
Rescuing three meets from PDFs
A national selection needed qualifying data that existed only as timing PDF exports across three separate meets. Not spreadsheets. PDFs.
I wrote a parser with Python and pdfplumber that pulled structured records out of those exports and reconciled them into a single 632-row workbook, which is what the selection then ran on.
It's the least glamorous thing here and the one that unblocked the most.
Where it stands
Built to World Athletics and UCI convention, because software that ignores the rulebook is no use to the people running a meet.
It's a young project and I'd rather say so than dress it up. The repository is private, adoption is gradual, and there's plenty still to build.