A second, independent implementation of the same contract, in the same repo:
- swehockey/dom.py builds the tree on html.parser, so the variant has no
runtime dependencies; the only HTML5 behaviour it depends on - stray <tr>
landing in an implicit <tbody> - is handled explicitly by table_rows().
- parse.py mirrors the Go layout profiles, dual-render checks, carry-forward and
invariants; a DriftError is raised in the same places with the same advice.
- cli.py offers the same three commands, flags and exit codes as the Go main.
- tests/ assert against the Go golden files, so the suite is also a
cross-implementation check. 23 offline tests plus an opt-in live test.
Verified against the live site: JSON and CSV are byte-identical to the Go
output (364 games, 102536 bytes / 365 lines), and the full archive crawl gives
the same 27 seasons, 2119 games and 1779 played.
Also split the DriftError hint out in Go, so a golden mismatch no longer tells
the reader to extend layouts[].
One HTTP GET per season, no browser: the page is server-rendered, so every row
arrives in one response and the extraction becomes a pure function of the HTML.
- layouts[] declares one profile per version of the site's table (modern and
legacy), each identified by its header row, so a new version of the page is a
new profile plus a fixture instead of a guess.
- The table is located by header signature, not by a CSS path.
- Every cell is shape-checked, and facts the site renders twice (desktop and
mobile date, time in the tooltip) must agree.
- Cross-field invariants: periods sum to the score, ids are unique, played games
always have an event link and unplayed ones never do.
- Output is sorted by date, time and id, so it does not inherit the site's row
order, and it carries no fetch timestamp: same HTML, same bytes.
- Drift is a typed error and exit code 3, with the header it actually found.
- Tests run on committed HTML fixtures and golden JSON; TestLive is opt-in.
Verified over the full SHL archive: 27/27 seasons, 2119 games, 1779 played.