Skip to content

feat(fixtures): season-2026 practice corpus loader and integrity test (8.0) - #359

Open
JoelA510 wants to merge 4 commits into
mainfrom
feat/phase8-0-corpus-loader
Open

feat(fixtures): season-2026 practice corpus loader and integrity test (8.0)#359
JoelA510 wants to merge 4 commits into
mainfrom
feat/phase8-0-corpus-loader

Conversation

@JoelA510

@JoelA510 JoelA510 commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 8 task 8.0 (docs/PHASE_8_PLAN.md). Adds a read-only loader and integrity test for fixtures/season-2026/practice/, mirroring packages/core/src/fixtures/season2026{Loader,Parsers}.js and tests/season2026Fixture.test.js. Three supervisor review rounds (8 + 7 + 5 items) are folded in.

  • packages/core/src/fixtures/season2026PracticeParsers.js — pure parsers, one .strict() Zod schema and one exact column contract per file (13 files, not the prompt's "seven"), a frozen finding-code table (28 codes) with a frozen severity table, one duplicate detector (noteDuplicate) for all five keyed files, compareDecoderRings() comparing the first occurrence of a code on both sides and carrying a kind per disagreement. Excel-date and unparsed availability rows are judged from the data, with the source's label read beside it. No Date construction; dates are YYYY-MM-DD, times are minutes past midnight; weekdays via availability/calendar.js's civil-date arithmetic; day names via a Map.
  • packages/core/src/fixtures/season2026PracticeLoader.jsnode:fs IO via the game loader's own helpers, the cross-corpus join, a deep-frozen result with findings, findingsByCode (every code, zero included), seasonLongClosures (all-day, all-fields rows covering ≥ half the days from first to last scheduled date), timeBoundedClosures (the same date test but a daily window — reported apart, never promoted) and meta (rowsRead / rowsParsed per file, plus what each check examined). The game corpus is the single producer of the season year; dir defaults season to the game corpus beside it and refuses one from anywhere else. One ALIAS_RESOLVES_TO_CLOSED_VENUE per season-long closure of the alias's venue.
  • tests/season2026PracticeCorpus.test.js — 49 cases: read-only guarantee, every README/plan figure derived at test time, positive controls, including one that raises all three blocking codes beside an assertion on the severity table's blocking count.
  • tests/reasonCodeReachability.test.js — the vocabulary is registered as audited. The corpus itself emits 22 of the 28 codes; the six it does not carry (AVAILABILITY_UNPARSED, PERMIT_DAY_MISMATCH, PERMIT_RESERVATION_OUTSIDE_SEASON_YEAR, CHANGE_LOG_DAY_MISMATCH, DUPLICATE_DECODER_CODE, PRACTICE_TEAM_NOT_ON_ROSTER) are driven from public parsers fed one- or two-row files. Header counts 19 / 424 / 413 / 11 are read back by the test itself.
  • fixtures/season-2026/practice/README.md — the source's "65 teams" claim kept visible, the derived 44 / 53 added with where each is computed; 6 / 22 / 136 marked unreconciled. Two seams in the game-corpus modules: computeFixtureChecksums(dir, files) and export const trim. No CSV modified.

Claims table

Claim Result Actual
Seven files (plan prompt) DID NOT HOLD 13 CSVs; all parsed
457 practice rows across 7 source sheets HELD 457 / 7
88 distinct teams hold a slot HELD 88
Exactly one practice team (16BSelect02) plays no game HELD PRACTICE_TEAM_PLAYS_NO_GAME = [16BSelect02]
65 game-playing teams hold no practice slot DID NOT HOLD 44 from the roster (ROSTER_TEAM_HOLDS_NO_PRACTICE); 53 from every named side of combined_schedule.csv (computed in the test). README corrected
Two slot regimes, no third HELD 45 @ 16:00/16:45/17:30; 60 @ 16:00/17:00/18:00
Practices Mon–Fri, 19 Friday rows HELD FRI = 19
201 coach registrations, 19 naming a second player HELD 201 / 19
1153 players, 29 playing up HELD 1153 / 29; no birth year ≥ 2026
13 constraint rows, 3 venues closed effectively all season HELD 13; Cedarbrook Park, Fivepines Park, Quarrywood Park — all-day (00:00–23:00) all-fields rows covering 85/85, 71/85, 85/85 of the scheduled season's days (threshold ½); no time-bounded multi-day rows; the other all-fields rows are single days
4 permits, 767 reservations, 2026-08-10 to 2026-12-20 HELD 4 / 767; all in the season year; lead-in/tail counted in meta.examined.reservationsBeforeSeason/AfterSeason
14 inventory, 27 code names, 42 availability rows HELD 14 / 27 / 42
Decoder rings share 20 codes, disagree on 12 HELD 20 / 12 = 11 label conflicts + 1 blank-vs-label (11v11 Field 2), asserted as such
Disagreements = 7v7 Field 1, 9v9 Field 1, Junior Field 1–7 DID NOT HOLD (incomplete) those 9 plus 9v9 Field 2, 7v7 Field 2, 11v11 Field 2
Two 142-row sheets differ only in being a makeup week HELD row-for-row identical bar source_sheet
28 rows carry venue = (unresolved) HELD 28, all in Select OLD; named finding, rows kept
22 select_coaches / 9 field_equipment / 167 game_change_log rows HELD 22 / 9 / 167
Co-coach split 71/24/29/9/3/65 HELD, column 1 only column 2: 10/3/4/7/177
"7v7 Field 1" → Cedarbrook Park, Offline 08-01..11-28 HELD ALIAS_RESOLVES_TO_CLOSED_VENUE = 1 (one closure, one finding)
Eight aliases → Maplewood, closed 2026-10-23 HELD 8; Maplewood all-fields rows 09-24 and 10-23
Summit HS permit attaches Field Lights HELD all 39 PERMIT-04 rows, no other permit
Every practice team_code resolves in the game corpus except 16BSelect02 HELD PRACTICE_TEAM_NOT_ON_ROSTER = 0
Every registration person_key on ../coach_roster.csv or minted ASSERTED coach_registration 166 / 35 minted; select_coaches 15 / 7 minted
Excel-date / unparsed availability rows reported, raw retained HELD 15 (detected from the data; label agreement stated), 0 unparsed (judged from the data: every empty window is accounted for by unavailable or competitive-programme); 2026-MM-DD(MM+12):00–(DD+12):00 on every row
6 venues / 22 fields / 136 team codes UNRECONCILED game corpus: 7 venues, 24 field ids, 132 roster teams. 136 = 131+5 or 132+4; 6 and 22 have no found reading

Findings the corpus README does not state

  • select_coaches.csv vs ../coach_roster.csv, both directions: 8 sheet rows name a person the roster does not have on that team (7 minted + teagan hobbes); 1 names a rostered coach at another slot (emerson crane, 19BSelect01, sheet slot 1 / roster slot 2 — slot is a clash-breaker, not a role); 8 roster coaches of Select teams are omitted by the sheet.
  • Venue names: practice says Maplewood, the game corpus Maplewood Back / Maplewood Front; Larkfield Green hosts no game. VENUE_NOT_IN_GAME_CORPUS = 33 across all 9 venue-bearing files (list derived from the column contracts).
  • field_constraints.csv Gardening Day row has fields = 2026-01-07 (Excel-corrupted 1-7).
  • 9 coach-registration player birth years are 2026.
  • Duplicates: verity bramteford registered twice; players imre hesgaley, ilse wyndafield; Willowmead Park inventoried twice.
  • 9 named registration players have no player_registration.csv row; of 10 unresolvable co-coach keys, 7 are registered players' keys (CO_COACH_KEY_IS_PLAYER) and 3 nowhere.
  • 11v11 Field 1 carries a label but no venue (DECODER_RING_ALIAS_VENUE_BLANK); 11v11 Field 2 carries neither. confirmed is empty on all 27 code-name rows and is carried on every disagreement.
  • Not changed (operator decision, carried in the progress log): apparent real organisation and place names in game_change_log.csv matchup cells.

Positive controls (assertions proven able to fail)

Inline, kept as tests: header-only and full extra column, long row, empty file, unknown enum value, unknown record key, non-ISO and impossible calendar dates (Feb 30), a third slot regime, a reconciled decoder ring (11 < 12), a duplicated alias row (still 20 / 12 plus DUPLICATE_DECODER_CODE), a duplicated code-name row on the fields side in both orders (agree-with-first → 0, differ-with-first → 1; a last-wins index gets both wrong), unparsed and unlabelled date-shaped availability rows, an empty unlabelled window (AVAILABILITY_UNPARSED) against unavailable / competitive-programme (not), a permit on the wrong weekday, a 2027 reservation (finding) and a 2026-01-05 one (figure, not finding), a change-log note on the wrong day, birth year without a season year for both registration parsers (refuse), a 59-day mid-season closure (season-long) vs a 60-day January one (not), the 2026-09-05..11-28 16:00–19:00 all-fields row (time-bounded, not season-long, no alias finding), two season-long closures of one venue (two alias findings; corpus stays at one), a sheet with one team's rows removed (its three roster coaches reported), 'constructor' / '__proto__' as day names, a season from a non-sibling directory (refused), and one control that raises all three blocking codes beside the severity-table count.

Executed against corrupted copies of the corpus in a scratch directory (removed), across the rounds: dropping a grid row, a non-roster team code, Rookery→Rookerie, an unparsed row, resolving one (unresolved) venue, shortening the Cedarbrook closure, Friday→Thursday, 16BSelect02→16BSelect01, a minted select coach on a roster key, an added column, a new class value, a wrong-weekday reservation, an edit inside the 142-row makeup sheet, a 30-minute slot, a venue misspelt only in the equipment sheet, a wrong change-log note, a duplicated decoder code, a confirmed mark, dropping a team from the sibling coach_roster.csv, every reservation re-dated to 2027 beside the 2026 corpus, dropping one select_coaches row, swapping 19BSelect01's slots, filling the 11v11 Field 1 venue, a player born 2026. The noteDuplicate generalisation was proven by a byte-for-byte comparison of the serialised corpus finding list before and after (111,484 bytes, identical).

Verification

Statically reviewed: /code-review at high and medium (11 items, 10 fixed, 1 handled by carrying confirmed rather than resolving on it); supervisor rounds 1, 2 and 3 (20 items, all fixed).

Executed (actual output at head 7ce44d9):

npm run lint            0 errors, 1 warning (baseline: 0 errors, 1 warning)
npm run typecheck       0 errors
npm run test            before (origin/main b048022): 2165 passed | 34 skipped | 6 todo (2205), 158 files
                        after: 2216 passed | 34 skipped | 6 todo (2256), 159 files
npx vitest run tests/season2026Fixture.test.js tests/season2026PracticeCorpus.test.js tests/reasonCodeReachability.test.js
                        110 passed (34 + 49 + 27)
npm run frontend:build  clean
npm run check:advisors  PASS
npm run check:bundle    PASS
npx prettier --check fixtures/season-2026/practice/README.md   clean

Test-verified: everything in the claims table and the inline controls above. The corrupted-copy controls were executed, not kept as tests.

Left open, deliberately: the 65 / 6 / 22 figures (unknown derivation, now stated in the README); the change-log organisation names (operator); whether used_for / remainder on field_code_names.csv should ever be load-bearing (retained as record data); what a time-bounded season closure means for practices (reported as timeBoundedClosures with its window; the corpus has none).

🤖 Generated with Claude Code

https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9

… (8.0)

Add a read-only loader and integrity test for fixtures/season-2026/practice/,
mirroring the game-corpus loader. All thirteen files parse into .strict() Zod
records; nothing is dropped, and every parse limit the corpus README states
(28 unresolved venues, the Excel-corrupted availability and constraint cells,
the two decoder rings' 12 disagreements) is carried as a finding with its raw
value beside it, graded by a frozen severity table. The join with the game
corpus enumerates teams from the roster and people from the roster, and
reports which registration person keys are minted, where select_coaches.csv
disagrees with the roster, and which venue names the game corpus does not
know. Dates stay YYYY-MM-DD strings, times stay minutes past midnight, and the
loader constructs no Date.

Every README and plan figure is derived at test time. Two did not hold: the
"65 teams that play a game hold no practice slot" claim (the roster says 44,
every named schedule side says 53) and the README's disagreement table (it
names 9 of the 12). The prompt's "seven files" is thirteen.

The finding vocabulary is registered with tests/reasonCodeReachability.test.js
and every one of its 24 codes is driven from a public entry point. Two small
reuse seams open in the game-corpus modules: computeFixtureChecksums() takes
a file list and trim() is exported.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
squadlogic Ready Ready Preview Sep 4, 2026 9:20pm UTC

- Enumerate the select-coach check from the roster's Select teams too, so a
  coach the sheet omits is SELECT_COACH_OMITTED_BY_SHEET (8 in the corpus)
  rather than a name inside another row's detail string.
- Split the sheet-to-roster check: SELECT_COACH_NOT_ON_ROSTER_TEAM (8) for a
  person the roster does not have on the team, SELECT_COACH_SLOT_DIFFERS (1,
  emerson crane) for the same person at another slot. Slot is a clash-breaker,
  not a role.
- Take the season year from the game corpus, the single producer; report
  reservations outside it as PERMIT_RESERVATION_OUTSIDE_SEASON and delete
  seasonYearOfReservations().
- An alias with a label but no venue is DECODER_RING_ALIAS_VENUE_BLANK
  (11v11 Field 1), never a silent skip from the venue and closure joins.
- DAY_NAME_TO_CODE is a Map; 'constructor' no longer resolves.
- parsePlayerRegistration() requires the season year and judges birth years,
  the same contract as parseCoachRegistration().
- Closure-span comment matches inclusiveSpanDays() (92 and 120).
- README: the source's "65 teams" claim is kept visible and the derived 44
  (from the roster) and 53 (from every named schedule side) added with their
  derivations; 6 / 22 / 136 marked unreconciled. No CSV touched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9
- compareDecoderRings() compares each display name once; a duplicated alias
  row is the parser's DUPLICATE_DECODER_CODE, not a second comparison.
- Every disagreement carries a kind (label-conflict / blank-vs-label); the
  test asserts 12 = 11 + 1 and says why the blank counts.
- PERMIT_RESERVATION_OUTSIDE_SEASON is PERMIT_RESERVATION_OUTSIDE_SEASON_YEAR,
  which is what it checks; reservations before the first and after the last
  scheduled date are meta figures.
- One duplicate detector, noteDuplicate(), at all five sites; the corpus
  finding list is byte-identical before and after.
- Season-long closures are decided in the loader by overlap with the
  scheduled season (>= half its days), not by a 60-day span in the parser;
  seasonLong and SEASON_LONG_CLOSURE_MIN_DAYS are gone, spanDays stays.
- The omitted-coach control filters one team out of the sheet (reachable)
  instead of forging an empty record set (refused at the header).
- README: no escaped underscores, no bullet that reads as a minus, and the
  sentence says where 44 and 53 are each computed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9
- compareDecoderRings() indexes the fields sheet by first occurrence, as the
  aliases side already did; a duplicated code_name can neither invent nor
  reconcile a disagreement.
- AVAILABILITY_UNPARSED is judged from the data: an empty window the source
  does not account for (unavailable, competitive-programme) is unparsed
  whether or not the label says so.
- A season-long closure must be an all-day row (00:00 to 23:00, the sheet's
  own spelling); a multi-day all-fields row with a daily window is reported
  apart as timeBoundedClosures, never promoted. Corpus: still exactly
  Cedarbrook, Fivepines, Quarrywood.
- One venue-to-closures map, one ALIAS_RESOLVES_TO_CLOSED_VENUE per closure.
- The severity table's blocking count is asserted, and all three blocking
  codes are raised by one control in the corpus test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants