From 10b56789e21cc20b3365fd5136424e5b3b7c40f7 Mon Sep 17 00:00:00 2001 From: Bill Ballou Date: Sun, 1 Mar 2026 11:35:55 -0500 Subject: [PATCH] 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. --- CHANGELOG.md | 36 ++++++++++++ README.md | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d7b492c --- /dev/null +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index e5e0403..a4915ff 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,160 @@ -# emba-course-solver +# EMBA Specialization Solver -EMBA course solver \ No newline at end of file +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