Skip to content

Latest commit

 

History

153 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ta-course-match

TA-to-course assignment tool for the McMaster ECE department. Generates optimized assignments of graduate students to undergraduate courses for the annual TA cycle, replacing the manual CAS-based process.

Built on Timefold for constraint-based optimization. The CLI is the V1 surface — designed for a non-technical office admin. A V2 web UI (FastAPI + Next.js) ships alongside it for student/instructor self-service and admin review; see PLAN.md for the full plan and TODO.md for the sprint backlog.

What V1 does

  • Loads instructors, courses, students, and their preferences from CSV files.
  • Validates the inputs and reports problems in plain English (missing files, unknown MACIDs, capacity shortfalls, infeasible vetos, missing preferences).
  • Solves a constraint optimization problem balancing instructor + student preferences, prior-TA experience, and (optionally) term spread.
  • Exports results in four formats: master_list.csv (matches the existing CAS Excel format), per_course.csv, per_student.csv, manual_review.csv, plus canonical assignments.json/yaml/xml.
  • Explains the final score with Timefold's per-constraint breakdown: which constraint contributed what, and which exact (assignment, ranking) pairs triggered each match.

Out of scope for V1 (the CLI): web forms, email integration, database persistence. The admin distributes / collects CSV templates via their normal email client. The V2 web stack adds web forms, a SQLite/Postgres backing store, and an admin import path that wraps the same loaders — production SSO is still pending (the dev build runs an X-MacID header shim).

