Johnson Power Rankings: Building a Ranking System With My Dad
Johnson Power Rankings is a data-driven weekly world ranking for ultramarathon runners, launching with the 200+ mile tier. The cited authority that doesn't exist yet for the longest races.
The gap is real. ITRA's Performance Index caps at a generic "Endurance" bucket. The UTMB World Series index stops at 100 miles. UltraSignup's per-race score is trivial. The IAU doesn't recognize 200 miles as a distance category. UltraRunning Magazine's "Ultrarunner of the Year" is a subjective panel vote. If you want to know who the best 200-mile runners in the world are, ranked by performance data, there's no number to point to.
We're building that number.
The Johnson in the name
The "Johnson" is Paul James Johnson, an ultramarathon runner, pacer, crew member, and my dad.
JPR came out of recorded kitchen-table conversations. Not pitch meetings, not whiteboard sessions. Kitchen-table conversations where Dad would say a name like Rachel Entrican and explain why she's the best in the world at this distance, and I'd ask how you'd prove that with data, and he'd start listing the variables: course difficulty, field depth, conditions, DNF context. He knows the runners by name. He knows the culture of backyard ultras, which podcasters matter, the economics of race photography, which courses are genuinely harder than their elevation profiles suggest.
I'm the technical execution. Next.js, Supabase, React Native, Claude-powered development.
The PRD literally names the roles. Owner: Jeffrey Michael Johnson. Domain expert: Paul James Johnson. The family name is the brand, on purpose. Dad wants to be able to say "Rachel Entrican is number one in the Johnson Power Rankings" and have it carry weight. We want podcasters to reference it the way economists reference the Michigan Consumer Sentiment Index: a recurring, cited number with a methodology you can inspect.
The week I'm writing this, he ran another one.
Two days to a system
The system was scaffolded, built, and deployed in roughly two days across about 36 commits. An npm-workspace monorepo: a Next.js web app, a separate Next.js admin panel, an Expo mobile app, a pure-TypeScript ranking engine, a Supabase backend, and real data ingestion.
Insight
Two days got us the system. The tuning is the actual work, and it's ongoing. Building the scaffolding is fast. Earning the authority to publish a number that people trust is slow. That distinction matters.
The engine
The ranking engine lives in @jpr/core, tested with Vitest. The model:
A course-difficulty score built from elevation gain and loss per mile, surface factors (track 0.96, road 1.0, trail 1.1, technical 1.22, sand 1.18, snow 1.2), terrain factors (high-altitude 1.12, mountain 1.06, desert 1.05, arctic 1.15), a weather index, and a historical-slow factor.
An iterative field-strength computation with damping, outlier capping at 4x the median, and a convergence loop.
A time-decayed weighted mean of each runner's best-N performance scores over a 36-month rolling window, with linear decay between months 12 and 36, a cold-start prior, and an uncertainty score.
Per-format performance scoring. A backyard-ultra scorer that converts yards to effective miles with assist and co-champion handling. Age-group, region, and format segmentation. Weekly digest generation. Versioned rating parameters.
Every runner who's competed in a qualifying 200+ mile event is ranked, including DNFs. Rankings publish weekly on Mondays as immutable snapshots with movement arrows. The data is source-cited. Removal requests are respected.
Ingestion
@jpr/ingest has connectors for UltraSignup, RunSignup, DUV, and OpenSplitTime, plus bespoke scrapers for Vol State, the Backyard Ultra, and the Self-Transcendence 3100. A normalizer. Identity resolution with fuzzy name matching, diacritics and script folding, and confidence scoring.
The backend
Supabase with 11 migrations, RLS on all 20+ tables, SECURITY DEFINER admin RPCs. Auth with TOTP and WebAuthn passkeys. Six Deno edge functions: poll-ultrasignup, poll-duv, backfill, resolve-identities, recompute-rankings, generate-digest. A Monday cron that recomputes the rankings and publishes an immutable weekly snapshot.
The admin panel
Eleven sections: dashboard, data operations, identity resolution, difficulty and course data, a rating-formula explorer, publications, style management, feature flags, cron and jobs, dev tools, audit log, removal requests.
What almost broke it
Identity resolution. The same runner appears as "Rachel Entrican" on UltraSignup, "R. Entrican" on DUV, and possibly "Rachel Entrican-Smith" on RunSignup. Fuzzy name matching with diacritics folding and confidence scoring gets you most of the way, but "most of the way" isn't good enough when you're publishing a ranked list with someone's name on it. This is not fully solved. It's actively being tuned.
The other pressure point is data density. The 200+ mile tier is, by definition, the thinnest tier. There aren't that many 200-mile races per year. That means the field-strength computation runs on small samples, which makes it more sensitive to outliers. The 4x-median cap helps, but small fields are still fragile. Moving down to 100-mile and shorter tiers will ease this, but the 200+ tier is where we started because it's where the gap is biggest.
Where it stands
The preview is live at johnson-power-rankings.vercel.app. Currently in the data-backfill-and-tuning phase: international race sources added across the US, Europe, Asia, Africa, and Oceania. DUV and UltraSignup event-ID mappings for 200+ mile events from 2021 through 2026. Field-strength saturation fixes. Marquee-race difficulty overrides. A combined "All" world ranking merging men and women.
The mobile app is scaffolded but not yet on TestFlight. Public launch pending.
The JPR case study covers the architecture in more detail.
The thing that transfers
The pattern here isn't "I built a ranking system." The pattern is: domain expert plus technical execution is the actual unit of a good product. Not just "talk to your users." Something more specific than that. The domain expert has to be in the room, naming the variables, catching the errors the data can't surface. The technical person has to be in the room, building fast enough that the domain expert can react to real output instead of wireframes.
Dad didn't review a spec. He reviewed a ranked list with real names on it, and he said "that's wrong" or "that's right." That feedback loop, running on real data against a real system, is what makes the product credible. Not the algorithm. Not the stack. The loop.
I don't know if JPR will become the cited number we want it to be. But the shape of it is right: a family name, a methodology you can inspect, and a domain expert who actually runs the races.