Add project documentation and changelog
Populate README with problem description, features, tech stack, development/deployment instructions, project structure, and solver explanation. Add CHANGELOG.md marking current state as v1.0.0.
This commit is contained in:
36
CHANGELOG.md
Normal file
36
CHANGELOG.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
161
README.md
161
README.md
@@ -1,3 +1,160 @@
|
||||
# emba-course-solver
|
||||
# EMBA Specialization Solver
|
||||
|
||||
EMBA course 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
|
||||
|
||||
Reference in New Issue
Block a user