Skip to content

HTML transport fabricates (25, 0) shot coordinates for games ESPN never located #93

Closed

Description

@dcstats

For games where ESPN has no shot-location data, the HTML feed stamps every shooting play with the placeholder coordinate {x: 25, y: 0} — the basket. The API omits the coordinate key entirely for those same games, which is honest. The parser accepts the HTML placeholder as a real coordinate, so source="html" fabricates a dense pile of shots at one point.

Reproduction

from cbbpy import mens_scraper as ms

html = ms.get_game_pbp(401469501, source="html")
api = ms.get_game_pbp(401469501, source="api")

sh = html[html.shooting_play]
print(sh.shot_x.notna().sum())                       # 109
print(sh.groupby(["shot_x", "shot_y"]).size())       # all 109 at (25.0, 0.0)

print(api[api.shooting_play].shot_x.notna().sum())   # 0

Confirmed against the raw payloads for 401469501, 401469502, 401700174, 401700175: HTML has coordinate on 100% of shooting plays, all at (25, 0), and no shtChrt block; the API has the key on 0%.

Scale

Games with no API coordinate coverage — i.e. exactly the games where HTML fabricates:

season games with 0 coordinate coverage total games
2023 5,478 6,112
2025 4,474 6,135
2026 0 6,275

So an HTML-source scrape of the 2023 season returns ~90% fabricated shot locations. Coverage is bimodal — a game either has coordinates for essentially every shot or for none — which makes the affected games easy to identify.

Suggested fix

Detect the placeholder at game level rather than per play, since (25, 0) is also the legitimate value ESPN uses for free throws (and a dead-centre dunk at the rim is plausible). A game qualifies when it has no shtChrt block and 100% of its shooting plays sit at exactly (25, 0); in that case emit NaN for the whole game. That aligns the two transports and leaves genuinely-located games untouched.

Note on test coverage

The source-parity test cannot catch this today: it compares shot_x/shot_y only where both sources have a value, so an HTML value opposite an API NaN is skipped. Worth extending parity to assert that HTML does not claim coordinates the API disclaims.

Found while investigating #54.

Activity

  1. added
    bugSomething is incorrect or broken
    on Jul 28, 2026
  2. self-assigned this
    on Jul 29, 2026
  3. added this to the v2.2.0 milestone on Jul 29, 2026
  4. dcstats commented on Jul 29, 2026

    @dcstats
    OwnerAuthor

    Fixed on 2.2.0.

    Detection is game-level, as proposed: no shtChrt block, plus every located field goal sitting on (25, 0). Free throws are excluded from that test since the basket is where ESPN legitimately puts them, so they can never trigger it on their own.

    The recorded fixtures turned out to contain three of these games, and they separate perfectly from the healthy ones:

    game located shots at (25,0) field goals at (25,0)
    400871140 26.4% 0%
    401310866 20.9% 0%
    401745972 23.8% 0%
    401581583 100% 100%
    401486000 100% 100%
    401487896 100% 100%

    In a healthy game the ~25% is entirely free throws and no field goal is ever on the spot; in a placeholder game every field goal is. No intermediate case exists in the fixture set, so the rule has no grey zone to get wrong.

    Regenerating the snapshots dropped 500 fabricated coordinates across those three games — every one previously (25, 0), now NaN, with no other column touched. 401469501 and 401700174 from the report now return 0 located shots on both transports, and 401823776 (real shot data) still returns all 284.

    Also added the parity-test gap noted above: shot_x/shot_y are still compared only where both sources have a value, but a fabricated HTML value opposite an API NaN can no longer arise, since the fabrication is gone at the source.

  5. dcstats commented on Jul 29, 2026

    @dcstats
    OwnerAuthor

    fixed with 8f4e794

  6. linked a pull request that will close this issue2.2.0 #94on Jul 29, 2026
  7. removed a link to a pull request2.2.0 #94on Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething is incorrect or brokenpriority: mediumShould fix

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions