fix(practice): assistant coaches enter the conflict check; slot-expansion docstring corrected (8.1) - #363
Open
JoelA510 wants to merge 3 commits into
Open
fix(practice): assistant coaches enter the conflict check; slot-expansion docstring corrected (8.1)#363JoelA510 wants to merge 3 commits into
JoelA510 wants to merge 3 commits into
Conversation
…sion docstring corrected (8.1) Defect 1: the practice conflict check read team.coachId only, so the assistant coaches on teams.assistant_coach_ids never entered it. In the season-2026 corpus that is 83 of 215 assignments (82 of 132 teams carry more than one coach), all unseen. - practiceScheduling.js exports listTeamCoachIds(team): head coach plus assistants, deduplicated, empty ids dropped. The list is read as assistantCoachIds first, then assistant_coach_ids (teamSnapshot.js's precedence). A missing or null list is "no assistants", matching the `if (team.coachId)` contract for a missing head coach; a list that is present and not an array throws a TypeError rather than reading as conflict-free. - The greedy pass books, releases, counts (multi-team priority) and overlap-checks every coach on the team, and unions unavailableSlotIds over every coach. Preferred days/slots (scoring) still follow the head coach; that is 8.2's one-coach-model work. - autoScheduler.js: checkHardConstraints no longer returns valid on !team.coachId; unavailability and overlap loop over team.coachIds, precomputed once per run rather than per mutation. buildState and the mutation add/remove book every coach. - practiceMetrics.js: coachConflicts and coachLoad are keyed by every coach on the team, so a shared-assistant overlap is reported. One entry per coach, as before; documented. Defect 2: practiceSlotExpansion.js claimed to "account for daylight adjustments" with no sunset, daylight or lighting input anywhere in the module. Chosen: correct the docstring, not implement the adjustment. Implementing it is task 8.9 and depends on data this repo does not yet hold. A test now guards the module against re-acquiring the claim. Tests (19 new; every behavioural one fails on origin/main's sources and passes here): shared assistant with only overlapping slots is reported as a coach conflict; no-head-coach team still enters the check; locked assignment books the assistant; assistant unavailability blocks as a hard constraint; the optimizer's relocate cannot seat a team on its assistant's clash or unavailability (these two fail with only autoScheduler.js reverted, so they pin the optimizer layer specifically); evaluator flags and controls; the teams-column spelling is read; a present non-array is rejected. Raised, not fixed: #361 (practice_slots.day_of_week CHECK forbids the 19 Friday rows the league runs) and #362 (the app runtime is a Deno port of these modules with the same head-only check, and the page adapter drops assistants before the request; the core fix is unreachable from the app until both change). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
… auto-scheduler; one conflict per overlapping pair (8.1 review round 1) Review round 1 on #363. 1. The shipped path. The app runs supabase/functions/auto-scheduler/index.ts, not the core modules, and the page dropped assistants before the request. - New _shared/engines/practice-coaches.ts: listTeamCoachIds and checkHardConstraints, mirroring the core fix exactly; kept in _shared so a Deno test can reach the seam without importing the serve() module. - index.ts: TeamSchema gains assistantCoachIds; every coachId-only site (buildState, mutation add/remove, greedy assign, multi-team priority) loops team.coachIds, resolved once at input preparation. - _shared/schemas/scoring.ts Team gains assistantCoachIds; scoring-engine.ts reports conflicts for every coach on the team, one entry per overlapping pair with coachIds listing the coaches shared. - PracticeSchedulingPage normalizeTeam forwards assistantCoachIds (read from either spelling the rows carry); exported for its vitest case. - Deno: `deno check` on the three files reports only the pre-existing TS2339 (`.catch` on void, line 638; line 644 on origin/main). New _shared/tests/practice-coaches_test.ts: 8 passed; with scoring-engine.ts and scoring.ts at origin/main the two scoring-engine cases fail. The checkHardConstraints cases have no "before" -- the seam is new. The pre-existing scoring-engine_test.ts "coach conflict detection" case fails on origin/main too (expects 'Time overlap' in a reason that reads 'has overlapping practices'); untouched. 2. practiceMetrics.js: coachConflicts is one entry per overlapping pair of assignments, coachIds listing every coach the pair shares and coachId the first; computeFitness therefore charges an overlap once whether the pair shares one coach or two, and evaluationPipeline emits one issue naming all of them. coachLoad still counts each coach. Controls: equal fitness for one- and two-coach pairs, one pipeline issue each, zero without a clash. 3. practiceMetrics.js: the coachConflicts note now sits above the first @PARAM instead of between params.slots and @returns. 4. practiceScheduling.js: busiestCoachCount is computed once per team alongside coachIds; the comparator compares stored numbers. A fixture test pins the processing order (read off which slot each team receives) and passed unchanged before and after the change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9
… assistant spellings, admits null (8.1 review round 2) Review round 2 on #363. 1. scoring-engine.ts visited only the first overlap per coach per assignment (schedule.find), so a pair's coachIds could omit coaches it shares and a pair could vanish. It now mirrors practiceMetrics.js: each coach's assignments sorted, every overlapping pair visited, pairs merged across coaches. Deno control: T1{h}, T2{h,s}, T3{h,s} all overlapping reports T2/T3 as [h, s] and T1/T2, T1/T3 as [h]. 2. practice-coaches.ts listTeamCoachIds reads assistantCoachIds first, then assistant_coach_ids, null/undefined as none, present non-array throws -- the core helper's contract. scoring-engine.ts honours a precomputed team.coachIds (index.ts sets it once at input preparation) and falls back to the helper otherwise. Deno controls: a row carrying only assistant_coach_ids is conflict-checked on its assistant; a precomputed list wins over the raw fields. 3. assistantCoachIds is .nullable().optional() in the shared TeamSchema. The request schema moved to _shared/schemas/auto-scheduler.ts on the import-map zod (index.ts composed the shared TeamSchema with its own esm.sh zod, which does not type-check across two zod instances; index.ts used z nowhere else, so its esm.sh import is gone). Deno control: a request with assistantCoachIds: null validates and the team is seated on its head coach; a non-array is still rejected. 4. The coach-conflict issue and the entry's reason are built after the pair is fully merged, naming every shared coach: "Coaches h, s have overlapping practices on Monday" / "Coach h has ...", matching evaluationPipeline.js. Deno control: T1{h,s} vs T2{h,s} yields one issue naming both; a one-coach pair keeps the singular. 5. tests/practiceSlotExpansion.test.js no longer scans the source for wording. It expands the same slots with and without sunset / lighting / daylight input and asserts identical output, that an honoured input (fewer phases) does change it, and that the declared parameter shape names no such input (a fake ({ slots, sunset }) fails the same check). Deno: check reports only the pre-existing TS2339 (.catch on void, now index.ts:566). practice-coaches_test.ts 14 passed; with the four round-1 sources restored (--no-check) 7 failed / 7 passed -- every new control red. The pre-existing scoring-engine_test.ts "coach conflict detection" stays red: it expects reason to contain 'Time overlap' while the Vitest suite tests/unit/scoring-engine.test.ts, green on main, expects 'overlapping practices' from the same field; the main wording is kept. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 8 task 8.1 (
docs/PHASE_8_PLAN.md§8.1). Two defects in the practice scheduler — fixed in the core modules and in the shipped Edge Function path (review rounds 1–2) — one raised-not-fixed item, and the reachability finding that turned into the port.Closes #362.
What changed
Defect 1 — assistant coaches were invisible to the conflict check.
Core.
packages/core/src/practiceScheduling.jsexportslistTeamCoachIds(team): head coach plus assistants, deduplicated, empty ids dropped; read asassistantCoachIdsfirst, thenassistant_coach_ids(the precedenceteamSnapshot.jsalready applies). A missing ornulllist is "no assistants" (the siblingif (team.coachId)contract); a list that is present and not an array throws aTypeErrorrather than reading as conflict-free. Everyteam.coachIdsite consults every coach:practiceScheduling.js— booking, release, the multi-team priority (busiest coach, computed once per team alongsidecoachIds), the overlap check, and theunavailableSlotIdshard constraint (unioned over all coaches). Preferred days/slots are scoring hints and still follow the head coach — 8.2's one-coach-model work, left open on purpose.autoScheduler.js—checkHardConstraintsno longer returnsvalidon!team.coachId; unavailability and overlap loop overteam.coachIds, precomputed once per run.practiceMetrics.js—coachConflictsis one entry per overlapping pair:coachIdslists every coach the pair shares,coachIdthe first.computeFitnesscharges an overlap once whether the pair shares one coach or two;evaluationPipeline.jsemits oneerrorissue naming all of them ("Coaches h, s have overlapping practices").coachLoadstill counts each coach. Stated in the JSDoc above the first@param.Shipped path (rounds 1–2). The app POSTs to
supabase/functions/auto-scheduler/index.ts, a Deno port of these modules, andPracticeSchedulingPage.normalizeTeamdropped assistants before the request.supabase/functions/_shared/engines/practice-coaches.ts:listTeamCoachIds(same contract as core: camelCase then snake, null/undefined → none, present non-array → throw) andcheckHardConstraints; in_sharedso a Deno test reaches the seam without importing theserve()module.supabase/functions/_shared/schemas/auto-scheduler.ts: the request schema, moved out ofindex.tsonto the import-map zod so it is testable and composes the one sharedTeamSchema(index.tscomposed it with its own esm.sh zod, which does not type-check across two zod instances;index.tsusedznowhere else, so that import is gone).assistantCoachIdsisz.array(z.string()).nullable().optional()in_shared/schemas/scoring.ts.index.ts: everycoachId-only site (buildState, mutation add/remove, greedy assign, priority sort) loopsteam.coachIds, resolved once at input preparation._shared/engines/scoring-engine.ts: mirrorspracticeMetrics.js— each coach's assignments sorted, every overlapping pair visited, pairs merged across coaches; a precomputedteam.coachIdsis honoured, else the helper; reason and issue built after the merge, naming every shared coach.frontend/src/pages/PracticeSchedulingPage.jsxnormalizeTeamforwardsassistantCoachIds: team.assistantCoachIds ?? team.assistant_coach_ids ?? []; exported for its vitest case (3 cases).Defect 2 —
practiceSlotExpansion.jsdocstring. Chosen: correct the docstring, not implement the adjustment (stated in the commit); implementing it is 8.9. Guarded behaviourally: expanding with and withoutsunset/lighting/daylightinput gives identical output, an honoured input (fewer phases) changes it, and the declared parameter shape names no such input (a fake({ slots, sunset })fails the same predicate).Raised, not fixed: #361 —
practice_slots.day_of_weekCHECK forbids Friday while the league runs 19 Friday rows. Migration is its own PR with revert + smoke scripts underdocs/sql/.Claims verified
practiceScheduling.jsandautoScheduler.jsconsult onlyteam.coachIdcoachIdat the lines listedpracticeMetrics.js;auto-scheduler/index.ts;_shared/engines/scoring-engine.ts; pagenormalizeTeam;PracticeOverridePanel.jsx(head-coach display names, manual-override UI — untouched). All but the last fixed here.loadSeason2026()at run timepracticeSlotExpansion.js:13daylight claim; no sunset/daylight/lighting input{ slots, seasonPhases }('mon','tue','wed','thu'); 19 Friday rowsfri; CSV Friday rows = 19; 8.0 loader asserts 19assistantCoachIds; DB column and mock client:assistant_coach_ids; both helpers read camelCase first, then snake; the request schema declares camelCase (what the page sends) and the snake spelling arrives via passthrough.Failing-then-passing evidence
Vitest. Six suites with the new tests and core sources reverted to
origin/main:11 failed | 137 passed. Layer isolation: with onlyautoScheduler.jsreverted,2 failed | 18 passed— the two "relocate cannot seat…" tests, built where fitness favours the clash. Item-4 comparator change (round 1): the processing-order fixture (read off which slot each team receives, sinceassignmentsis returned sorted by id) passed identically before and after.Deno (executed via
npx --yes deno@2, 2.9.6,DENO_NO_PACKAGE_JSON=1, remote imports through the proxy):deno checkonindex.ts,practice-coaches.ts,scoring-engine.ts,schemas/auto-scheduler.ts: exactly one error, pre-existing —TS2339 Property 'catch' does not exist on type 'void', nowindex.ts:566(:644onorigin/main, same line of code). No new errors.deno test _shared/tests/practice-coaches_test.ts: 14 passed after round 2. With the four round-1 sources restored (--no-check): 7 failed | 7 passed — every round-2 control red: snake spelling in the helper;TeamSchemanull; request-schema null; issue naming both coaches; three-team merge (T2/T3 =[h, s]);assistant_coach_ids-only row; precomputedcoachIds. Withscoring-engine.ts+scoring.tsatorigin/main(round 1): 2 failed | 6 passed. The threecheckHardConstraintscases have no red "before" — the seam is new — and are proven by their in-test controls._shared/tests/scoring-engine_test.ts"coach conflict detection" (line 49) assertsresult.coachConflicts[0].reason.includes('Time overlap'). It fails onorigin/mainand still fails:tests/unit/scoring-engine.test.ts(Vitest, green onmain) asserts the same field contains'overlapping practices', so the two pre-existing tests contradict each other and themainwording is kept. Not made to pass naturally.Positive controls proven to fail: no-head-coach teams; "distinct assistants" controls; fitness control (two-coach pair equals one-coach pair, both below no-clash); pipeline control (one issue each, zero without a clash); optimizer oracle on a shared-assistant pair and a head/assistant crossover;
normalizeTeamcontrol (no rawassistant_coach_idskey survives); docstring test's fake({ slots, sunset })and phase-count change; request schema still rejects a non-array; singular rendering for a one-coach pair.Deliberately left open
PracticeOverridePanel.jsxmanual-override check compares head-coach display names.deno checkerror atindex.ts:566and the contradictory pre-existing scoring-engine tests — outside 8.1.packages/core/package.jsonmainpoints at a nonexistentsrc/index.js; no index to registerlistTeamCoachIdsin.Verification
Test-verified (executed, output captured):
npm run test— main baseline 2216 / 34 / 6 → 2242 / 34 / 6 (160 files, +26 tests). Deno suite 14/14 after, 7 red before. All before/after and isolation runs above.Executed:
npm run lint— 0 errors, 1 warning (theuseVirtualizerbaseline).npm run typecheck— 0 errors.npm run frontend:build— clean.npm run check:advisors— PASS.npm run check:bundle— PASS; both re-run after each Edge Function edit.prettier --checkon the TypeScript files — clean.deno checkas above./code-reviewat high before opening (6 findings, all addressed).Statically reviewed only: the
index.tsbooking loops inbuildState, the mutation add/remove and the greedy seed — they call the tested seam but the module runsserve()at top level and cannot be imported by a test; the Edge Function needs a deploy to take effect.🤖 Generated with Claude Code
https://claude.ai/code/session_011ppMMPnorTKrb9ZVaGTpS9