feat: multi-tenancy, auth, superadmin dashboard, onboarding, and test suite#1
Merged
Merged
Conversation
- Add School, User, Session, OtpCode models to Prisma schema - Add schoolId FK to Teacher, Period, TimetableEntry, SickReport, ReliefAssignment - Implement email OTP auth (stubbed - logs to console) with session cookies - Add Next.js middleware for route protection (/dashboard, /admin, /onboarding) - Update all 11 API routes with schoolId filtering for data isolation - Create SchoolContext provider for path-aware nav components - Set up demo mode at /demo/dashboard (unauthenticated, uses demo school) - Build superadmin dashboard at /admin (create schools, manage users) - Build school admin onboarding wizard (timetable import + colleague whitelist) - Update landing page with "Get Started" and "Try Demo" CTAs https://claude.ai/code/session_012VnoQSMuzvxRdHiJsdibcJ
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, add factories Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o shared helpers - Wrap POST /api/sick-reports in try/catch and return 500 on unexpected errors - Replace getSchoolId() with requireSchool() in sick-reports, relief-assignments, dashboard, and teachers routes so unauthenticated requests receive 401 instead of silently using the demo school - Extract mockSession/mockNoSession into src/__tests__/helpers.ts and update all 8 test files to import from there - Add 401 unauthenticated tests to sick-reports, relief-assignments, teachers (GET+POST), and dashboard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Apr 9, 2026
This was referenced Apr 25, 2026
guangshinhaha
added a commit
that referenced
this pull request
Apr 27, 2026
#111) Every school interviewed said they want to see a teacher's daily schedule before assigning relief — the #1 confidence gap in the current UX. This adds a compact horizontal strip below each candidate's name in the assign modal showing their full-day timetable at a glance. Visual: colored blocks per period - Blue = teaching (hover shows subject + class) - Amber-light = already covering another relief slot - Orange/highlighted = the period being assigned (ring indicator) - Gray = free period Data pipeline: - API: enriched allEntriesForDay query with className/subject/period number; new schoolPeriods query; buildDaySchedule() helper combines timetable + existing relief into per-teacher schedule - Types: new TeacherPeriodSlot type, daySchedule field on AvailableTeacher in both DashboardContent and AssignReliefModal - Modal: inline strip rendered with flex blocks, hover tooltips, and ring indicator on the target period Scannable in < 2 seconds as KPs work at 6:45am. Closes #67 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
garygoh-dev
pushed a commit
that referenced
this pull request
Apr 27, 2026
Every school interviewed said they want to see a teacher's daily schedule before assigning relief — the #1 confidence gap in the current UX. This adds a compact horizontal strip below each candidate's name in the assign modal showing their full-day timetable at a glance. Visual: colored blocks per period - Blue = teaching (hover shows subject + class) - Amber-light = already covering another relief slot - Orange/highlighted = the period being assigned (ring indicator) - Gray = free period Data pipeline: - API: enriched allEntriesForDay query with className/subject/period number; new schoolPeriods query; buildDaySchedule() helper combines timetable + existing relief into per-teacher schedule - Types: new TeacherPeriodSlot type, daySchedule field on AvailableTeacher in both DashboardContent and AssignReliefModal - Modal: inline strip rendered with flex blocks, hover tooltips, and ring indicator on the target period Scannable in < 2 seconds as KPs work at 6:45am. Closes #67 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
guangshinhaha
pushed a commit
that referenced
this pull request
Apr 30, 2026
Replaces the per-slot cascading-filter algorithm as the default for internal teacher matching. The new WEIGHTED mode runs in two stages: Stage 1 — Rank-Order Centroid (ROC) weighted sum across enabled rules. Weights are derived automatically from the admin's drag-to-reorder rule order (Barron 1992); no manual tuning. For 6 rules the weights are roughly 41% / 24% / 16% / 10% / 6% / 3% — rule #1 dominates but isn't absolute, so a candidate strong on multiple lower-rank rules can beat one only strong on rule #1. Fixes the small-pool collapse problem where strict lex / cascade always pick on rule #1. Stage 2 — 2-opt pairwise swap pass over the Stage-1 proposals. Catches cross-slot teacher trades — e.g. a teacher who is the only Math + 3A match shouldn't get "used up" on a less critical earlier slot. Each swap must strictly improve total weighted score and satisfy hard constraints (no double-booking, no busy-period conflicts, no self-cover). Deterministic, sub-100ms for n ≤ 20. CASCADE mode (the legacy strict-priority filter) stays available as an opt-in via School.algoMode = 'CASCADE'. Hard constraints (no self-cover, no double-book, no assign while sick, hard weekly cap) are surfaced in the Settings → Algorithm page as locked, uneditable cards above the editable soft-rule list.
guangshinhaha
added a commit
that referenced
this pull request
May 4, 2026
…ives #146) (#149) * feat(auto-assign): ROC-weighted ranking + 2-opt global swap pass Replaces the per-slot cascading-filter algorithm as the default for internal teacher matching. The new WEIGHTED mode runs in two stages: Stage 1 — Rank-Order Centroid (ROC) weighted sum across enabled rules. Weights are derived automatically from the admin's drag-to-reorder rule order (Barron 1992); no manual tuning. For 6 rules the weights are roughly 41% / 24% / 16% / 10% / 6% / 3% — rule #1 dominates but isn't absolute, so a candidate strong on multiple lower-rank rules can beat one only strong on rule #1. Fixes the small-pool collapse problem where strict lex / cascade always pick on rule #1. Stage 2 — 2-opt pairwise swap pass over the Stage-1 proposals. Catches cross-slot teacher trades — e.g. a teacher who is the only Math + 3A match shouldn't get "used up" on a less critical earlier slot. Each swap must strictly improve total weighted score and satisfy hard constraints (no double-booking, no busy-period conflicts, no self-cover). Deterministic, sub-100ms for n ≤ 20. CASCADE mode (the legacy strict-priority filter) stays available as an opt-in via School.algoMode = 'CASCADE'. Hard constraints (no self-cover, no double-book, no assign while sick, hard weekly cap) are surfaced in the Settings → Algorithm page as locked, uneditable cards above the editable soft-rule list. * fix(auto-assign): address review of ROC + 2-opt branch (PR #146 revival) B1 — 2-opt was a no-op for the headline use case. `scoreCandidateForSlot` called `rule.score(teacher, ctx, [teacher])`. The soft-skip branch in `match_subject` / `prefer_same_class` (`if (!pool.some(...)) return 1`) fired for every (teacher, slot) pair because the single-element pool never matched, so swap deltas were always 0 and the cross-slot trades the algorithm exists to find never fired. Pass the full teacher pool through `runTwoOptImprovement` instead. B2 — `pickedRuleId` reported the dominant ROC contributor, not the differentiator. With heavy front-loaded weights, a tied rule #1 still won attribution even when both candidates scored equally on it. Switch to `argmax weights[i] * (score(picked) - score(runnerUp))` so the deciding rule is the one that actually separated the pick from the runner-up. Fixes nonsensical "Lower balance_workload" alternative strings as a side effect. C5 — flip rollout default to CASCADE. Schema and migration now default new schools to the legacy filter; WEIGHTED is opt-in per-tenant via DB flip until a settings UI ships. C7 — `AlgorithmTab.handleReset` hardcoded `enabled: true` for every rule and wiped `config` blocks. Now reads `defaultEnabled` from the registry and preserves existing config (the workload-window settings admins set explicitly). C3 — explicit caveat in `scoreCandidateForSlot` docstring that `prefer_consecutive` is excluded from Stage 2 because the prior-period teacher is null; a swap can break a deliberately-consecutive pair. Tests: Q4 multi-tenant CASCADE/WEIGHTED isolation, Q5 hard_weekly_cap exclusion in WEIGHTED mode, C4 3-proposal forward-sweep ordering. --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
Summary
getSchoolId()demo fallback withrequireSchool()to prevent unauthenticated accessTest Plan
🤖 Generated with Claude Code