Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

570 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DoubleCheck

License: MIT

→ doublecheckff.com

Fair schedules for fantasy football leagues. No team gets doubled against the same opponent year after year.


What it looks like

DoubleCheck schedule view

The problem

In a 12-team, 14-week fantasy football league, every team plays 3 opponents twice and 8 once. Random scheduling clusters those repeat matchups: some pairs get doubled multiple seasons in a row, others never. With 66 possible pairs across the league, ~4 will draw the short straw in any given season.

Across four seasons of random schedules, the probability that a specific pair gets doubled in 3+ seasons is ~6.5% - but multiplied across the league, somebody is almost always living it.

The math

In a 12-team / 14-week league each team doubles 3 of 11 opponents. Over four seasons that's 12 doubled-opponent slots competing for 11 unique opponents, which means:

  • Every opponent gets doubled exactly once in a four-season cycle, plus
  • ~6 forced repeats drawn from the oldest no-longer-avoided season (12 teams × 1 forced repeat ÷ 2).

The tool generates each year's doubles to satisfy that rotation, with the lookback window derived per format (see below).

Supported formats

Format Doubles/team Singles/team Rotation cycle Notes
8 / 13 6 1 ~7 years Inverted problem (who do you NOT double)
10 / 13 4 5 ~3 years
10 / 14 5 4 ~2 years
12 / 13 2 9 ~6 years
12 / 14 3 8 ~4 years Most common league shape
14 / 14 1 12 ~13 years Minimal impact
14 / 15 2 11 ~7 years

Pure round-robins (e.g. 14-team / 13-week) and complete double round-robins (e.g. 8-team / 14-week) have no fairness problem - the tool detects these and tells you no schedule is needed. Odd-sized leagues, 16+ team leagues, and regular seasons longer than two full round-robins (e.g. 8 teams / 15 weeks, where some opponents play a third matchup) are out of scope.

The algorithm

  • Fair rotation. Every opponent gets doubled exactly once over a full rotation cycle (~4 years for 12-team / 14-week). No pair keeps drawing the short straw season after season.
  • Per-format lookback window. The lookback is computed per format to maximize rotation coverage. Recent seasons are hard-avoided (cannot repeat), with the next oldest soft-avoided (preferred to skip but allowed when the constraint set is too tight).
  • Maximum-separation placement. Doubled pairs are placed at the largest possible week distance within the season (weekCount - doublesPerTeam), so a doubled rivalry's first and rematch games are as far apart as the format allows.
  • Rivalry weeks. Commissioners can pin specific matchups to specific weeks (or let the algorithm pick the week). Pin an entire week's worth of matchups to create a full rivalry week. One pin forces a single appearance at that week; two pins for the same pair force a double. Pins override the avoidance system - a hard-avoided pair pinned to a week still plays that week - while the algorithm continues rotating the remaining doubles fairly across the rest of the league. Pinned matchups also display in the order you entered them - each leg of a doubled rivalry follows its own pin (enter the two pins in opposite orders for home-and-home), and a leg without its own pin flips automatically. Works across all 7 supported formats, including the most constrained (14/13 pure round-robin and 14/14 with only 1 double per team).
  • Identity tracking via user IDs. Doubled pairs are stored by Sleeper user ID, ESPN user ID, or Yahoo manager GUID - not team name. The schedule survives team renames, manager changes, and roster reshuffles between seasons.
  • Auto-detected league format on imports. Team count and week count come from the imported league data for Sleeper, ESPN, and Yahoo Fantasy. Manual-import users (NFL.com, CBS, etc.) pick team count and week count from two dropdowns instead.

