Skip to content

About

Schedule crafting passion project

Resources

Stars

5 stars

Watchers

2 watching

Forks

Latest commit

 

History

420 Commits

Folders and files

Repository files navigation

Schedule Optimizer

A course scheduling tool for Western Washington University students. Add your courses, set preferences, and generate optimized, conflict-free schedules instantly.

Try it live

Disclaimer: This project is not affiliated with Western Washington University. It is an independent initiative developed for educational and personal use. All data is for informational purposes only and should not be considered official or binding.

Features

Schedule Generation

  • Add courses by subject + number (e.g., "CSCI 247") or by CRN
  • Mark courses as required or optional with configurable min/max bounds
  • Pin specific sections to lock in a preferred CRN
  • Blocked times — paint time slots as unavailable with named groups, custom colors, opacity, and hatching patterns
  • Browse generated schedules with scoring based on gaps, start time, and end time preferences
  • View async/TBD courses separately
  • Final exams on each section with a scheduled final, matched to the registrar's finals schedule; times the meeting pattern leaves ambiguous (labs, several meeting times, once-a-week classes) are marked as guesses
  • Finals week view of the current schedule's exams, toggled from the schedule header
  • Add to Calendar: download an .ics file with classes (holidays skipped), final exams in their class's room, holidays, registration phases, and add/drop/withdraw deadlines, the term's dates titled short (W27 - Drop deadline (no W)) with the registrar's full wording in the description

Term Dates

  • Sidebar status line for the selected term: countdown to registration, the next registration phase, or finals week, and a pointer to the next term when its registration is about to open
  • Key dates popover: registration phases, classes, holidays, and finals week, marked when the dates are still estimates

Under the hood, schedule generation uses a bitmask-based conflict detection algorithm with backtracking and pruning. Each section is encoded as a 450-bit time mask (5 days x 90 ten-minute slots from 7am-10pm), enabling O(1) conflict checks. On a modern CPU, conflict detection runs in ~3ns and a full 10-course generation completes in under 2ms.

Course Search

  • Search across a single term, an academic year, or all historical data
  • Filter by subject, course number, title, instructor, open seats, and credit range
  • Results scored by relevance and term recency
  • Add courses or specific CRNs directly from search results to the schedule builder

Data Pipeline

  • Automated scraping from WWU's Banner API with concurrent page fetches
  • Background job scheduler that scrapes each term on a cadence set by its phase (every 8 hours during registration, daily before registration and in session, once when past; a failed scrape retries hourly), with phases taken from the registrar's published registration and term dates
  • Course-centric data model supporting cross-term search — organized by unique course identity, not term hierarchy
  • SQLite with WAL mode for concurrent reads during scraping

Frontend

  • Dark/light theme with system preference detection
  • Mobile-responsive layout with drawer navigation
  • Export schedules as PNG (with native share API on mobile)
  • Announcement banners and in-app feedback submission
  • All state persisted to localStorage (courses, filters, schedules, preferences)

Tech Stack

Layer Technology
Backend Go 1.26, Gin, SQLite (WAL mode)
SQL sqlc (type-safe codegen), golang-migrate
Frontend React 19, TypeScript, Vite
Styling Tailwind CSS v4, shadcn/ui
State Zustand (persisted), TanStack Query
Testing Go test + benchmarks, Vitest
Logging slog + tint

Architecture

Banner API        → Scraper  ─┐
registrar.wwu.edu → Calendar ─┴→ SQLite → Cache (in-memory) → REST API → React Frontend
                       ↑
          Jobs Service (scheduled scraping)

Backend

backend/
├── cmd/server/          # Entry point
├── migrations/          # SQL migration files (golang-migrate)
├── internal/
│   ├── api/             # HTTP handlers
│   ├── cache/           # In-memory term snapshots, loaded on request, dropped after jobs rewrite them
│   ├── calendar/        # Registrar calendar scraper, term phases, finals matching
│   ├── config/          # Environment configuration
│   ├── db/              # SQLite connection setup
│   ├── generator/       # Bitmask conflict detection + backtracking
│   ├── jobs/            # Background job scheduler (scrape scheduling)
│   ├── meeting/         # Meeting-time payload shared by every section response
│   ├── scraper/         # Banner API client + data extraction
│   ├── search/          # Course search with scoring
│   ├── server/          # Router, middleware, graceful shutdown
│   ├── static/          # Embedded frontend serving
│   ├── store/           # sqlc-generated query code
│   └── termcode/        # Term code (YYYYQQ) helpers
├── Makefile
└── sqlc.yaml

