2026 · DATA ENGINEERING | FINANCE
NBA Salary Cap Tracker
Where every NBA team's payroll stands, what they owe in luxury tax, and what they're allowed to do next — backed by the actual CBA bracket math.

Tech Stack
Overview
The NBA luxury tax works like income tax: each bracket rate applies only to salary within that band, not the total. That distinction matters — it's why Phoenix owes $101M in tax while other teams just over the line owe a fraction of that. I built a Python engine that implements the full CBA formula and verified it with 27 unit tests against known boundary values. The frontend shows where each team sits relative to the cap, tax line, and both aprons, and lists what they can and can't do based on their tier.
What I Built
- Custom luxury tax bracket engine — mathematically correct CBA bracket calculations with 27 passing unit tests
- All 30 teams with full player salary tables across 3 seasons (2026-27, 2027-28, 2028-29)
- SVG CapThresholdBar — visual payroll position relative to cap, tax line, 1st apron, and 2nd apron
- Per-team restriction engine — dynamically lists what each team can and cannot do based on their tier
- League summary dashboard — total payroll, total tax collected, teams in each tier
- 3-year payroll projection per team as contracts expire
- GitHub Actions daily validation — 30-team seed data validated and engine tests run every 24 hours
- Sort and filter by payroll, tax bill, cap space, conference, and tier
Screenshots

Dashboard Overview

Filter Controls
What I Learned
The NBA luxury tax is an incremental bracket system like income tax — each rate applies only to salary within that band, not the total
Repeater tax adds $1.00 to every bracket rate for teams that paid tax in 3 of the prior 4 seasons — this is what drives Phoenix's $101M bill
Vite non-interactive scaffolding silently no-ops when the target directory isn't empty — always verify file count before reporting build success
GitHub Actions exit-code gating: validation exits 1 on bad data, stopping the workflow before stale data reaches cache
Render prefers .python-version over runtime.txt — include both for reliability across deploy environments