feat(auto-assign): default internal matching to lexicographic ranking#146
Closed
guangshinhaha wants to merge 1 commit into
Closed
feat(auto-assign): default internal matching to lexicographic ranking#146guangshinhaha wants to merge 1 commit into
guangshinhaha wants to merge 1 commit into
Conversation
Replaces the cascading-filter algorithm as the default for internal teacher auto-assignment. Each rule now scores candidates in [0,1] and the pool is ranked by the score tuple; no candidate is eliminated mid-cascade, so a teacher slightly heavier on workload can still win the slot if they're the only subject + class match. The legacy cascade stays available as School.algoMode = 'CASCADE' for schools that prefer strict rule-priority elimination — flip via DB until a settings UI ships. Settings page now surfaces the hard constraints (no self-cover, no double-book, no assign while sick, hard weekly cap) as locked uneditable cards above the soft-rule list, and the soft-rule copy is updated to describe ranking instead of cascade narrowing. hard_weekly_cap moves out of the soft list (it remains in the rule registry for cap-value config).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
4 tasks
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.
Replaces the cascading-filter algorithm as the default for internal teacher
auto-assignment. Each rule now scores candidates in [0,1] and the pool is
ranked by the score tuple; no candidate is eliminated mid-cascade, so a
teacher slightly heavier on workload can still win the slot if they're the
only subject + class match. The legacy cascade stays available as
School.algoMode = 'CASCADE' for schools that prefer strict rule-priority
elimination — flip via DB until a settings UI ships.
Settings page now surfaces the hard constraints (no self-cover, no
double-book, no assign while sick, hard weekly cap) as locked uneditable
cards above the soft-rule list, and the soft-rule copy is updated to
describe ranking instead of cascade narrowing. hard_weekly_cap moves out
of the soft list (it remains in the rule registry for cap-value config).