Skip to content

Season Discovery

Season discovery creates the canonical game inventory before any game-level play-by-play processing begins.

Direct NBA source

Historical discovery calls the NBA Stats schedule endpoint directly:

GET https://stats.nba.com/stats/scheduleleaguev2
    ?LeagueID=00
    &Season=2025-26

The public NBA schedule page uses a regional CDN document such as scheduleLeagueV2_1.json. That file is useful for the current schedule but its URL is not season-addressable. The NBA Stats endpoint accepts an explicit Season value and is therefore the source for repeatable historical discovery.

The project does not use an NBA client package. It owns the HTTP request, result set parsing, cache, and canonical mapping.

Discover a season

uv run nba-discover-season 2025-26

The command writes:

data/raw/scheduleleaguev2/
  2025-26.json
  2025-26.meta.json

data/catalog/
  games.parquet

The raw JSON contains the exact response bytes. Its sidecar records the season, request URL, UTC fetch time, and SHA-256 digest. A valid cached response is reused by default.

Use --refresh to request a new source response:

uv run nba-discover-season 2025-26 --refresh

Catalog update semantics

games.parquet is a multi-season catalog. Discovering a season replaces all existing rows for that season and retains every other season. This makes a repeated discovery idempotent and lets a corrected NBA schedule remove as well as add games.

Use --replace-catalog only when the output should contain the newly discovered season and nothing else.

Source mapping

The normalizer validates the nested leagueSchedule, game-date, game, and team objects and rejects malformed rows or source seasons that differ from the request.

Source evidence Canonical field
gameId Lexical ten-character game_id
seasonYear season
gameDateEst League schedule game_date
gameDateTimeUTC Optional UTC game time
gameStatus, gameStatusText Canonical game status
Home and away team fields Integer IDs and string tricodes
Labels plus game ID prefix Normalized season_type

Known NBA game ID prefixes map to preseason, regular, all_star, playoffs, play_in, and nba_cup_final. The prefix is authoritative because NBA Cup quarterfinals and semifinals have 002 regular-season IDs even though their labels contain the word "final." Text labels are a fallback for an otherwise unknown source prefix.

Overtime evidence

The schedule does not provide a dedicated final-period field. It distinguishes regulation finals from overtime in gameStatusText: Final, Final/OT, and Final/OT2 appeared in the 2025-26 response. Discovery maps those values to four, five, and six periods respectively and accepts the alternate Final/2OT form defensively.

Scheduled and live games retain null period and overtime fields. Exact final period counts can also be checked against the game boxscore when the season execution pipeline processes the catalog.

Failure policy

Discovery does not fall back to a third-party schedule. HTTP failures, non-JSON responses, nested schedule contract drift, duplicate game IDs, unknown game ID prefixes, and invalid team identity all fail the command before the catalog is rewritten.