MyGM
Fantasy Sports
Surfaces
  • Managers
  • Games
  • Market
  • Players
  • League
    • Seasons
    • History
    • Champions
    • Records
    • Rivalries
    • Trends
    • Reads
    • Press Box
    • Data model
    • SQL explorer
  • Connect
checking…
Connect Claude →
MyGM
Home/Data model
Reference

Data model

Where every number on the site actually comes from — the JSON snapshot, the indexed accessors, and the aggregation passes that produce careers, records, H2H, and rivalry intensity.

Seasons
7
2020–2026
Managers
12
14 users in lookup
Trade events
183
526 traded picks
Matchup rows
1,168
Σ PF 115,900

Pipeline

  1. 1
    Sleeper API → sleeper_raw.json
    tools/refresh-data.mjs walks previous_league_id back through every season, dumps the raw league/users/rosters/matchups/brackets/drafts/transactions/traded_picks per year.
  2. 2
    Raw + CBS bridge → normalized.json
    tools/refresh-data.mjs + tools/build-cbs-2020.mjs join Sleeper with cbs_2020.json (pre-Sleeper history), fold rosters → SeasonManager rows, collapse transactions → TradeEvent rows, compute the Career[] table once, and capture expansions + champion finishes.
  3. 3
    Build-time JSON import → lib/data.ts
    Pages and analytics import normalized.json + sleeper_raw.json statically; lib/data.ts builds Maps (byUserId, matchupsByYear, rosterToUserByYear, draftPickByPosition…) once per server boot.
  4. 4
    Per-page aggregation → lib/analytics/*
    H2H, records, rivalries, lineup efficiency, trade hindsight, trends — each is a pure function over the same in-memory snapshot.

Refresh cadence: cron hits /api/cron/refresh nightly; the static JSON is what every page reads.

Entities

click any row to inspect a real sample
User14
lib/types → User
▸

Manager identity. user_id is the only stable key — display_name and team_name change.

user_iddisplay_nameteam_nameavatar
View sample row
{
  "user_id": "682694793616236544",
  "display_name": "bloodyrichard",
  "team_name": "USPS Allstars ⭐️📦✅",
  "avatar": "f0edbf4278f53f9425db175073df6584"
}
Career12
data.careers (precomputed)
▸

One row per manager, summed across every Sleeper + CBS season. Reconciled against data.champions at module load so championships / runner_ups / thirds reflect playoff results.

seasonswins/losses/tiesfpts / fpts_againstchampionships / runner_ups / thirds / lastsbest_finish / worst_finishwin_pctavg_fpts_per_season
View sample row
{
  "user_id": "682694793616236544",
  "display_name": "bloodyrichard",
  "team_name": "USPS Allstars ⭐️📦✅",
  "seasons": 7,
  "wins": 51,
  "losses": 32,
  "ties": 0,
  "fpts": 11441.759999999998,
  "fpts_against": 10451.54,
  "championships": 2,
  "runner_ups": 0,
  "thirds": 2,
  "lasts": 0,
  "best_finish": 1,
  "worst_finish": 3,
  "first_year": 2020,
  "last_year": 2026,
  "win_pct": 0.614,
  "avg_fpts_per_season": 1634.54
}
Season7
data.seasons[]
▸

Per-year league snapshot. previous_league_id is the chain pointer; settings/scoring_settings power Trends.

yearleague_idprevious_league_idsettingsscoring_settingsroster_positionsmanagers[]
View sample row
{
  "year": 2026,
  "league_id": "1326430722143027200",
  "name": "United Dynasty Fantasy Football League",
  "status": "pre_draft",
  "total_rosters": 12,
  "previous_league_id": "1194779081882595328",
  "season_type": "regular",
  "settings": {
    "best_ball": 0,
    "waiver_budget": 750,
    "disable_adds": 0,
    "divisions": 0,
    "capacity_override": 0,
    "waiver_bid_min": 0,
    "taxi_deadline": 4,
    "draft_rounds": 5,
    "reserve_allow_na": 0,
    "start_week": 1,
    "playoff_seed_type": 0,
    "playoff_teams": 7,
    "veto_votes_needed": 5,
    "squads": 1,
    "num_teams": 12,
    "daily_waivers_hour": 9,
    "playoff_type": 0,
    "taxi_slots": 8,
    "sub_start_time_eligibility": 0,
    "daily_waivers_days": 5461,
    "sub_lock_if_starter_active": 0,
    "playoff_week_start": 15,
    "waiver_clear_days": 1,
    "reserve_allow_doubtful": 0,
    "commissioner_direct_invite": 0,
    "veto_auto_poll": 0,
    "reserve_allow_dnr": 0,
    "taxi_allow_vets": 1,
    "waiver_day_of_week": 2,
    "playoff_round_type": 0,
    "reserve_allow_out": 1,
    "reserve_allow_sus": 1,
    "veto_show_votes": 0,
    "trade_deadline": 10,
    "taxi_years": 2,
    "daily_waivers": 1,
    "faab_suggestions": 0,
    "disable_trades": 0,
    "pick_trading": 1,
    "type": 2,
    "max_keepers": 1,
    "waiver_type": 2,
    "max_subs": 0,
    "league_average_match": 0,
    "trade_review_days": 0,
    "bench_lock": 0,
    "offseason_adds": 1,
    "leg": 1,
    "daily_waivers_base": 4,
    "reserve_slots": 2,
    "reserve_allow_cov": 1,
    "daily_waivers_last_ran": 5
  },
  "scoring_settings": {
    "sack": 0,
    "fgm_40_49": 0,
    "pass_int": -2,
    "pts_allow_0": 0,
    "bonus_pass_yd_400": 2,
    "pass_2pt": 2,
    "st_td": 6,
    "rec_30_39": 0.75,
    "fgm_yds_over_30": 0.1,
    "rec_td": 6,
    "rec_20_29": 0.5,
    "idp_blk_kick": 2,
    "fgm_30_39": 0,
    "idp_fum_ret_yd": 0.1,
    "xpmiss": -1,
    "rush_td": 6,
    "idp_tkl": 0,
    "pass_td_40p": 0,
    "fgm": 3,
    "idp_sack_yd": 0.04,
    "rec_2pt": 2,
    "idp_tkl_loss": 0,
    "pass_int_td": -4,
    "idp_tkl_solo": 0.5,
    "st_fum_rec": 2,
    "fgmiss": -1,
    "ff": 0,
    "idp_int": 2,
    "rec": 1,
    "idp_int_ret_yd": 0.1,
    "idp_safe": 3,
    "pts_allow_14_20": 0,
    "fgm_0_19": 0,
    "idp_def_td": 6,
    "int": 0,
    "def_st_fum_rec": 0,
    "fum_lost": -2,
    "pts_allow_1_6": 0,
    "kr_yd": 0.04,
    "idp_sack": 2,
    "fgm_20_29": 0,
    "pts_allow_21_27": 0,
    "bonus_pass_yd_300": 1,
    "xpm": 1,
    "rec_40p": 1,
    "rush_2pt": 2,
    "fum_rec": 0,
    "idp_pass_def": 1,
    "def_st_td": 0,
    "pass_cmp_40p": 1,
    "fgm_50p": 0,
    "def_td": 0,
    "idp_fum_rec": 2,
    "rec_td_40p": 1.5,
    "safe": 0,
    "pass_yd": 0.04,
    "blk_kick": 0,
    "pass_td": 4,
    "idp_qb_hit": 0,
    "rush_yd": 0.1,
    "rush_40p": 2,
    "pr_yd": 0.04,
    "fum": 0,
    "pts_allow_28_34": 0,
    "pts_allow_35p": 0,
    "fum_rec_td": 6,
    "rec_yd": 0.1,
    "rush_td_40p": 0,
    "def_st_ff": 0,
    "pts_allow_7_13": 0,
    "idp_ff": 3,
    "st_ff": 1,
    "idp_tkl_ast": 0.25
  },
  "roster_positions": [
    "QB",
    "RB",
    "RB",
    "WR",
    "WR",
    "TE",
    "FLEX",
    "K",
    "DL",
    "DB",
    "IDP_FLEX",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN",
    "BN"
  ],
  "draft_id": "1326430722159816704",
  "avatar": "e7666f3276fce37438e1e566a76f00eb",
  "source": "sleeper",
  "managers": [
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 1,
      "user_id": "474788159146684416",
      "display_name": "McPeake1",
      "team_name": "The McCafrican Americans",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 436,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 2,
      "user_id": "682694793616236544",
      "display_name": "bloodyrichard",
      "team_name": "USPS Allstars ⭐️📦✅",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 484,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 3,
      "user_id": "683165664151670784",
      "display_name": "verbbb",
      "team_name": "verbbb",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 263,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 4,
      "user_id": "682704177654198272",
      "display_name": "Patek007",
      "team_name": "Cursed team",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 445,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 5,
      "user_id": "682684750502096896",
      "display_name": "mattkieser",
      "team_name": "Mike Vick’s Puppies🐶",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 380,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 6,
      "user_id": "682657697815961600",
      "display_name": "Marble221",
      "team_name": "Kirko Chainz Gang",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 544,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 7,
      "user_id": "682658309609697280",
      "display_name": "Purshdaddy",
      "team_name": "Glock Purdy 🔫",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 542,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 8,
      "user_id": "632483874114478080",
      "display_name": "PsychJawn",
      "team_name": "Shipping DeJawn",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 400,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 9,
      "user_id": "682991018118832128",
      "display_name": "BrettRV",
      "team_name": "Njigbas with Attitude ",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 256,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 10,
      "user_id": "682658216940728320",
      "display_name": "andyarm",
      "team_name": "First Down Syndrome ",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 614,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 11,
      "user_id": "609107523265437696",
      "display_name": "TheEnglishman43",
      "team_name": "Nick top Bobby bottom ",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 553,
      "total_moves": 0,
      "final_rank": null
    },
    {
      "year": 2026,
      "league_id": "1326430722143027200",
      "roster_id": 12,
      "user_id": "918211486294814720",
      "display_name": "WillGaul314",
      "team_name": "Golden Branch in Pants ",
      "wins": 0,
      "losses": 0,
      "ties": 0,
      "fpts": 0,
      "fpts_against": 0,
      "waiver_budget_used": 250,
      "total_moves": 0,
      "final_rank": null
    }
  ]
}
Champion7
data.champions[]
▸

Final finish per season. source='cbs' for pre-Sleeper years; 'sleeper' rows include the championship game row.

yearchampion_user_idrunner_up_user_idthird_user_idlast_user_idchampionship_gamesource
View sample row
{
  "year": 2026,
  "league_id": "1326430722143027200",
  "league_name": "United Dynasty Fantasy Football League",
  "total_rosters": 12,
  "status": "pre_draft",
  "champion_user_id": null,
  "champion_roster_id": null,
  "runner_up_user_id": null,
  "runner_up_roster_id": null,
  "third_user_id": null,
  "third_roster_id": null,
  "last_user_id": null,
  "last_roster_id": null,
  "championship_game": {
    "p": 1,
    "m": 7,
    "r": 3,
    "l": null,
    "w": null,
    "t1": null,
    "t2": null,
    "t2_from": {
      "w": 5
    },
    "t1_from": {
      "w": 4
    }
  },
  "source": "sleeper"
}
TradeEvent183
data.trade_events[]
▸

Flat transactions: adds, drops, traded picks, waiver-budget moves. Drives /trades, hindsight, trade trees.

yearweektransaction_idroster_ids[]adds{}drops{}draft_picks[]waiver_budget[]
View sample row
{
  "year": 2026,
  "week": 1,
  "transaction_id": "1353923413617434624",
  "created": 1777163005789,
  "roster_ids": [
    7,
    11
  ],
  "draft_picks": [
    {
      "round": 2,
      "season": "2027",
      "league_id": null,
      "roster_id": 2,
      "owner_id": 7,
      "previous_owner_id": 11
    },
    {
      "round": 2,
      "season": "2026",
      "league_id": null,
      "roster_id": 8,
      "owner_id": 11,
      "previous_owner_id": 7
    }
  ],
  "adds": {},
  "drops": {},
  "waiver_budget": []
}
MatchupTeam1,168
sleeper_raw[year].matchups[week][] (1168 rows total)
▸

One row per team per week. Two rows sharing a matchup_id = one game. starters_points + players_points feed records, H2H, and lineup efficiency.

roster_idmatchup_idpointsstarters[]starters_points[]players[]players_points{}
View sample row
— no sample —
BracketGame83
sleeper_raw[year].winners_bracket / losers_bracket
▸

Sleeper's compact bracket node. p=1 is the final, w/l are roster_ids, t1_from/t2_from chain the bracket.

mrt1t2wlpt1_fromt2_from
View sample row
{
  "m": 1,
  "r": 1,
  "l": null,
  "w": null,
  "t1": 8,
  "t2": 2
}
DraftPick530
lib/data.ts → draftPickByPosition
▸

Every draft slot across every year, keyed `${year}-${round}-${slot}`. Combined with allTradedPicks to render the rookie/start-up board.

draft_idrounddraft_slotpick_noplayer_idroster_idpicked_by
View sample row
{
  "year": 2020,
  "pick": {
    "draft_id": "cbs-2020-draft",
    "draft_slot": 1,
    "is_keeper": null,
    "metadata": {
      "first_name": "Christian",
      "last_name": "McCaffrey",
      "position": "RB"
    },
    "pick_no": 1,
    "picked_by": "474788159146684416",
    "player_id": "4034",
    "roster_id": 1,
    "round": 1
  },
  "user_id": "474788159146684416"
}

Aggregations

Career row

view →
data.careers[]
  1. Group every SeasonManager by user_id (joined via roster_id → owner_id per year).
  2. Σ wins/losses/ties/fpts/fpts_against across all seasons.
  3. Read final_rank per season → tally championships (rank=1), runner_ups (rank=2), thirds (rank=3), lasts (rank=N).
  4. win_pct = wins / (wins+losses+ties); avg_fpts_per_season = fpts / seasons.
USPS Allstars ⭐️📦✅ · 51-32-0 · 11442 PF · 2🏆

H2H matrix

view →
lib/analytics/h2h.ts → computeH2HMatrix()
  1. Walk every (year, week) in matchupsByYear.
  2. Group rows by matchup_id — pairs of length 2 are valid games.
  3. Resolve each roster_id → user_id via rosterToUserByYear[year].
  4. For each pair, increment cell[i][j] and cell[j][i] symmetrically: games, wins/losses/ties, pf, pa.
  5. Final cell = total record between user i and user j across the whole archive.
N×N symmetric — sortedUserIds defines the column order.

Records (highs, streaks, blowouts)

view →
lib/analytics/records.ts → computeRecords()
  1. Flatten every matchup pair into one game-row per team (allGames).
  2. Sort by pts ↓ for single_game_high, ↑ for single_game_low.
  3. margin = pts − opp_pts → max(margin) is biggest_blowout, min(margin>0) is closest_win.
  4. Per uid chronological feed → bestStreak scans for longest 'W' or 'L' run.
  5. Σ pts per (year, uid) → season_pf_high / season_pf_low.
single_game_high / season_pf_high / longest_win_streak feed the home page record book.

Rivalry intensity

view →
lib/analytics/rivalries.ts → topRivalries()
  1. Start from H2H allPairs() (every pair with ≥2 games).
  2. Annotate each pair by re-walking matchups: close_games (|margin| ≤ 7), blowouts (|margin| ≥ 25), playoff_meetings (week ≥ 15).
  3. Composite score = 0.35·balance + 0.25·close% + 0.15·blowout% + 0.15·playoff + 0.10·volume.
  4. balance = 1 − |a_wins − b_wins| / games; scaled ×100 → 0–100 intensity.
Top intensity weights: balance > close-games > blowouts ≈ playoff > volume.

Lineup efficiency

view →
lib/analytics/lineup_efficiency.ts
  1. For each (year, week), read row.players + row.players_points.
  2. Build optimal lineup greedily: walk roster_positions, strict slots first (QB/RB/WR/TE/K/DEF), then FLEX/SUPER_FLEX. Pick max-points eligible unused player per slot.
  3. actual_pts = Σ starters_points; optimal_pts = Σ greedy lineup pts.
  4. left_on_bench = max(0, optimal_pts − actual_pts); efficiency = actual / optimal.
  5. Career roll-up: Σ actual, Σ optimal, Σ left across every week per uid.
Greedy is optimal for this lineup grammar — strict slots can't be displaced by flex.

Trends

view →
lib/analytics/trends.ts
  1. scoring_inflation: Σ row.points across every week of a year ÷ matchup-team count → league_avg per season.
  2. trade_volume: count trade_events filtered by year.
  3. faab_usage: Σ waiver_budget.amount per year across trade_events.
  4. scoring_changes: diff scoring_settings between consecutive seasonByYear entries.
1 expansion event detected.

Same data, different views

/managers
data.careers — sorted
/h2h
computeH2HMatrix()
/records
computeRecords()
/rivalries
topRivalries() · intensity 0–100
/optimal/career
computeLineupCareer()
/trends
computeTrends()
Press BoxSupportPrivacyTerms
MyGM · United Dynasty FFL · MCP at /api/mcp