Supported platforms

  • Sleeper - fully public API. Enter your Sleeper username (pick from your leagues) or paste a league ID directly. Imports completed seasons plus the in-progress one once week 1 has been played - Sleeper publishes the full schedule up front, so the current season's doubles reflect the complete scheduled matchups.
  • ESPN - public leagues by league ID. Private leagues must be set to public by the commissioner before import. Imports seasons through last year; the current season is never included.
  • Yahoo Fantasy - sign in with Yahoo Fantasy (OAuth 2.0). Tokens are stored in an encrypted httpOnly cookie - no database, no account needed. Imports completed seasons only. Distinct Yahoo leagues that share a name may collapse into one picker entry; rename one league on Yahoo to import both.
  • Manual - for unsupported platforms (NFL.com, CBS, etc.). Pick your league format, name your teams, and click the doubled matchups from each past season on an interactive grid.
  • Restore from link - returning users paste a previously-saved share URL (full URL, /s/slug path, or bare 8-char slug) to bring a league back into a fresh browser. Restores teams, userIds, league name, history, and rivalry pins from the share payload. Save & Share records the just-generated season into history before writing the link, so when you restore next year the shared year's doubles are already in the avoidance window - just import the new season and regenerate. The old schedule and any one-off manual avoids are dropped so you regenerate against current avoidance constraints.

Because Sleeper includes the in-progress season, a mid-season Sleeper re-import replaces a current-year history row saved via Save & Share with the season as actually scheduled on Sleeper - identical if you applied the generated schedule, and a correction if you didn't. Either way the current year's schedule generation is unaffected: avoidance only looks at earlier seasons.

If some of your seasons fail to fetch, or were played at a different roster size than your league's detected format, DoubleCheck imports only the unbroken run of newest seasons and tells you exactly which seasons were withheld and how to recover them - retry the import, or fill the missing year via Add Past Season and re-import. A hole in your season history would silently skew the recency-based avoidance, so a shorter unbroken history is always safer than a longer one with a gap.

Quick start

The tool is a three-step flow at doublecheckff.com:

  1. Import or restore. Pick your platform and enter your league ID (or, for Sleeper, your username - DoubleCheck lists your leagues to pick from). League format is auto-detected from imported data. For platforms without automatic import (NFL.com, CBS, etc.), choose Manual and enter your league info directly. Returning users can pick Restore from link and paste a previously-saved share URL to bring a league back into a fresh browser - teams, history, and rivalry pins all come back. Save & Share records the just-generated season into history before writing the link, so when you restore next year the shared year's doubles are already in the avoidance window - just import the new season and regenerate.
  2. Review. See the lookback window (with optional override if you want to weight more or fewer prior seasons), scan past season history, review the avoidance matrix, and optionally pin rivalry matchups to specific weeks.
  3. Schedule. Browse the generated schedule week-by-week (pinned rivalry matchups are highlighted), click Save & Share to get a read-only /s/{slug} link your league members can open from any device, or expand Full Schedule (Text) for a plain-text dump. Links expire after 365 days.

No platform exposes a write API for league schedules, so commissioners enter the generated matchups into their platform's commissioner tools by hand. It's a one-time ~10-minute annual task per league.

Tech stack

  • Next.js 16 (App Router, Turbopack) with TypeScript
  • React 19
  • Tailwind CSS for styling
  • ESLint 9 with flat config (eslint.config.mjs)
  • Vitest for the test suite (453 tests; algorithm coverage across all 7 supported formats, including rivalry pins, plus import/share route logic and import-flow and schedule-step component coverage via React Testing Library)
  • Vercel for deployment

See ROADMAP.md for the full product roadmap and current state.

Development

git clone https://github.com/shahirtaj/doublecheck.git
cd doublecheck
npm install
cp .env.example .env.local
npm run dev

The app runs without any environment variables configured - Yahoo Fantasy OAuth and share links won't work, but Sleeper imports, ESPN imports (public leagues), manual import, and schedule generation are fully functional locally.

Run npm test to execute the test suite (453 tests). See CLAUDE.md for the full command reference and project structure.

Support

If DoubleCheck saves your league from unfair schedules, buy me a coffee.

License

MIT - see LICENSE.

About

Fair schedules for fantasy football leagues

Resources

Stars

1 star

Watchers

0 watching

Forks

Sponsor this project

Used by

Contributors

Languages