Cypress Scheduler
A course-scheduling tool that turns a list of desired classes into conflict-free weekly schedules.
Every term starts with a spreadsheet nobody wants to build.
Cypress College has no visual course scheduler. Planning a term means cross-referencing the published section list against the myGateway portal by hand. You copy meeting times into a spreadsheet, then check every pair of classes for an overlap before your registration window opens.
The work is mechanical, but the stakes aren't: one missed overlap and a student registers into a week they can't actually attend, or gives up a section they needed while re-planning around it.
What the tool has to get right.
Search the real catalog, not a sample of it. Three terms of Cypress College sections, with live seat and waitlist counts refreshed on a schedule rather than typed in by hand.
No two meeting blocks in a generated schedule may overlap, including courses that meet on several days or split into separate lecture and lab meeting patterns.
A list of valid schedules is not an answer. Options render as a week and are ranked against the student's own constraints, so they can be judged at a glance instead of decoded.
Registration itself stays in myGateway. The app plans a term, it doesn't enroll you in one. Instructor quality is a link out to RateMyProfessors rather than something I model.
Add the courses you want, get the weeks that work.
Students search the catalog, add the courses they want, and the app generates conflict-free schedule options out of every section combination available. Those render on a weekly calendar they can step through, save to their account, and share by link.
Everything lives on one screen: search on one side, the week on the other, so adding a section and seeing where it lands is a single glance rather than a page transition. Plans sit side by side, because comparing “the Tuesday-heavy one” against “the one with no Fridays” is the decision students are actually making.
Three stages, one pass per change.
Scraped catalog
A Python scraper pulls the district's public course and section JSON for three terms, joins titles and units onto meeting times and seat counts, and seeds Postgres through Prisma. GitHub Actions keeps it current.
Conflict-free permutations
Each added course expands into a pool of its sections. A depth-first walk takes one section per course and abandons a branch the moment two meeting blocks overlap.
Ranked weekly calendar
Surviving combinations are scored against the student's constraints (no Fridays, no early mornings, a cap per day) and the best ones render on the calendar.
From a list of courses to a set of weeks.
The generator re-runs whenever the course list or the constraints change. Each step below is one stage of that pass.
Normalize the catalog
Every section becomes a set of meeting blocks: days, start, end, building, room. Times and dates are stored as plain strings in Postgres, so no timezone can ever be applied to a class time by accident.
Expand each course into a pool
A course the student added is matched against every section sharing its subject and number, deduplicated by CRN. The search therefore covers alternate sections the student never explicitly clicked on.
Walk the pools, reject on first conflict
One section is chosen per course, depth-first. Meeting blocks are projected onto a fixed dummy week so an overlap is a single interval test — starts before the other ends, ends after it starts — and a candidate is dropped the moment it collides, pruning that whole branch instead of scoring a dead schedule. A visit cap keeps a wide course list from exploding the cross-product.
Score and rank the survivors
Each complete schedule is scored on Friday classes, how far it starts before the preferred hour, the busiest day's load, and how many days it puts the student on campus. Lowest score wins, and the top options are the ones the student steps through.
Choices in the calendar view.



What I chose, and what it cost.
Enumerate combinations in the browser
CostWork grows with the cross-product of sections, so a visit cap stands in for real pruning. That's ample for one student's term and would need rethinking for anything wider.
A scheduled catalog snapshot, not a live feed
CostSeat and waitlist counts are only as fresh as the last scraper run, so the tool plans a term rather than tracking one minute to minute, in exchange for never depending on a portal session.
It was the nightly data job. The GitHub Actions run scraped and seeded but never synced the schema, so when the production database drifted the seed threw, the courses API swallowed the error, and the site quietly served a stale committed JSON fallback that looked completely normal. Nothing was broken on screen, which is exactly why it went unnoticed. The fix was making every stage fail loudly instead of degrading: assert the scrape returned sections before touching the database, push the schema first, seed row by row with inserted/skipped/failed counts, and commit the refreshed snapshot back so the fallback is never older than the last successful run.
Where I'd take it.
Unit tests around conflict detection, starting with sections that carry two meeting patterns, like a lecture on Monday and Wednesday with a lab on Friday. It's the one place a silent bug produces a wrong schedule that still looks right.
Alerting on the data pipeline. The courses API falls back silently, so a failed nightly run is currently invisible; a post-deploy assertion on the course count would surface it.
Versioned Prisma migrations instead of a schema push, plus an incremental upsert keyed on (CRN, term) so the course table is never briefly empty mid-run.