Site map & schema reference

App Info

Every player-facing page, and the database behind them, in one reference. For the admin side, see Admin Tour.

Pages Guide

A tour of every player-facing page in the site — what it's for, what's on it, and the route behind it. Previously its own page at /about/pages, which still redirects here.

Home /

The landing page. Shows the pool rules, scoring breakdown, and payout structure.

  • Displays group scoring rules and tiebreaker explanation.
  • Shows payout table — example payout shown until enough paid entries are confirmed; actual pool amount shown once the threshold is reached.
  • If you're logged in, shows your entries for the current year with quick links to edit or view.
My Entries /my-entries

Your personal dashboard. Requires login.

  • Lists all your brackets for the current year.
  • Before lock: Edit and Delete buttons are available.
  • During tournament: shows a "How am I Doing?" card with your current rank, total points, how far behind 1st place you are, and how many points ahead of the next entry.
  • Links to each entry's full detail view and the Standings page.
Submit / Edit Entry /enter & /edit/<id>

Create or update a bracket. Requires login; locked once tournament goes live.

  • Bracket name must be unique per year.
  • Players are grouped by seed range. Pick the required number from each group.
  • Group 5 (Coaches) — pick 4 teams whose coach earns 10 pts per win.
  • Tiebreaker: predict the total combined points scored in the championship game.
  • Confirmation email sent via AWS SES on submit.
Current Standings /leaderboard

Full ranked leaderboard of all entries.

  • Columns: Rank, Bracket, Tiebreaker, Name, Remaining picks, per-round scores, Predicted total, Score.
  • Click any column header to sort ascending or descending.
  • Search box filters by bracket name or participant name.
  • Select any two entries with the checkboxes, then click Compare Selected to jump to the Compare page.
  • Live game scores widget refreshes every 30 seconds.
  • Predicted column links to that entry's full prediction breakdown.
Details (All Entries) /report/entries

Per-player breakdown for all entries. The most detailed view.

  • Every entry's picks shown with round-by-round points.
  • Filter by bracket, player status (In Play / Active / Eliminated), seed group, or round.
  • Sort by points earned in any direction.
  • Color-coded status: green = in play, muted = active (not yet played), red/strikethrough = eliminated.
  • Click any bracket name to jump to that entry's detail view.
Compare Entries /compare

Head-to-head comparison of any two brackets.

  • Select Entry A and Entry B from dropdowns (cross-excluded so you can't pick the same twice).
  • Three columns: Only Entry A | Only Entry B | Both Picked.
  • Filter by player status. Rows sorted: In Play → Active → Eliminated.
  • Score difference and current totals shown at a glance.
  • Can also be launched directly from the Standings page checkboxes.
Prediction /predict

Projects each entry's final score based on remaining players and expected wins.

  • Shows current actual score + predicted future points side by side.
  • Filter by bracket name. Sort by predicted total or actual score, ascending or descending.
  • Useful for seeing who could still climb or fall in the standings.
  • Prediction logic uses seed-based expected advancement (Seeds 1–4 project to Final Four by default).
Today's Games /all-games

All tournament games scheduled for today.

  • Shows in-progress games (with live score and game clock) and upcoming games (with tip-off time ET).
  • Each game card links to the ESPN box score.
  • 2 columns on mobile, 4 columns on desktop.
  • Also appears as a live widget on Standings, Upsets, and Last Update pages.
Upsets /upsets

Every upset in the tournament — cases where a higher seed eliminated a lower seed.

  • Left panel: Eliminated teams, who beat them, the round, and how many entries had players from that team.
  • Right panel: Upset winners (Cinderellas), with a Nx badge and row highlight if a team pulled off multiple upsets.
  • Badge count on the left links to the Details page to see which entries are affected.
Player Data /tourney-data

How popular is each player? How much have they scored?

  • Shows every player and coach available for drafting, with their seed group and season PPG.
  • Brackets column: how many entries picked this player.
  • Round-by-round actual points accumulated.
  • Filter by seed group, player status, or search by name/team.
  • Helpful for scouting before submitting an entry.
Submitted (Roster) /roster

Simple list of all entries and their payment status.

  • Shows participant name, bracket name, and a paid indicator.
  • Useful for the pool organizer to verify who has paid.
  • Entry count shown at the top.
Last Score Update /last-update

Log of the most recent score-fetch run from ESPN.

  • Shows each game processed, with win/loss results and any errors.
  • Filter by status: All, Winners, Playing, OK, Errors.
  • Timestamp of the last run shown in the header.
  • Useful for diagnosing score loading issues.
Daily Update /daily

AI-generated bracket commentary — who's leading, the biggest mover, who's struggling — regenerated on demand with a shared code, no login required.

  • Shows the latest published update, or a "check back soon" notice if none has been generated yet this year.
  • A code-gated "AI feature" link leads to /daily/live, the same generator-comparison view used to build this page — Claude direct, Spark via queue, and the two agentic tool-calling generators, side by side, with a live Progress timeline.

Database Design

Every table, what it's for, and how the core tables relate — kept accurate against the running MySQL schema, not just the migration files (see Deploy for how schema changes actually reach production). Previously its own page at /about/db, which still redirects here.

Entity Relationship Overview

High-level relationships between the core tables.

erDiagram
    users ||--o{ entries : "submits"
    entries ||--o{ picks : "contains 22"
    players ||--o{ picks : "is picked in"
    teams ||--o{ players : "rosters"
    teams ||--o{ player_pts : "scores as"

    users {
        int id PK
        string full_name
        string email
        string cell
    }
    entries {
        int id PK
        int user_id FK
        int year
        string bracket_name
        int tiebreaker
    }
    picks {
        int id PK
        int entry_id FK
        string espnPlayerId FK
        int groupId
    }
    teams {
        string espnTeamId PK
        string teamName
        int seed
        int gameYear
    }
    players {
        string espnPlayerId PK
        string espnTeamId FK
        string playerName
        float ppg
        int gameYear
    }
    player_pts {
        string espnPlayerId FK
        string espnTeamId FK
        int pts
        int round
        string result
        string coach
    }
      
users
ColumnTypeNotes
idINT PKAuto-increment
full_nameVARCHAR(100)
emailVARCHAR(200)Unique; used for login
cellVARCHAR(20)Unique; formatted (XXX) XXX-XXXX
password_hashVARCHAR(255)Werkzeug PBKDF2 hash
created_atTIMESTAMPAuto
entries
ColumnTypeNotes
idINT PKAuto-increment
user_idINT FK→ users.id
yearINTTournament year
bracket_nameVARCHAR(100)Unique per year
full_nameVARCHAR(100)Copied from user at submit
tiebreakerINTChampionship total guess
tokenCHAR(64)Magic link access
paidTINYINT(1)0 or 1
picks
ColumnTypeNotes
idINT PK
entry_idINT FK→ entries.id (CASCADE DELETE)
espnPlayerIdVARCHAR(20)→ players.espnPlayerId
groupIdINT1–5 (seed group / coaches)
password_reset_tokens
ColumnTypeNotes
idINT PK
user_idINT FK→ users.id (CASCADE DELETE)
tokenCHAR(64)Unique random hex
expires_atDATETIME24 hours from creation
usedTINYINT(1)0 = active, 1 = consumed
Tournament Data Tables

These tables are loaded from ESPN data by the score-fetching pipeline and are not user-editable.

teams

ColumnNotes
espnTeamIdESPN team identifier
teamNameFull team name
seed1–16
gameYearTournament year
play_inFlag: play-in game team
play_in_result'undecided' / 'in' (advanced) / 'out' (eliminated)
predict_from_roundRound scoring begins (play-in adjustment)
outFlag: eliminated from the tournament
predict_finalsFlag: seed projected to reach the Final Four
rankAdmin-set ranking, nullable
espnRankESPN's own ranking, nullable

players

ColumnNotes
espnPlayerIdESPN player ID
espnTeamId→ teams.espnTeamId
playerName"Coach" for coaching picks
ppgSeason points per game
pickDisplay string shown in entry form
gameYearTournament year

player_pts  (one row per player per game)

ColumnNotes
espnPlayerIdPlayer or team ID (for coach rows, equals espnTeamId)
espnTeamIdThe team that played
vsEspnTeamIdThe opponent team
ptsPoints scored in this game (actual or 10 for coach wins)
round1 = Rd 1 … 6 = Championship
result'win', 'loss', 'In-Play'
gameIdESPN game ID
gameYearTournament year
seedTeam seed
coach'Y' = coach/team row, 'N' = player row
Operational Tables

settings  (key-value store)

KeyPurpose
active_yearCurrent tournament year
entries_live'Y'/'N' — lock flag
entries_live_atScheduled go-live datetime
tournament_roundsNumber of rounds (default 6)
payout_example_thresholdPaid entries needed to show real pool
scores_*Score-fetcher config (date, round, frequency)

score_run_log

ColumnNotes
run_idUUID for each fetch run
game_idESPN game ID (if game-specific)
messageLog message text
statusinfo / winner / playing / ok / error
created_atLog timestamp

tournament_config

ColumnNotes
yearUnique per year
stateSETUP / OPEN / LOCKED / IN_PROGRESS / COMPLETE
lock_timeWhen entries locked
current_roundRound the tournament is on
Season Games & Odds Tables

Regular-season data (never NCAA tournament games) and round-of-64 betting lines, loaded by the admin Workflow steps — see Big Picture and Data Warehouse for how this feeds analysis.

games  (one row per team per regular-season game)

ColumnNotes
espnGameIdESPN game ID
espnTeamId / opponentEspnTeamIdThe two teams
gameYear / gameDateSeason year and date
game_typeRegular season / conference tournament
result / siteWin/loss, home/away/neutral
opponent_conference / opponent_conference_tierOpponent's conference, High/Low tier
opponent_final_wins / opponent_final_lossesOpponent's final season record (not as-of-game-date)
opponent_point_diff / opponent_standing_summaryOpponent strength context
team_streak_enteringWin/loss streak entering this game (signed)

player_game_pts  (one row per player per regular-season game)

ColumnNotes
espnPlayerIdPlayer
espnTeamId / espnGameIdTeam and game
gameYear / gameDateSeason year and date
ptsPoints scored in this game
game_typeRegular season / conference tournament

game_odds  (one row per team's round-of-64 odds)

ColumnNotes
espnTeamId / opponentEspnTeamIdThe matchup
gameYear / gameDateTournament year and date
spread / moneyline / totalBetting lines
favorite_espnTeamIdWhich team was favored
result / is_upsetOutcome, whether the underdog won

season_games_load_status  (completion marker, one row per team per year)

ColumnNotes
espnTeamId + gameYearComposite primary key
completed_atOnly written once a team's full season-game loop finishes — lets an interrupted import re-run cleanly instead of skipping partial data
Daily Commentary Tables

Support the admin AI-generated daily update feature (Claude via MCP).

daily_commentary

ColumnNotes
yearTournament year
contentGenerated commentary text
generated_atWhen it was generated
scores_snapshotScore state at generation time

daily_scores_cache

ColumnNotes
yearPrimary key
snapshotScore snapshot, for next-day comparison
saved_atLast saved

commentary_jobs

ColumnNotes
year / statuspending / running / done / error
result / error_messageJob outcome
created_at / updated_atTimestamps