Over the past few weeks, I’ve been building something I once swore I’d never make:
a ranking system for Oregon high school tennis.
It’s something that’s annoyed me for years. But the use case was so limited — so hyper-specific — that I could never justify the time. That is, until generative tools made it faster to prototype models, simulate seasons, and design something realistic. Suddenly, what used to be 100 hours of spreadsheets became something I could test, break, and refine in a week.
The first part of this is the OSAA started using this really bad proprietary tool called Tennisreporting where you can log matches across the state when they’re played. It’s been good in the sense that for all of the flaws of the web tool, it lets you see what’s going on statewide, as long as other school’s coaches put their results in. The problem with this platform is that it comes with really bad rankings that we ignore, it has really bad findability and was designed like someone was living in the 90s built it and never bothered to update it. Besides that, it’s fine.
So I started initially working on making my own replacement for this tool mostly out of frustration than out of practicality. But in the development of this tool, I realized it made more sense for me to just focus on improving the APR formula that OSAA uses to develop power rankings. Oh, the OSAA is the state association for middle & high school sports in Oregon, they oversee all the sanctioned high school varsity sports. Anyway, I’ve basically gotten this tool built out with the help of Lovable, this was a very silly project that I didn’t need to be focusing on, so “vibecoding it” was actually kind of perfect because it got me far enough along that I didn’t have to really invest much into getting the prototype working.
The harder part of this was getting the formula devised. I’m pretty proud of it. It’s a working model for Adjusted Playoff Rankings (APR) specifically for tennis, — a transparent, simulation-tested system that makes it easier to determine which teams deserve playoff spots in a proposed team tournament format.
If you’ve ever looked at the OSAA’s ranking system for other sports — football, basketball, soccer — you’ve probably seen the terms:
RPI (Rating Percentage Index)
Colley (bias-free matrix model)
Opponent Win % (OWP)
These systems assume each contest is a single event with one winner and one loser. But high school tennis doesn’t work that way.
In tennis, every dual match is made up of eight individual matches (called “flights”):
four singles, four doubles.
Each flight is independent. You could dominate three flights and get crushed in five and still lose the dual 5-3. But what if your three wins came at 1st Singles, 2nd Singles, and 1st Doubles — the core positions that usually determine state qualifiers? That should count for something.
In short:
Wins are not equal.
And tennis can be easily manipulated by hiding stronger players in weaker flights to stack dual wins.
The OSAA system — as it currently exists — can’t tell the difference between an 8-0 blowout against a bottom-tier school and a gutsy 4-4 draw against a top program where your team took 1S and 1D.
That’s where this new system comes in.
I built the Adjusted Playoff Ranking (APR) system around a simple premise:
If you win key matches against strong teams, your team should be rewarded — even if you don’t win every dual.
Rather than looking only at W-L records, APR evaluates:
1. Which flights you won
2. Who you played
3. How tough your schedule was
4. How consistent you were across the season
It’s modular. You can simulate seasons. You can simulate teams. You can test weird edge cases, rainouts, and ties. It’s a working engine.
APR is calculated using two components:
Let’s break that down.
WS10 is your normalized flight win score, adjusted to account for the relative value of each position won. It captures the nuance of team strength based on which positions you consistently win, not just how many duals you win overall.
Flight Point Values:
Only these five flights are scored in WS10.
Other flights (3D, 4S, etc.) are not used for ranking, only for official match W-L totals.
Ties count as 0.5 wins. So a 4-4 draw gives partial credit.
OSI is a schedule strength multiplier. It looks at the average WS10 score of your opponents — not just whether you won, but who you played.
This incentivizes competitive scheduling, especially for programs like Catlin Gabel or Marist who schedule up into 6A or 5A matches.
OSI is scaled so the median team sits around 0.90. The range typically falls between 0.75–1.25 depending on your schedule.
It removes classification bias. 6A, 5A, 4A/3A/2A/1A teams are all ranked together and compared fairly based on opponent strength.
It eliminates score manipulation. Stacking weak flights doesn’t help — you need to win the meaningful ones.
It adjusts dynamically. Opponents’ performance improves your score over time, and ties don’t tank your rankings.
It reflects reality. When you simulate a season with real data and realistic matchups, the rankings pass the sniff test. Top teams rise. Weak schedules fade.
You can see what so far on github, I’m still messing around with this including tinkering with the interface more, but I’m really excited about the direction. The format that this would serve hasn’t even been approved yet. If we change the current state tournament format from being served by the individual singles & doubles events to a true team tournament, we’ll need rankings like this.