Frontend

frontend/src/
├── components/
│   ├── ui/                  # shadcn/ui primitives
│   ├── schedule/            # Grid, time blocks, course dialogs, blocked times
│   └── schedule-builder/    # Course input, rows, previews
├── hooks/                   # API hooks, theme, export, drag-to-paint
├── stores/                  # Zustand store (persisted to localStorage)
└── lib/                     # API client, schedule utils, term status, finals, .ics export

API

Method Endpoint Description
GET /api/health Health check
GET /api/terms Available terms with phase, start and end, and registration opening
GET /api/calendar/:term Registration phases, holidays, deadlines, and finals week
GET /api/subjects Subject codes (optionally by term)
GET /api/course/:subject/:courseNumber Course details + sections
GET /api/search Filtered course search
GET /api/crn/:crn CRN lookup
POST /api/courses/validate Batch validate courses
POST /api/generate Generate schedule combinations
GET /api/announcement Active announcement
POST /api/feedback Submit feedback

Section payloads from generate, search, and CRN lookup carry final ({date, start, end, certain, reason?, location?}, the location being the room of the meeting time the exam is based on) when the section has a final exam slot, and mark lab meetings with lab: true. Calendar deadlines carry the registrar's wording as name and a short title for calendar events.

Getting Started

Prerequisites

  • Go 1.26+
  • Node.js 22.13+ and pnpm
  • SQLite 3
  • sqlc — go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
  • golang-migrate — go install -tags 'sqlite3' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

Backend

cd backend
cp .env.example .env        # configure environment
make migrate-up             # apply database migrations
make run                    # start server (default port 48920)

Frontend

cd frontend
pnpm install
pnpm dev                    # dev server at http://localhost:5173

Production Build

cd frontend && pnpm build   # compile frontend
cd ../backend && make build # builds binary with embedded frontend
./bin/server                # single binary serves everything

Useful Commands

# Backend
make test                   # run all Go tests
make bench                  # run benchmarks
make sqlc                   # regenerate query code after editing queries.sql

# Frontend
pnpm lint                   # eslint
pnpm test                   # vitest
pnpm format                 # prettier

Environment Variables

PORT=48920                          # server port
ENVIRONMENT=development             # development | production
DATABASE_PATH=data/schedule.db      # SQLite database location
CORS_ALLOWED_ORIGINS=http://localhost:5173
SCRAPER_CONCURRENCY=4               # parallel page fetches
JOBS_ENABLED=true                   # background scraping
JOBS_ACTIVE_SCRAPE_HOURS=8          # hours between scrapes of terms in registration
JOBS_DAILY_SCRAPE_HOUR=3            # Pacific hour (0-23) for daily scrapes
JOBS_PAST_TERM_YEARS=5              # years of historical data to backfill

The frontend build reads VITE_SITE_URL (the production origin, used by index.html's canonical and social tags and as the host in calendar event UIDs) from frontend/.env. Override it in frontend/.env.local or the build environment; the build fails if it is unset.

Testing

The project uses table-driven tests throughout. Backend test coverage spans all major packages:

internal/generator   — unit + benchmark (bitmask, backtracking, scoring)
internal/search      — unit + fuzz + benchmark
internal/jobs        — unit (scheduler, simulated scrape cadence)
internal/calendar    — unit + integration (parsers on captured pages, phases, storage, finals matching, term calendar)
internal/api         — integration (handler endpoints)
internal/cache       — unit
internal/scraper     — unit
internal/db          — unit
internal/config      — unit
internal/static      — unit

Frontend tests cover the Zustand store, schedule utilities, and warning generation (118 tests).

Authors

Cooper Morgan — Author & Maintainer cwooper.dev · @Cwooper

Konnor Kooi — Previous Contributor konnorkooi.com · @konnorkooi

Previous contributors include Rory Bates, Arne Wiseman, and Ben Huynh.

License

MIT License — see LICENSE for details.

About

Schedule crafting passion project

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages