# Fix: "Achievable" status ignores credit sharing with achieved specs **Date:** 2026-03-27 **Type:** Bugfix ## Problem `determineStatuses()` marks specializations as "achievable" based solely on per-specialization upper bounds (`computeUpperBounds`), which ignore credit sharing between specializations. A spec can show "achievable" (upper bound >= 9) even when it's infeasible alongside the already-achieved higher-priority specs because shared courses have committed their credits elsewhere. **Reproduction scenario:** - 11 courses selected, Fall 1 open - Priority: CRF > STR > LCM > MGT - LCM upper bound = 10 (from spr1-collaboration, spr3-M&A, fall1-managing-change, fall3-corporate-governance) - CRF + STR achieved, consuming credits from spr3 and fall3 - LCM shows "Achievable" but `checkFeasibility([all courses], ['CRF', 'STR', 'LCM'])` is infeasible for all S2 choices - Selecting Managing Change for Fall 1 achieves MGT (priority 4) instead of LCM (priority 3) ## Root Cause `determineStatuses()` in `optimizer.ts:146-185` checks only: 1. Whether the spec is in the achieved set 2. Whether the required course gate passes 3. Whether `computeUpperBounds` >= 9 (per-spec, ignoring sharing) It never checks whether the spec is actually feasible alongside the achieved set. ## Fix In `determineStatuses()`, after a non-achieved spec passes the upper bound check, add a feasibility check: call `checkWithS2(selectedCourseIds, [...achieved, specId])`. If infeasible, mark as `unreachable` instead of `achievable`. ### Changes **`optimizer.ts` — `determineStatuses` function:** - After the upper bound check passes (currently falls through to `statuses[spec.id] = 'achievable'`), add: ```ts const testSet = [...achieved, spec.id]; const feasResult = checkWithS2(selectedCourseIds, testSet); if (!feasResult.feasible) { statuses[spec.id] = 'unreachable'; continue; } ``` - No new parameters needed — `selectedCourseIds` is already passed to the function. ### What doesn't change - No new status types — reuses existing `unreachable` - No UI changes — `unreachable` already renders correctly with grey styling - `computeUpperBounds` unchanged — still used for credit bar display - `AllocationResult` type unchanged - `checkWithS2` helper already exists in the same file ### Test updates - Add a test for the bug scenario: given the specific course selection and CRF > STR > LCM > MGT ranking, verify LCM status is `unreachable` (not `achievable`) when CRF and STR are achieved - Existing test `marks achievable when required course is in open set` should be unaffected (uses all-open sets with no achieved specs, so feasibility check passes trivially) ### Performance 14 specializations total, at most ~10 non-achieved specs to check. Each check is a small LP solve. Negligible overhead.