b282709476
The v1.3.1 comparator used a sum-of-weights priorityScore. With weights
15..1 across 15 specs, three lower-priority specs (BNK+BRM+CRF, sum 39)
could outrank a single top-priority spec (HCR alone, sum 15). In
priority-order mode this surfaced lower-priority plans above the user's
top spec — the opposite of intent.
Fix: replace sum-of-weights with a lexicographic rank weight. Each spec
encodes as a bit, top-ranked spec = highest bit. So [HCR] = 16384 beats
[BNK,BRM,CRF,EMT,ENT,FIN,FIM,GLB,LCM,MGT,MKT,MTO,SBI,STR] = 16383. A plan
containing a higher-ranked spec ALWAYS outranks any plan that doesn't,
regardless of how many lower-ranked specs the latter contains. Lower
specs only act as tiebreakers among plans that all contain the same
higher-ranked spec.
Both modes use lex weight as the priority key; modes still differ in
ordering:
priority-order: (rankWeight desc, count desc, key asc)
maximize-count: (count desc, rankWeight desc, key asc)
Score display changes from the legacy sum (e.g. "score 29") to the lex
weight in compact form (e.g. "score 24.6k"). Hover for full integer.
The display now actually corresponds to ranking order.
Other:
- Cache cap (500k leaves) now retains existing entries instead of
clearing on overflow. New entries past the cap are dropped; the
cached subset stays available as a warm starting point.
- Two new lex-weight tests in searchDecisionTree.test.ts:
- single top-ranked spec outweighs all 14 others combined
- tiebreaker is the next-ranked spec
- All 84 tests pass; cached leaves stay valid across the comparator
change since achievedSpecs (the input to lex compare) is unchanged.
Files: solver/priority.ts (new functions), solver/decisionTree.ts
(comparators take ranking), components/{TopPlans,CourseSelection}.tsx
(score display + Recommended badge), state/appState.ts (cache-cap
behavior), vite.config.ts, CHANGELOG.md.
12 KiB
12 KiB
Changelog
v1.3.3 — 2026-05-09
Changes
- Lexicographic priority comparison — fixes a scoring bug where combinations of lower-priority specializations could outrank a single higher-priority specialization in priority-order mode. The comparator now uses lex-by-rank: a plan containing a higher-ranked specialization always beats a plan that doesn't, regardless of how many lower-ranked specializations the latter contains. Lower-ranked specializations only act as tiebreakers among plans that all contain the same higher-ranked specs. Same logic also tiebreaks within maximize-count mode.
- Score display matches the comparator — the per-plan score now shows the lexicographic rank weight in compact form (e.g.
score 24.6k) instead of the legacy sum-of-weights. Hover the score for the full integer. - Cache cap retains warm entries — when the leaf cache hits the 500k cap, new entries are now dropped instead of clearing the cache; the existing 500k stay as a starting point for subsequent pin/unpin operations.
- Cache stays valid across the comparator change — leaves cached under v1.3.2 still produce correct rankings under the new comparator since
achievedSpecs(the input to lex compare) is unchanged.
v1.3.2 — 2026-05-09
Changes
- Leaf cache for instant pin/unpin — decision-tree leaf outcomes are now cached on the main thread keyed by their full 12-course assignment. Pin operations filter the cache and re-derive the top-K + per-set ceilings instantly with no worker spawn. Unpin operations show the cached subset immediately and stream improvements as a background worker fills in the missing leaves. The cache persists across pin, unpin, and adopt-plan operations.
- Cache invalidation — the cache is cleared only when the active mode or the specialization ranking changes. Pin/unpin alone never invalidates.
skipKeysworker contract — workers now accept a list of cached assignment keys and skip the optimizer call for any leaf already in the cache, while still counting iterations toward the global progress percentage.leafEvaluatedworker event — workers stream individual leaf outcomes to the main thread for cache population as the search progresses.deriveFromLeavesshared helper — pure function that produces the top-K and per-set ceilings from a leaf collection; used by both the main-thread cache filter and the worker's final emission for parity.- 500,000-leaf soft cap — the cache is cleared if it grows beyond 500k entries, bounding worst-case memory at ~150 MB. Typical sessions stay well below.
v1.3.1 — 2026-05-09
Changes
- Exhaustive decision-tree search — replaced the saturation early-termination with full enumeration of the open-set cartesian product. Per-set ceiling cells now reflect the true best outcome for every (set, course) pair instead of leaving most cells stuck at "0 specs". Top Plans surfaces all genuinely-feasible plans, including 3-spec maximize-count plans that the v1.3.0 search missed. The previous iteration cap has been removed; search runs to full completion.
- Mode-dependent enumeration ordering — priority-order mode keeps the priority-target-first heuristic; maximize-count mode now orders DFS children by descending count of qualifications for reachable specializations, surfacing generalist courses (e.g., Climate Finance with 6 qualifications) before specialists.
- Mode-aware comparator — top-K and per-cell ceiling rankings now match the active mode: priority-order ranks by
(priorityScore, count)so the top-priority spec surfaces; maximize-count ranks by(count, priorityScore)so the highest count wins. Recommended badges follow the same rule. - "Recommended" badge per set — each elective set now highlights the choice with the best ceiling outcome under the current mode. Rendered inline next to the course name to keep button height stable.
- Color-coded spec tags — the per-cell outcome list and the Top Plans badges now use a fixed per-spec color palette so each specialization is visually identifiable at a glance.
- "Top outcome if picked ↓" caption — added a small column header on each open elective set so the spec tags are clearly identified as decision-tree outcomes (not the course's own qualifications).
- Visual progress bar — Top Plans header now shows a progress bar with
iterations / total · NN%while the search runs, replacing the earlier text-only count. - Per-cell streaming indicators — courses that haven't been evaluated yet show a "searching" pulse instead of misleading "0 specs"; cells transition to their final value as the search completes.
- Per-set spinner — each elective set heading shows a spinner while at least one of its choices is still unevaluated.
v1.3.0 — 2026-05-09
Changes
- Top Plans panel — new ranked list of up to 10 complete course plans, each showing the achieved specializations and the courses to pin. An "Adopt plan" button pins all of a plan's courses in one click. Updates progressively as the search finds better outcomes.
- Priority-aware decision tree — fixes the bug where a specialization could show "Achievable" without any per-set ceiling cell surfacing it. The decision-tree search now compares enumerated combinations by
(count desc, priority score desc)and reorders DFS children so courses qualifying for the user's first reachable ranked spec are tried first, surfacing high-priority outcomes early. - Bounded search with saturation termination — search stops when the top-K stabilizes (default 500 stable iterations) or when the iteration cap (10000) is hit; partial results are flagged in the UI.
- Per-cell streaming — the worker now emits per-cell ceiling updates instead of per-set rollups, so the per-set table refines progressively rather than appearing in coarse chunks.
v1.2.2 — 2026-05-09
Changes
- Healthcare specialization (HCR) — added 15th specialization, Healthcare; qualifies via The Business of Health & Medical Care (Spring Set 2), Analytics & Machine Learning for Managers (Spring Set 3), Digital Marketing Strategy in Practice (Summer Set 2), and Managing Change (Fall Set 1); 10 total credits available, no required course gate
- Course rename — "Social Media and Mobile Technology" renamed to "Digital Marketing Strategy in Practice"; description replaced with new MSKCC-anchored content covering digital strategy and agentic AI; instructor cleared pending confirmation
- Cancellations (Approach B) — switched from delete-and-replace to flagging cancelled courses with
cancelled: true. "Customer Insights" (Spring Set 5) is now marked cancelled. "Managing Growing Companies" reappears in Summer Set 2 as a cancelled placeholder per the J27 sheet - Reachability test —
data.test.tsnow excludes cancelled courses when counting per-spec set reachability, so future cancellations are caught by an obvious assertion failure
v1.2.1 — 2026-03-27
Bug Fixes
- Achievable status accuracy — specializations marked "Achievable" are now verified via LP feasibility check against already-achieved specs; previously, a specialization could show "Achievable" based on raw credit potential while actually being infeasible due to credit sharing with higher-priority achieved specializations
v1.2.0 — 2026-03-27
Changes
- Course info popovers — each course now has an info icon that opens a popover showing the course description, instructor(s), and specialization tags, extracted from the J27 Electives PDF; opens on hover (desktop) or tap (mobile), with smart positioning that flips above when near the bottom of the viewport
- Page title and favicon — updated browser tab from "app" with Vite icon to "EMBA Specialization Solver" with a graduation cap favicon in NYU Stern purple
- Viewport-fitted layout — desktop layout now fits within the viewable area without page-level scrolling; each pane scrolls independently
v1.1.1 — 2026-03-27
Changes
- Course replacement — replaced cancelled "Managing Growing Companies" with new course "Innovation and Design" in Summer Elective Set 2; qualifies for Brand Management, Entrepreneurship and Innovation, Marketing, and Strategy (S2)
v1.1.0 — 2026-03-13
Changes
- Cancelled course support — "Managing Growing Companies" (Summer Elective Set 2) is marked as cancelled and rendered with strikethrough, greyed-out styling, and a "(Cancelled)" label; it is excluded from solver computations and decision tree enumeration
- Duplicate course prevention — courses that appear in multiple elective sets (e.g., "Global Immersion Experience II" in Spring Set 1 and Summer Set 1, "The Financial Services Industry" in Spring Set 2 and Fall Set 4) are now linked; selecting one automatically disables and excludes its duplicate from selection and solver calculations, shown with an "(Already selected)" label
- Credit bar tick marks — specialization progress bars now display light vertical tick marks at 2.5-credit intervals for visual scale reference, layered above bar fills with the 9.0 threshold marker remaining visually distinct
v1.0.0 — 2026-02-28
Initial release of the EMBA Specialization Solver.
Features
- Optimization engine — LP-based credit allocation solver with two modes:
- Maximize Count — finds the largest feasible set of specializations, using ranking as tiebreaker
- Priority Order — greedily adds specializations in user-ranked order
- Course selection UI — select one course per elective set across 12 sets (Spring, Summer, Fall terms)
- Drag-and-drop specialization ranking — reorder the 14 specializations by priority with touch and keyboard support
- Decision tree analysis — Web Worker enumerates remaining course combinations to compute ceiling outcomes per choice
- Status tracking — each specialization classified as achieved, achievable, missing required course, or unreachable
- Mode comparison — displays what the alternative optimization mode would produce
- Credit bars and allocation breakdowns — visual progress toward the 9-credit threshold with expandable per-course detail
- Credit legend — collapsible explainer for bars, badges, and limits
- Required course labels — courses that are prerequisites for a specialization show "Required for ..." labels
- Algorithm explanations — clear descriptions of how each optimization mode works
- Skeleton loading — placeholder UI while decision tree analysis runs
- Auto-expand achieved specializations — achieved specs show their credit breakdown by default
- Responsive layout — two-panel grid on desktop/tablet, single-column on mobile
- Mobile floating banners — top banner summarizes specialization statuses, bottom banner shows selection progress (N/12); both appear via IntersectionObserver and scroll to their sections on tap
- CSS transitions and animations — cross-fade course pin/unpin, credit bar width changes, status badge color transitions, expand/collapse panels, mode toggle switching, flash on status changes; all respect
prefers-reduced-motion - State persistence — rankings and selections saved to localStorage
- Docker deployment — multi-stage Dockerfile (Node 22 build → Nginx Alpine serve) with Docker Compose, gzip compression, SPA fallback routing, immutable cache headers for hashed assets, configurable port (default 8080)
- Full test suite — data integrity, feasibility solver, optimizer, and decision tree tests via Vitest
Constraints Modeled
- Credit non-duplication (2.5 credits per course shared across specializations)
- Maximum 3 specializations (30 total credits, 9 required each)
- Required course prerequisites for 4 specializations
- Strategy S1/S2 tier system (at most 1 S2 course contributes to Strategy)
- Mutual exclusion from same-set conflicts