Fixes the bug where a specialization could show "Achievable" while no
per-set ceiling cell surfaces a path to it. Reproduction: pin SP2=Business
of Health & Medical Care, SP4=Foundations of Fintech, SP5=Corporate Finance,
SE1=GIE; rank HCR first. Healthcare showed Achievable but every ceiling
cell excluded HCR.
Root cause: computeCeiling used strict > on count alone, so the first
equal-count combination found won permanently and HCR-including outcomes
were never recorded.
Changes:
- Replace per-(set, choice) computeCeiling loop with a single full-tree
searchDecisionTree DFS. Both the per-set ceiling table and a new ranked
top-K plan list (default K=10) are populated from one enumeration.
- Comparison rule everywhere is (count desc, priority score desc,
deterministic-tiebreak). priorityScore extracted from optimizer.ts
into a shared priority.ts module used by both call sites.
- Heuristic enumeration ordering: select the first reachable ranked spec
as priorityTarget; reorder DFS children at every level so target-
qualifying courses are tried first. High-priority outcomes surface in
early iterations instead of being blocked by less-relevant equal-count
results.
- Bounded search: terminate on saturation (top-K stable for 500
iterations) or hard cap (10000 iterations); set partial=true if cap
hit. Mitigates the worst-case enumeration cost.
- Worker protocol: tagged-union response with topKUpdate, choiceUpdate
(per-cell, replaces per-set setComplete), and allComplete events.
- App state adds topPlans/topPlansPartial slices and an adoptPlan action
that pins a plan's full course assignment in one click. Also fixes
loadState's stale "ranking.length !== 14" check (now uses
SPECIALIZATIONS.length so HCR-era saved state restores correctly).
- New TopPlans component renders the ranked list with adopt buttons,
placed above CourseSelection in the right column.
- 17 new tests in searchDecisionTree.test.ts covering priority scoring,
bounded ranked list, comparison rule, target selection, the user's
reproduction scenario, streaming monotonicity, saturation termination,
and a performance smoke test (< 5s for the 8-open-set case).
- Existing decisionTree.test.ts: one test amended for per-cell streaming
semantics; remaining 3 unchanged and passing.
Apply the J27 (5/6/2026) Stern specialization sheet:
- Add Healthcare (HCR) as the 15th specialization, with HCR cross-listings on
spr2-health-medical, spr3-analytics-ml, sum2-social-media (renamed), and
fall1-managing-change. 10 credits available, no required-course gate.
- Rename sum2-social-media to "Digital Marketing Strategy in Practice";
replace its description with new MSKCC-anchored content; clear instructor
pending confirmation of new lead.
- Switch from delete-and-replace to the previously-unused cancelled flag
(Approach B): mark spr5-customer-insights cancelled, add Managing Growing
Companies back to Summer Set 2 as a cancelled placeholder per the printed
sheet.
- Update data integrity tests: course count 46 -> 47, spec count 14 -> 15;
per-spec "across sets" helper now filters cancelled courses so future
cancellations trigger an obvious assertion failure (BRM 6 -> 5,
MKT 7 -> 6, HCR 4 new).
- Replace hardcoded 14 in optimizer.test.ts with SPECIALIZATIONS.length.
determineStatuses() was marking specs as 'achievable' based solely on
per-specialization upper bounds, ignoring credit sharing with achieved
specs. Now performs an LP feasibility check to verify the spec can
actually be achieved alongside the current achieved set.
- Course info popovers with description, instructors, and specialization
tags; opens on hover (desktop) or tap (mobile) with smart positioning
- Page title and graduation cap favicon in NYU Stern purple
- Desktop layout fits viewport without page-level scrolling
Replace cancelled course in Summer Elective Set 2 with new course
"Innovation and Design" qualifying for Brand Management, Entrepreneurship
and Innovation, Marketing, and Strategy (S2).
- Mark "Managing Growing Companies" as cancelled with visual indicator and solver exclusion
- Prevent selecting duplicate courses across elective sets (e.g., same course in Spring and Summer)
- Add 2.5-credit interval tick marks to specialization progress bars
- Bump version to 1.1.0 with date display in UI header
On mobile, the single-column layout makes it easy to lose context when
scrolling between the specializations and course selection panels. This adds
two floating banners that appear via IntersectionObserver:
- Top banner: summarizes specialization statuses (achieved/achievable/missing/unreachable)
- Bottom banner: shows course selection progress (N/12 selected)
Both slide in/out with CSS transitions and scroll to their respective
sections on tap. Only rendered on mobile viewports (max-width: 639px).
- Replace terse one-line optimization mode descriptions with clearer multi-sentence
explanations of how Maximize Count and Priority Order algorithms behave
- Add skeleton loading placeholders on course buttons while analysis is pending
- Auto-expand achieved specializations to show credit breakdown by default
- Add instructional subtitles to Course Selection and Specializations sections
- Make Clear and Clear All buttons more prominent with visible backgrounds
Replace top-level mutual exclusion banner with dynamic per-course
"Required for ..." labels derived from specialization data. Labels
appear on any course that is a specialization prerequisite, across
all elective sets.