Prerequisites

  • Python 3.12 (pinned in .python-version; Timefold's Python translator currently caps at 3.12).
  • Java 17+ at runtime — Timefold needs a JDK. Easiest install on macOS/Linux: SDKMAN!sdk install java 17.0.14-tem.
  • uv for environment management.

Install

uv sync

Run

The CLI is one entry point (tcm) with five subcommands wrapping a full assignment cycle. See PLAN.md §8 for the workflow rationale.

# 1. Scaffold a new cycle's input directory with CSV templates
uv run tcm init 2026-27
#  → data/2026-27/ now contains *_template.csv files + config.yaml + README.md

# 2. (Admin work) Pre-fill roster CSVs, email *_preferences templates,
#    drop the returned files in as e.g. student_preferences.csv

# 3. Validate before solving — admin-friendly diagnostics
uv run tcm validate 2026-27

# 4. Run the solver (default time budget: 30s)
uv run tcm solve 2026-27 --time-limit-seconds 60
#  → results/2026-27/run-<timestamp>/ contains master_list.csv +
#    per_course / per_student / manual_review / assignments.{json,yaml,xml}

# 5. (Optional) Re-export a prior run without re-solving
uv run tcm report 2026-27

# Run the full pipeline against synthetic data (no input files needed)
uv run tcm demo

See uv run tcm <subcommand> --help for flags. Notable ones:

  • --constraint-version {default,with_term_spread,full} — pick a constraint set.
  • --solving-method {blocking,solver_manager}solver_manager streams live best-score updates while solving.
  • --time-limit-seconds N — solver budget (default 30 for solve, 10 for demo).
  • --forceinit overwrites an existing cycle; solve proceeds despite validation errors.

What the output looks like

After tcm solve, the run directory contains:

File Purpose
master_list.csv One row per filled seat — matches the CAS "Master List" Excel format.
per_course.csv One row per course; lists assigned students and staffing status.
per_student.csv One row per student; lists assigned courses and load vs. capacity.
manual_review.csv Understaffed courses, unassigned slots, under-loaded students.
score_breakdown.txt Human-readable summary of total/filled/unfilled seats.
assignments.json/yaml/xml Canonical structured form for downstream tooling.

The terminal log itself is verbose — pre-solve problem summary, sanity check, streaming solver progress, post-solve per-constraint score breakdown (with the top indictments per constraint), and a per-course + per-student assignment dump.

Web UI

The web stack adds a student portal (rank preferences), an instructor portal (rank candidates), and an admin dashboard (cycle wizard, validate, solve, review, lock) on top of the same solver and loaders. Local dev needs Node 20+ and the [web] Python extras.

uv sync --extra web
uv run alembic upgrade head
TCM_ADMIN_MACIDS=admin1 uv run tcm-web      # backend on :8000 (Swagger at /docs)

# in another terminal:
cd frontend
npm install
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 npm run dev   # frontend on :3000

Auth is a shim during development: send X-MacID: <macid> on every request. Admins are the MacIDs listed in TCM_ADMIN_MACIDS; students and instructors are anyone whose MacID was imported into the current cycle. The shim refuses to boot in production — flip TCM_AUTH_MODE=sso when the SSO seam lands (see PLAN.md §12.4).

Docker

If installing Python 3.12 + a JDK on the admin's machine is a pain, use the bundled Docker image instead. It ships the full Python + Java 17 runtime so the only prerequisite on the host is Docker Desktop. The host's data/, results/, and logs/ directories are bind-mounted into the container, so CSVs you edit in your text editor and result files the solver writes are visible on your filesystem immediately.

Build once:

docker compose build         # ~2-3 min first time, ~10 s on rebuilds

Two ways to run the CLI:

(1) One-shot subcommands — fresh container per invocation, removed on exit:

docker compose run --rm tcm demo
docker compose run --rm tcm validate 2026-27
docker compose run --rm tcm solve 2026-27 --time-limit-seconds 60

(2) Long-lived interactive shell — skip the per-command container startup, useful when you're iterating (validate → fix → re-validate → solve):

docker compose up -d tcm-shell                 # start the background container
docker compose exec tcm-shell bash             # drop into a shell
  > tcm validate 2026-27
  > tcm solve 2026-27
  > exit                                       # leaves the container running

The up -d step only needs to run once — until you call docker compose down, the tcm-shell container keeps running in the background, and you can re-enter it as often as you like:

docker compose exec tcm-shell bash             # re-enter — no rebuild, instant

Or run a single subcommand without dropping into a shell:

docker compose exec tcm-shell tcm solve 2026-27

When you're done with the session entirely:

docker compose down                            # stop + remove the container

The shell ships with a colored prompt, common ls aliases, and a brief cheatsheet banner (see docker/bashrc).

The image bundles data/example/ as offline reference data, so even a fresh clone with no real cycle inputs can be exercised end-to-end:

docker compose run --rm tcm solve example

Linux hosts: match the UID

On Docker Desktop (macOS / Windows) file ownership is mapped transparently to your host user. On a native Linux engine, rebuild with your UID so bind-mounted files don't end up owned by container-internal UID 1000:

APP_UID=$(id -u) APP_GID=$(id -g) docker compose build

Without compose

If you'd rather not use compose, the raw equivalent is:

docker build -t ta-course-match .
docker run --rm -it \
  -v "$PWD/data:/app/data" \
  -v "$PWD/results:/app/results" \
  -v "$PWD/logs:/app/logs" \
  ta-course-match solve 2026-27

Full web stack (FastAPI + Next.js)

The same compose file also runs the backend API and the Next.js dev server with hot-reload from your host source. Two images, both shipped from this repo:

docker compose build tcm-web tcm-frontend       # ~3 min first time, fast on rebuilds
TCM_ADMIN_MACIDS=admin1 docker compose up -d tcm-web tcm-frontend

Endpoints:

Source mounts: ./backend, ./src, and ./frontend are bind-mounted into the containers. Uvicorn auto-reloads (TCM_RELOAD=1) on Python changes; Next.js hot-reloads on TSX changes. Container-local node_modules and .next are anonymous volumes so the host's macOS/Apple-Silicon modules can't shadow the Linux ones.

Tail logs from one service:

docker compose logs -f tcm-web        # or tcm-frontend

Stop everything:

docker compose down

By default the backend writes to bundled SQLite under /app/data/tcm.sqlite3 (visible on the host bind mount). To use the local Postgres service instead:

TCM_DB_URL=postgresql+psycopg://tcm:tcm@postgres:5432/tcm \
  docker compose up -d postgres tcm-web tcm-frontend

Environment variables the backend service honors (set in your shell or a .env file next to compose):

Variable Purpose Default
TCM_ADMIN_MACIDS comma-separated admin MacIDs the auth shim recognizes admin1
TCM_AUTH_MODE shim (dev) or sso (production seam, not yet wired) shim
TCM_CORS_ORIGINS comma-separated allowed origins for the browser http://localhost:3000
TCM_DB_URL SQLAlchemy URL — opt into Postgres etc. bundled SQLite
NEXT_PUBLIC_API_BASE_URL API base used by the browser http://localhost:8000

Tests

uv run pytest           # default: fast tests
uv run pytest -m slow   # include slow / end-to-end solver tests (needs Java 17+)

Layout

See CLAUDE.md for the directory map, command cheatsheet, and non-obvious gotchas.

About

A TA to course assignment platform based on instructor/TA preferences and other constraints, using Timefold as scheduling optimizer.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages