Database Design

Entity Relationship Overview

High-level relationships between the core tables.

  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  users   β”‚ 1───n  β”‚    entries        β”‚ 1───n  β”‚ picks  β”‚
  β”‚ (id)     β”‚        β”‚ (id, user_id,     β”‚        β”‚(id,    β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚  year, bracket)   β”‚        β”‚entry_idβ”‚
                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚player_id)
                                                   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”˜
                                                        β”‚ n
                                                        β”‚
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                    β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
  β”‚  teams   β”‚ 1───n  player_pts  n───1  β”Œβ”€β”€β”€β”€β”€β”€β” β”‚  players   β”‚
  β”‚(espnTeam β”‚        (espnTeam,         β”‚teams β”‚ β”‚(espnPlayer,β”‚
  β”‚  Id,     β”‚         espnPlayer,       β””β”€β”€β”€β”€β”€β”€β”˜ β”‚ espnTeam,  β”‚
  β”‚  seed,   β”‚         pts, round,                β”‚ playerName,β”‚
  β”‚  year)   β”‚         result, coach)             β”‚ ppg, year) β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        
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)

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

allTeams  (season schedule)

ColumnNotes
espnTeamIdTeam
gameIdESPN game ID
resultWin/loss
gameYearSeason year
vsEspnTeamIdOpponent
name / vsNameTeam names
rank / vsRankAP poll ranks