The public NBA pages
Written: 2026-10-01 (Job BJ), the declarations first and the code after. Status: built 2026-10-01. Everything under "Declared" was written into this file and committed before the code was written and before any figure on the pages was read; anything added after is marked "added".
Four pages anyone may read, signed in or not, with no league: the NBA's schedule in ordinary weeks, who rests, each team's pace, and who has historically taken a starter's minutes when he sits. They are the NBA's facts and nothing else: no league, no team of anybody's, no member, no account. Every figure on them describes what is scheduled or what was played, with the sample behind it. Nothing on them predicts a night, ranks a man for a move or tells anyone what to do. A tool, not gospel.
Why (the owner, 2026-10-01)
The site should be found when people search for fantasy basketball. The broad search belongs to the big sites; the searches a small site can answer are specific and weekly: the NBA schedule this week, back-to-backs this week, a load management tracker, NBA team pace. Each page answers one.
Declared
The pages
| Address | Title (the search it answers) |
|---|---|
/nba/schedule |
NBA schedule this week for fantasy basketball: games, back-to-backs, light nights |
/nba/rest |
NBA rest tracker: load management and back-to-back nights off |
/nba/pace |
NBA team pace for fantasy basketball: possessions per game, last 15 games |
/nba/minutes |
When an NBA starter sits: who takes his minutes, team by team |
/nba goes to /nba/schedule (a 307, by a path, never a full address).
Each page has its own title, description, canonical address and share card words, is in the sitemap, is allowed in robots.txt, is linked from the landing page's foot and from the other three, and ends with one quiet line: "See this for your own league: connect it", to League connection (which asks a signed-out reader to sign in first and comes back).
What they read, and what they never read
Only the NBA-wide tables: the schedule (pro_team_games), box scores
(player_game_stats), the NBA's official injury reports
(injury_reports), a man's season position (player_season_stats), his
NBA team from ESPN's status reads (player_status_snapshots, the team
alone) and his name and date of birth (players). Never a league, a
league season, a fantasy team, a roster, a matchup, a member, an account
or anything typed by anybody. One exception, said here: the schedule
grid's calendar (season_calendar) clamps a played season to the last
fantasy matchup day stored; these pages do not use that clamp and date
the days themselves.
The season and the day
- The season in play is ESPN's for today's date (
current_season): 2026-27 from October 1, 2026. - The day is today's date in US Eastern time, the NBA's own calendar, and its ESPN scoring period (one a calendar day, numbered from opening night).
- Before the season (no box score of the season in play before today): each page says when the season starts and shows last season's figures, labelled as last season's. The schedule page is the exception it has to be: its subject is the coming schedule, so it shows the coming season's first weeks, and says the season has not started.
- After the regular season, until the next season is in play: each page shows the season as it finished, and says so.
- The dated line, at the top of each page: "Updated Wed, Oct 15, from box scores through Tue, Oct 14": the day it was built and the date of the latest game with a box score in the season shown.
Schedule (/nba/schedule)
The grid's counting, unchanged (app/inseason/nba_schedule.py,
docs/schedule_grid.md): a back-to-back is a team playing two days in a
row, counted on its second night; a light night is a day with
LIGHT_NIGHT_MAX_GAMES (5) or fewer games; the week's usual is the median
of the 30 teams' games that week, the lower middle; a cell's shade is the
same three steps against it.
- A week is a standard one, Monday to Sunday (Eastern dates). A public page has no league, and a league's weeks are its own (a two-week All-Star period, its playoff weeks): the page says so and points to a league's own Schedule page, which a connected league has. Week 1 is the week holding opening night; the last is the week holding the regular season's last game.
- This week is the week holding the day; next week the one after. Before opening night the first two weeks stand in for them, named as the season's first two.
- The table: the 30 teams, sorted by this week's games then next week's: this week's games (and how many are left from the day, the day included), its second nights, next week's games, its second nights, the games left in the season and the second nights left. Shaded as the grid is.
- Light nights of this week and next: the date, its games and the teams playing, with the grid's one line about them and nothing more.
- The season, every week across, in its own frame that scrolls sideways: the grid's own picture, in standard weeks.
- The NBA Cup: ESPN's schedule holds 80 games a team before the Cup's group stage is played; the NBA adds the other two after it. The page says so while any team has fewer than 82.
Rest (/nba/rest)
The rest tracker's rules, unchanged (app/inseason/rest.py,
docs/rest_tracker.md): the same regular (5 or more games for the team,
10.0 or more minutes over his last up to 10), the same classes (the NBA's
official report where a season has one, else the declared rule: sat the
second night of a back-to-back, having played the first night and his
team's next game), the same sample after every rate.
- Last season whole, and this season so far (before the day). Before the season, last season alone, and the page says so.
- The list: every man with at least one rest night in either season, in the tracker's order (rest nights last season, then this season so far).
- Columns: the man, his NBA team, games rested of games as a regular and second nights rested of second nights, for each season shown; his minutes role; his team's back-to-backs left in the season (from the day, through the regular season's last day: a public page has no fantasy playoffs); and his second-night record in the tracker's own words ("Rested 9 of 14 second nights last season, 3 of 11 in 2023-24").
- No REST RISK tag, and nothing about tonight. The tag is the league pages', on a second night only. Here the record's words stand alone, in bold when they are at or over the tag's bar (25% of at least 8 second nights), as the league page shows them.
- Which seasons were read from reports and which by the rule is said under the table.
- Whose box scores: the page reads the box scores Box Out holds, which are every man a connected league has rostered and, in season, its free agents: about 85% of a past season's minutes, not every NBA player. A deep-bench man can be missing, so "how it's counted" says so under "A regular" (rest) and "His teammates" (minutes). The team totals cannot fill this in: they have no men. Added 2026-10-07.
- Whose box scores: the page reads the box scores Box Out holds, which are every man a connected league has rostered and, in season, its free agents: about 85% of a past season's minutes, not every NBA player. A deep-bench man can be missing, so "how it's counted" says so under "A regular" (rest) and "His teammates" (minutes). The team totals cannot fill this in: they have no men. Added 2026-10-07.
Pace (/nba/pace)
The team context study's measure (app/inseason/pace.py):
- A team's possessions in a game: its FGA - OREB + 0.44 FTA + TO (the
players' own turnovers), per 48 minutes: times 240 over five men's
minutes of the game, 25 more an overtime. From each team's own box
totals (
nba_team_games, every man who played; ESPN's game summary, read the night after, docs/espn_exit_plan.md R15). A game's pace: the mean of the two teams' figures. A game whose totals are not stored yet is counted as before, from the box scores of the men Box Out tracks (their minutes, when they played 120 or more a side). One function chooses (app.inseason.pace.season_game_paces), and says which. - Games is every game with a pace: 82 a team and 1,230 for the league
in a whole season once its totals are stored. The line under the
league's average names the basis, in one short sentence: "From each
team's box totals." when every played game's pace is; "From the men we
track, N games short." before any totals are stored; "From each team's
box totals, N games from the men we track, M games short." between
(a night whose totals have not landed yet). Played games are the final
scores stored (
nba_games), else the schedule's games with a box score. - A team's pace here is the plain mean of its games' paces this season so far, with the count beside it. Not shrunk to the league: the study shrinks it with 5 games at the league's mean to forecast a game, and this page describes what was played.
- Rank 1 is the fastest. Last 15: the mean over its latest 15 games with a pace, and the change from its season figure; with 15 or fewer games the change is not shown.
- Until the season has 30 games with a pace (
MIN_LEAGUE_GAMES) the page shows last season's, labelled. - What it means for fantasy, and no more: the study measured that, with his minutes held where they were, a man's points follow about 0.63 of the change in his game's expected pace, rebounds and assists about 0.25, his percentages not at all; held out on 33,858 player-games of 2024-25 and 2025-26. The expected pace sits within 4% of a man's usual in nine games of ten, so at those edges it moves his points by about 2.5%: half a point for a 20-point scorer. The page says that, with the numbers, and nothing that ranks anybody by it.
When a starter sits (/nba/minutes)
The minutes study's share rule M1, refitted on all five seasons
(REFIT_SHARES in app/inseason/minutes_bump.py, the "minutes may rise"
label's rule): a teammate gains the absent man's recent minutes times one
of six shares, by how their positions meet (same group, adjacent, far)
and whether the teammate starts.
- The starters: per team, each man placed on it (the study's placing) whose latest game is for it, with 5 or more games for it and recent minutes (his last up to 10) of 20.0 or more, starting (mean games-started flag 0.5 or more over those games).
- His teammates: men whose latest game is for the same team, with a baseline: 3 or more of their last up to 10 games for it on which the starter played; starter or bench by the same flag over those games; position group G, F or C from the season's primary position. Added while writing the code, before any figure was read: a teammate is also a regular of the team (10.0 or more minutes over his last up to 10, needing 5), so the page applies the rule to the team's rotation, as the brief asked, and not to a man with three garbage-time games.
- The words: "If X sits: about +3 minutes to A, +2 to B and +1 to C": every teammate the rule gives 1.0 minute or more, the three largest, each rounded to the whole minute. With none, "No teammate gains a whole minute by the rule: his minutes spread thin."
- Labelled as the study's measured average, with its sample: 52,975 teammate-games, 2021-22 to 2025-26; and its test: men it named for 3 or more minutes played 3 or more extra in 61% of games, against 24% on ordinary nights (6,103 named teammate-games held out). And the study's own warning: most of a missing man's minutes go to men outside the rotation, whom the rule does not name.
- The rotations are as of the morning of the day, this season's games. Before the season, last season's as it ended; a man whose latest ESPN status for the season in play puts him on another NBA team is left out on both sides, and the page says how many.
- Whose box scores: the page reads the box scores Box Out holds, which are every man a connected league has rostered and, in season, its free agents: about 85% of a past season's minutes, not every NBA player. A deep-bench man can be missing, so "how it's counted" says so under "A regular" (rest) and "His teammates" (minutes). The team totals cannot fill this in: they have no men. Added 2026-10-07.
- Whose box scores: the page reads the box scores Box Out holds, which are every man a connected league has rostered and, in season, its free agents: about 85% of a past season's minutes, not every NBA player. A deep-bench man can be missing, so "how it's counted" says so under "A regular" (rest) and "His teammates" (minutes). The team totals cannot fill this in: they have no men. Added 2026-10-07.
Server-rendered, cached daily
- The content is in the HTML. The tables and their words are written on the server, so a search engine reads them without running a script. The page's script only adds the theme switch and sorting.
- A snapshot a day. The rendered page is kept in the API's process,
one per page, against a key: today's Eastern date and the stored box
scores, schedule and reports of the seasons it reads (count and newest
id). The warm-up thread (
app/warm.py) checks the key once a minute and rebuilds a page off the request's path when it has changed: on a new day, and when the nightly pass's ingest has landed last night's box scores. A request that finds no snapshot for its key builds one, one at a time. Browsers and proxies may keep a page for 15 minutes. - Every visitor gets the same page: nothing on it depends on who asks.
Look
The landing page's house style for plain pages: its bar (with the four pages as its links, the page in view underlined in orange), its masthead, its section rules; tables at the workstation's metrics on a panel that scrolls inside itself at 390 wide. Light and dark from the tokens. No new colour, no new pattern.
Found (added 2026-10-01, after the build)
Read on a pg_dump copy of the local database on Thu, Oct 1, 2026, before the 2026-27 season: every page is in its pre-season form.
- Schedule: 2026-27 in 25 standard weeks. Its first week (Mon, Oct 19 to Sun, Oct 25): most teams play 3, PHL 4, five teams 2; 13 teams have a back-to-back; five light nights in the first two weeks. ESPN's schedule holds 80 games for every team, so the NBA Cup line shows.
- Rest: 2025-26, 173 of 343 regulars rested at least once; most, Robert Williams III (14 of 72 games), Kevin Love (13 of 72), Mitchell Robinson (10 of 71), as the league tracker found.
- Pace: 2025-26 as served on 2026-10-07 before the totals: CHI fastest at 110.5 possessions per 48, BKN slowest at 100.1, the league's mean 105.4 over 1,154 games, teams from 53 (CHI) to 82: the men tracked are about 85% of a past season's minutes and a game needed 120 of them a side. From the team totals (migration 0064, backfill queued after the deploy) every team has its 82 and the league its 1,230; the figures move (team totals count every man's possessions, so the level is the NBA's own, about 100 in 2025-26) and are to be read off the page once the backfill lands.
- Minutes: 177 starters on 30 teams from last season's rotations; 75 regulars ESPN now lists on another team are left out; 2 starters have no teammate the rule gives a whole minute.
- Cost: written cold in 0.4, 0.8, 0.2 and 0.15 seconds (schedule, rest, pace, minutes); a kept page is one fingerprint read (four small counts: the box scores, the schedule, the injury reports and the team totals).
- Titles follow the site's own rule, the subject then " · Box Out", not the brief's example "| Box Out": a page's title says its subject in the search's words either way.
- The sitemap names full addresses on the configured public address,
else
https://boxoutfantasy.com: a relative address is not a sitemap entry.
The examples in the sitemap, and the redirects (declared)
- The examples (
/sample/{page}, docs/site.md "Examples") werenoindexsince 2026-09-27: a reader was sent to them, a search engine was not. On the owner's word of 2026-10-01 the seven pages are indexed and in the sitemap, each with a title and a description of its own and a canonical address (the trade example's is its judged deal's, the address it is served at). Their frozen answers (/pages/static/sample/data/…) and the/sampleredirect staynoindex: they are data and a pointer, not pages. - Every redirect stays on https.
https://boxoutfantasy.com/sample/answered 307 tohttp://boxoutfantasy.com/sample. The cause, as read before the fix: the trailing slash is the web framework's own redirect (Starlette'sredirect_slashes), which writes a full address from the request's scheme; the proxy reaches the server over plain http on the tailnet and the server does not take the proxy's forwarded scheme, so the address it writes says http. The fix: a redirect to this site's own host is sent as a path (/sample), which a browser resolves against the https address it is on. Every redirect the site makes is checked for the same fault and a test holds it.
Code and routes
app/nba_public.py (the figures), app/api/nba.py (the routes, the
rendered HTML and the cache), app/api/static/nba.html (the page around
them). Tests: tests/test_nba_public.py.