You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The new pin rewriting refused its own first real bump, and now does not. Its
post-rewrite check greps the whole tree for the old pin, so it fired on a
comment inside bump_version.py - and would have fired on CHANGES.md at
the next release, because a released entry quoting the tag it shipped under
is a stale pin on purpose. The check honors the same exclusions the
discovery does, and a test covers the case.
The version directives on this release's new public symbols name 4.1.0, not
4.0.2. Five agents working in parallel were each told 4.0.2, correct for a
release of fixes; GET /api/coverage is a new endpoint, which makes this a
minor bump. Corrected in the pre-release audit, which is what that audit is
for - and the pinned install commands were rewritten by the bump script
itself this time (#60), rather than by hand minutes before the tag.
The web page now reports GET /api/coverage, what the warehouse actually
holds grouped into the three tiers a question can land in - box score, box
score plus play-by-play, and both of those plus NetPoints - and the page
renders it as a row of pills under the header. This replaces /api/health's
single min/max season as the page's only claim about coverage, which read as
"the whole span is answerable" when only its narrowest table was (#71): a
2016 shot chart and a 2016 NetPoints fingerprint looked equally reasonable to
ask for, and only one of them was. Each tier's floor is read straight from association.nba.coverage.COVERAGE - the same enforced table a template's
own refusal reads - rather than recomputed from row counts, so the page and
a refusal cannot say two different things about the same season; only how
far a tier's data currently reaches (last_season) is counted live, since no
floor records that. A season inside a tier's range but declared partial
there (2002's play-by-play, 2002-2003's shot chart) is marked rather than
shown as uniformly whole, and 1993 - ESPN's phantom copy of 1994 - is
excluded from the box score tier's range rather than offered.
A shooting-percentage leaderboard's qualifier now scales to a shortened
season instead of applying an 82-game-calibrated floor flat.ts_pct
(550 true-shooting attempts), efg_pct (480 field-goal attempts) and fg_pct (400 field-goal attempts) were measured against the warehouse: at
the flat floors, 2020 qualified 157 players and 2021 155, against 174-184
in every full season measured (2019, 2022-2026), and the 66-game 2012
lockout season qualified 127 - a published rule scales per team game, so
this was a stricter qualifier than the one it claimed to be, not a missing
one. LeaderboardMetric.scales_with_schedule marks the three floors this
applies to; leaderboard.default_min_sample scales them from the season's
own MEDIAN real_games team-game count (72 for 2020 and 2021, 66 for
2012), rounding half up, and falls back to the flat floor when real_games is unavailable. Re-measured: 2020 moves to 171-183 across the
three metrics, 2021 to 176-220, 2012 to 176-211, and every full season is
unchanged (proven identical, not just close, since the scaling factor at
82/82 is 1). LeaderboardResult.min_sample_applied - already wired into
the answer text - now names the real, scaled number rather than the flat
one. three_pt_pct and ft_pct share the identical shape and are not
scaled yet - see ISSUES.md.
NetPoints per-date rows that fail the exact display-name match now keep
the source name instead of dropping it.parse_net_points_daily and parse_net_points_daily_players used to write athlete_id=None with
nothing else on the row, so a query grouping by (event_id, athlete_id)
counted every unmatched row on a date as a duplicate of every other -
2,190 rows in net_points_player_game and 64,210 in net_points_player_game_fingerprint, measured 2026-09-18. Both tables now
carry display_name on every row (matched or not), and net_points_player_game also carries NBA.com's own nba_player_id
(plyrID in the source), where the file provides one (ISSUES.md #22).
A NetPoints display name shared by exactly two locally-known athlete ids
is no longer dropped unconditionally.Pipeline._name_to_athlete_id used
to treat any name with more than one match as permanently ambiguous, which
meant every one of the 8 players ESPN files under two athlete_ids in the
same box score (#87) had zero rows in net_points_player_game, net_points_player_game_fingerprint and net_points_player_fingerprint
for their entire career - Corey Brewer, 985 player_box_stats rows over
2008-2020, had no per-game NetPoints for a single one of them. A new Pipeline._resolve_duplicate_athlete_pairs applies the same proof fetch/repairs/duplicate_athletes.py uses to merge those ids at load time -
the pair appears in the SAME team's box score for the SAME game - straight
off the player_box_stats Parquet tree at fetch time, and resolves the name
to the established id when that proof holds. Measured against the live
warehouse: all 8 known pairs now resolve, to the identical id player_box_stats_deduped already treats as canonical, and none of the
other 13 names shared by exactly two ids (real different people, #21) picked
up a false match (ISSUES.md #101).
scripts/bump_version.py rewrites the pinned install commands itself, so
a release can no longer ship instructions that install an older one. The pins
said v1.4.0 through three later releases, 3.0.0 shipped still pointing at v2.2.0, and 4.0.0 and 4.0.1 were only right because a human edited them
minutes before each bump. The script discovers them with git grep rather
than a hardcoded list (a list of two files is what let docs/usage.rst rot
unnoticed), refuses to run if a pin names neither the current nor the new
version, and asserts no old pin survives the rewrite. It skips CHANGES.md, tests/ and scripts/, which hold the same pattern as release history,
fixtures and the regex itself - scanning them refused a real bump, which a
test against this repository now catches.
scripts/bump_version.py now rewrites the install-command pins itself
(#60). Because PyPI is unreachable, README.md, docs/installation.rst
and docs/usage.rst pin a release tag
(git+https://github.com/jeffknupp/association@vX.Y.Z), and nothing rewrote
them automatically - they said v1.4.0 through three later releases, and
3.0.0 shipped still pointing at v2.2.0. The bump script now finds every
such pin with git grep (not a fixed file list, so a pin added to a new doc
is covered the same way), rewrites @v<current> to @v<new> in the same
run as the version bump, refuses outright if a pin names a version that is
neither the current one nor the new one, and asserts no @v<current> pin
survives anywhere in the tree afterward. docs/releasing.rst now describes
this instead of telling a human to update the pins by hand.
A retired player's question that names no season no longer defaults to
the current one and stops there.player_stat, single_game_high, game_log, player_netpoints and shot_chart all read a missing season
slot as "now", and a retired player's "now" is empty - "Allen Iverson's
points" answered "Allen Iverson has no 2026 regular season numbers in the
warehouse", true and about a year nobody asked for. The refusal now
redirects to what the warehouse actually holds for him when the season was
defaulted rather than named: "... He last appears in 2010. The warehouse
holds his 1997-2010 regular seasons; name one, or ask for his career." Never
substitutes an answer, only names where to ask again - the same discipline entities.suggest_players already follows for a near-miss name. A season
the question names outright keeps its plain refusal, because that answer is
correct as given: _Span now carries a defaulted flag from _span_of so
every reader downstream can tell the two cases apart. player_netpoints and shot_chart do the same from their own local flag, since neither is built
on _Span; neither offers "or ask for his career", since neither template
has a career span to redirect to. (#18)