Case Study
Johnson Power Rankings: A World Ranking for the 200+ Mile Ultra
There is no Michigan index for the 200-mile ultra. So we built one.
Updated May 12, 2026
Johnson Power Rankings
200+ Mile · Week of May 12, 2026
Placeholder data for illustration
johnson-power-rankings.vercel.app
There{'’'}s no Michigan index for the 200
There is no credible, data-driven ranking for 200+ mile ultrarunners. The ITRA 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 does not recognize the 200-mile distance. UltraRunning Magazine’s “Ultrarunner of the Year” is a subjective panel vote, not a quantitative system.
Johnson Power Rankings fills that void. Modeled in spirit after the ITRA Performance Index, the UTMB Index, and the University of Michigan Consumer Sentiment Index: a recurring, cited number. The kind of number a podcaster can reference, a race director can validate against, and a runner can point to and say “that’s my standing.”
Built with my dad
The “Johnson” is Paul James Johnson: ultramarathon runner, pacer, crew member, and Jeff’s dad. JPR was born from recorded kitchen-table conversations. Paul is the domain expert. He knows the runners by name, the culture of backyard ultras, which podcasters matter, the economics of race photography, which courses are genuinely harder. Jeff is the technical execution: Next.js, Supabase, React Native, Claude-powered development.
The PRD literally names the roles: Owner, Jeff Michael Johnson. Domain expert, Paul James Johnson. The vision is personal. Paul wants to be able to say “Rachel Entrican is number one in the Johnson Power Rankings” and have it carry weight. They want podcasters to reference JPR the way economists reference the Michigan index. The family name is the brand, on purpose.

~2 days
36 Commits to Deploy
11
Supabase Migrations
4+3
Data Sources + Scrapers
20+
Tables, RLS Everywhere
The engine
The ranking engine lives in @jpr/core, a pure TypeScript package with full Vitest coverage. No runtime dependencies beyond the math.
Course difficulty model: 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 derived from past editions.
Field strength: iterative computation with damping, outlier capping at 4× median, and a convergence loop. Stronger fields amplify the signal of a fast time.
Backyard ultras: yards converted to effective miles, with dedicated scoring for assists and co-champion handling. A separate scorer for the format’s unique structure.
The rating itself: a time-decayed weighted mean of best-N performance scores over a 36-month rolling window. Linear decay between months 12–36, a cold-start prior for athletes with fewer results, and an uncertainty score that widens with sparse data.
How a result becomes a rating
Step 01
Result ingested
Race result pulled from UltraSignup, DUV, RunSignup, or OpenSplitTime. Time, distance, format, DNF status recorded.
Step 02
Difficulty + field strength scored
Course difficulty model (elevation, surface, terrain, weather, historical slow factor) and iterative field-strength computation applied.
Step 03
Rating computed
Time-decayed weighted mean of best-N performance scores over a 36-month rolling window. Linear decay months 12–36, cold-start prior, uncertainty score.
Four surfaces
JPR ships across four coordinated surfaces, all sharing a design system re-themed from PrayerMap’s “Vesper Twilight” palette.
Web
Next.js 15, server-rendered. Public rankings, athlete profiles, weekly snapshots. Live on Vercel.
Admin
11 functional sections: dashboard, data ops, identity resolution, difficulty data, formula explorer, publications, style, flags, cron, dev tools, audit log.
Mobile
Expo SDK 52 / React Native. Tabs for rankings, lookup, archive, about. Push notifications for weekly publishes.
Design System
Shared @jpr/design-system: CSS-variable tokens, Tailwind preset, shadcn-style primitives, RN theme, interactive StyleGuide.
Under the hood
packages/ingest handles connectors for UltraSignup, RunSignup, DUV, and OpenSplitTime, plus three bespoke scrapers for Vol State, Backyard Ultra, and the Self-Transcendence 3100. A normalizer parses time formats, converts distances, and classifies race formats. Identity resolution uses fuzzy name matching with diacritics and script folding, outputting confidence scores for human review.
Supabase backs the entire system: 11 migrations, RLS on all 20+ tables, SECURITY DEFINER RPCs for the admin surface, Auth with TOTP + WebAuthn passkeys, and 6 Deno edge functions (poll-ultrasignup, poll-duv, backfill, resolve-identities, recompute-rankings, generate-digest). A Monday-morning cron recomputes the rankings and publishes an immutable weekly snapshot with movement arrows.
The admin panel covers 11 sections: dashboard with stat cards, data operations, identity resolution queue, difficulty and course data, a rating-formula explorer, publications archive, style management, feature flags, cron and jobs, dev tools, and a full audit log with removal request handling. Built with TanStack Query + Table, react-hook-form + zod, and Supabase Auth + admin_users role + MFA.
CI runs GitHub Actions: typecheck, lint, test. TypeScript strict throughout with noUncheckedIndexedAccess and zero any.
Honest, by design
DNFs count. Every runner who has competed in a qualifying 200+ mile event is ranked, including those who did not finish. The ranking reflects participation in the distance, not just completion.
Immutable weekly snapshots. Rankings publish every Monday as a permanent record. No retroactive edits. The history is the history.
Source-cited data. Every result traces back to its ingestion source: UltraSignup, DUV, RunSignup, or OpenSplitTime. No manual entry without a verifiable origin.
Removal requests respected. Athletes can request removal. The system logs and honors it. Rankings exist to serve runners, not to trap them.
The whole system: 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 with 11 migrations + RLS + 6 edge functions, real data ingestion from four sources with three bespoke scrapers, and two live Vercel deployments.
Scaffolded, built, and deployed in roughly two days across 36 commits. Not a sprint for its own sake. Evidence of what focused, Claude-powered development looks like when the domain expert is sitting across the kitchen table.
Where it{'’'}s headed
JPR is live in preview now. The public site is running at johnson-power-rankings.vercel.app. The admin panel is deployed. Data backfill and tuning are ongoing: international race sources across the US, Europe, Asia, Africa, and Oceania; DUV + UltraSignup event-ID mappings for 200+ mile events from 2021–2026; field-strength saturation fixes; marquee-race difficulty overrides; a combined “All” world ranking merging men and women.
Next: public launch, the mobile app on TestFlight, weekly email digests via Substack, and the push-notification pipeline for Monday-morning publishes. The goal is the same one Paul described at that kitchen table: a number people reference, a ranking that earns trust by being transparent about its methodology and honest about its data.
Tech Stack
Frontend
Mobile
Backend
Tooling
Infrastructure
Want to discuss the architecture?
Get in Touch