# EMBA Specialization Solver A client-side web application that helps EMBA students optimize their elective course selections to maximize the number of specializations they can earn. ## The Problem The J27 EMBA program offers 46 elective courses across 12 elective sets (Spring, Summer, Fall terms). Students select one course per set — 12 electives total, 30 credits. The program defines 14 specializations, each requiring 9+ credits (at least 4 qualifying courses). The catch: course credits **do not duplicate** across specializations. When a course qualifies for multiple specializations, its 2.5 credits must be allocated (potentially split) among them. This makes course selection a non-trivial credit allocation optimization problem. Key constraints: - **Credit sharing**: Each course's 2.5 credits are split across qualifying specializations — no double-counting - **Maximum 3 specializations**: 12 courses × 2.5 credits = 30 total, and 3 × 9 = 27, so 3 is the theoretical max - **Required courses**: 4 specializations require a specific course to be selected - **Strategy S1/S2 tiers**: The Strategy specialization limits S2-marked courses to at most 1 contributing ## Features - **Two optimization modes**: - **Maximize Count** — finds the largest set of achievable specializations, using ranking as a tiebreaker - **Priority Order** — processes specializations in your ranked order, greedily adding each if feasible - **Drag-and-drop ranking** — reorder specializations by priority - **Live optimization** — results update instantly as you select courses - **Decision tree analysis** — a Web Worker enumerates remaining course combinations to show ceiling outcomes per choice (how many specializations each option can lead to) - **Status tracking** — each specialization is classified as achieved, achievable, missing a required course, or unreachable - **Mode comparison** — shows what the alternative mode would produce so you can pick the better result - **Responsive** — mobile layout with floating status banners - **State persistence** — selections and rankings saved to localStorage ## Tech Stack - **React 19** + **TypeScript** - **Vite 7** (dev server, bundler) - **javascript-lp-solver** — linear programming for credit allocation feasibility checks - **@dnd-kit** — drag-and-drop for specialization ranking - **Vitest** — test runner - **Nginx** — production static file server (Docker) ## Prerequisites - **Node.js** >= 22 - **npm** - **Docker** and **Docker Compose** (for containerized deployment) ## Development All commands run from the `app/` directory: ```bash cd app ``` ### Install dependencies ```bash npm install ``` ### Start the dev server ```bash npm run dev ``` The app will be available at `http://localhost:5173` with hot module replacement. ### Run tests ```bash npm test ``` Or in watch mode: ```bash npm run test:watch ``` ### Lint ```bash npm run lint ``` ### Build for production ```bash npm run build ``` Output goes to `app/dist/`. ### Preview production build locally ```bash npm run preview ``` ## Deployment ### Docker Compose (recommended) From the project root: ```bash docker compose up -d ``` This builds a multi-stage Docker image: 1. **Build stage** — installs dependencies and runs `vite build` in a Node 22 Alpine container 2. **Serve stage** — copies the built static files into an Nginx Alpine container The app is served on port **8080** by default. Override with the `PORT` environment variable: ```bash PORT=3000 docker compose up -d ``` ### Docker (standalone) ```bash docker build -t emba-solver . docker run -p 8080:80 emba-solver ``` ### Static hosting Run `npm run build` in `app/` and deploy the `app/dist/` directory to any static file host (Netlify, Vercel, S3, GitHub Pages, etc.). The app is fully client-side with no backend dependencies. ## Project Structure ``` ├── Dockerfile # Multi-stage build (Node → Nginx) ├── docker-compose.yml # Single-service compose config ├── nginx.conf # Nginx config with gzip, caching, SPA fallback └── app/ # Vite + React application ├── src/ │ ├── main.tsx # Entry point │ ├── App.tsx # Root component │ ├── data/ # Static course/specialization data │ │ ├── types.ts # TypeScript interfaces │ │ ├── courses.ts # 46 courses with qualifications │ │ ├── electiveSets.ts # 12 elective sets │ │ ├── specializations.ts # 14 specializations │ │ └── lookups.ts # Derived indexes │ ├── solver/ # Optimization engine │ │ ├── optimizer.ts # Maximize-count & priority-order modes │ │ ├── feasibility.ts # LP-based feasibility checks │ │ └── decisionTree.ts # Exhaustive ceiling analysis │ ├── components/ # UI components │ ├── state/ # App state (useReducer + localStorage) │ ├── hooks/ # Custom hooks (useMediaQuery) │ └── workers/ # Web Worker for decision tree └── vite.config.ts ``` ## How the Solver Works 1. **Feasibility checking** — uses a linear program (LP) to determine whether a target set of specializations can each reach 9 credits given the selected courses, respecting per-course capacity (2.5 max) and the Strategy S2 constraint 2. **Maximize Count** — tries all combinations of candidate specializations from size 3 down to 1, checking LP feasibility for each; among equal-size feasible sets, picks the one with the highest priority score based on ranking 3. **Priority Order** — iterates specializations in rank order, greedily adding each to the achieved set if the combined set remains LP-feasible 4. **Decision tree** — for each open (unselected) elective set, enumerates all possible remaining course combinations to compute the best-case outcome per choice, helping users identify which selections matter most