Skip to main content

Route

/benchmarks — BenchmarksPage.vue

Access

  • Summary cards — available to all tiers
  • Benchmark grid — Premium only (standard users see upgrade prompt)
  • Position chart — available to all tiers

Filter Sidebar (all functional)

FilterTypeEffect
View modeRadio groupControls which comparison columns show (Conference / Peer / School Only)
ConferenceLocked displayUser’s conference, not editable
Reporting PeriodDropdownFrom /benchmarks/filtersreporting_periods
SportDropdownFilters all 3 queries (summary, grid, positions)
PositionDropdownFiltered by selected sport, narrows summary + grid
Eligibility YearChip buttons (All, 1-5)Filters to athletes in that year
Comp TypeChip buttonsFilters to deals with that compensation type
Peer GroupConference chipsShows custom peer group (configured in Settings)
All filters are reactive — changing any value immediately refetches data via TanStack Query computed keys.

Summary Cards (4)

Each card shows “Yours” vs “Conference Avg” with a delta percentage pill.
  1. Avg NIL / Athletetotal_value / athletes_with_deals for school vs conference
  2. Avg Agreement ValueAVG(total_value_usd) across filtered deals
  3. % Athletes with Agreement — athletes with deals / total athletes
  4. Total NIL ValueSUM(total_value_usd) for school vs conference

Benchmark Grid (Premium)

Three-way comparison table: Your School vs Conference vs Peer Group. Each group shows Avg / Guaranteed / Performance sub-columns. Delta columns show percentage difference vs Conference and vs Peer Group. How it’s computed (GET /benchmarks/grid):
  • School: _agg() with university_id == user's
  • Conference: _agg() with university_id IN (same conference)
  • Peer Group: _agg() with university_id IN (universities in peer group conferences)
  • Percentiles: PostgreSQL percentile_cont(0.25/0.50/0.75) on per-athlete totals

Position Bar Chart

Vertical bar chart showing Avg NIL per position for the selected sport. Three bars per position (Yours / Conference / Peer) with hover tooltips showing exact values and athlete count. Chart mode toggle (Total / Guaranteed / Performance) changes which value drives the school bars:
  • Total → school_avg
  • Guaranteed → school_avg_guaranteed
  • Performance → school_avg_performance
View mode controls which comparison bars are visible.

Backend Engine

All benchmark data is computed from real PostgreSQL aggregation queries — no hardcoded or fake values. The _agg() helper function accepts sport_id, position_id, eligibility_year, and comp_type filters and applies them to every aggregation (school, conference, peer).
# Example: filtered benchmark aggregation
async def _agg(db, uni_filter, sport_id=None, position_id=None, eligibility_year=None, comp_type=None):
    q = select(
        func.avg(NilDeal.total_value_usd),
        func.count(NilDeal.id),
        func.sum(NilDeal.total_value_usd),
        ...
    ).where(uni_filter, NilDeal.deal_status == "active")
    if eligibility_year:
        q = q.join(Athlete).where(Athlete.eligibility_year == int(eligibility_year))
    if comp_type:
        q = q.where(NilDeal.compensation_type == comp_type)
    ...