A dating app-style course selection web app for Harvard students. Discover courses by swiping, save your favorites, and rank them through an interactive comparison game.
Watch a demonstration of Class Cupid in action: YouTube Video
- Discover Page: Swipe through courses personalized to your preferences (heart, star, or discard)
- Matches Page: Compare saved courses in a sorting game to create your personalized ranking
- Settings Page: Configure your year, affiliation, concentration interests, requirements, and term preferences
- Smart Filtering: Course recommendations based on year level, concentration, Gen Ed categories, divisional distributions, language requirements, and more
- Multi-Semester Support: View and compare courses from different semesters separately
- Binary Search Ranking: Advanced algorithm ranks your saved courses based on pairwise comparisons
- Undo Functionality: Undo your last action on both Discover and Matches pages
- Python 3.8 or higher
- pip (Python package manager)
Install all required Python packages:
pip install -r requirements.txtThis will install:
- Flask 2.3.3 (web framework)
- Flask-SQLAlchemy 3.1.1 (database ORM)
- Werkzeug 2.3.8 (security utilities)
- SQLAlchemy 2.0.23 (database toolkit)
- Click 8.1.7 (CLI framework)
Preferences and swipes are stored in your browser session (no accounts). The course catalog lives in catalog.db at the project root.
python scripts/build_catalog.pyThis imports data/json/2026_Fall_courses.json and data/json/2027_Spring_courses.json. To use other exports, run flask --app app import-courses path/to/file.json after the database exists.
Note: Delete catalog.db before rebuilding if you want a clean import.
Set a secret key for sessions (required in production), then start the server:
export SECRET_KEY="your-random-secret"
flask --app app runOr:
export SECRET_KEY="your-random-secret"
python app.pyThe application will start on http://localhost:5000 (default Flask port). If port 5000 is already in use, Flask will automatically use the next available port and display it in the terminal.
Open your web browser and navigate to:
http://localhost:5000
- Configure your preferences (required before using Discover):
- Open Preferences from the navigation bar (or you will be redirected there from the home page)
- Select your affiliation: "Harvard College" or "Other Affiliation"
- Select term(s): Choose "2026 Fall", "2027 Spring", or both (you can select multiple)
- If "Harvard College":
- Choose your year: Freshman, Sophomore, Junior, or Senior
- Select concentration interests (you can select multiple, or use "Select all")
- Select requirements to fulfill:
- Gen Ed categories: Science & Technology in Society, Aesthetics & Culture, Ethics & Civics, Histories, Societies, Individuals
- Divisional distributions: Arts and Humanities, Social Sciences, Science and Engineering and Applied Science
- Info icon: Click the (i) icon to see that selecting a division will show ALL concentrations within that division
- Language Requirement: Only shows introductory language courses (levels 1, 2, 3, A, B, AA, BA)
- First Year Seminar: Only available for freshmen - shows FYSEMR courses
- Quantitative Reasoning
- If "Other Affiliation":
- Select your graduate school(s) - you can select multiple:
- Graduate School of Arts and Sciences
- Harvard Business School
- Harvard School of Dental Medicine
- Harvard T.H. Chan School of Public Health
- Graduate School of Design
- Harvard Divinity School
- Harvard Graduate School of Education
- Harvard Kennedy School
- Harvard Law School
- Harvard Medical School
- Select your graduate school(s) - you can select multiple:
- Click Save Preferences to save your settings
-
Navigate to the Discover page from the navigation bar (fire icon).
-
First Visit: You'll see a welcome popup explaining the action buttons. Click "Got it!" to dismiss it.
-
Course Card: You'll see a course card with:
- Course number and title (hyperlinked to official Harvard course page)
- Instructor name
- Department
- Meeting times and days (displayed as day abbreviations like "MWF")
- Course description
- Course metadata (credits, enrollment, etc.)
-
Take Action (buttons arranged horizontally):
- ← Undo (light blue): Undo your last action and return to that specific course
- ✕ Discard: Remove the course from your feed
- ♥ Heart: Save the course for later (appears in Matches)
- ★ Star (yellow when active): Mark as a favorite (also appears in Matches, sorted above hearted courses)
-
Course Recommendations: The algorithm shows courses based on your profile preferences using weighted selection:
- Courses appropriate for your year level are weighted higher (more likely to appear)
- Once you swipe a course (any action), it won't appear again until you reset your choices
- If you run out of relevant courses, you'll see a prompt to either:
- Go to Matches to rank your saved courses
- Update your Settings to add more subjects/requirements
-
Navigate to the Matches page (compass icon) after you've saved some courses (hearted or starred).
-
Comparison Game:
- You'll see two course cards side-by-side with course details
- Click anywhere on a card to select it as your preference (winner)
- Click "Skip →" to skip the comparison and see a new pair
- Click "← Undo" to undo your last comparison
- Courses must be from the same semester to be compared
-
Progress Tracking:
- You'll see a message indicating how many comparisons you've made
- The minimum number of comparisons needed is
max(3, min(10, total_courses - 1)):- Minimum: 3 comparisons
- Maximum: 10 comparisons
- Scales with course count in between
-
Ranked List:
- After making sufficient comparisons, your courses are automatically ranked
- Rankings are displayed with numbers (1, 2, 3...) in a leftmost column
- The ranking uses a binary search algorithm that learns from your pairwise comparisons
- Display Order:
- Before ranking: Starred courses first, then hearted courses, sorted by course number
- After ranking: Courses displayed by rank (position 1 = favorite), but starred courses still appear above hearted courses
-
Managing Saved Courses:
- In the saved classes list, you can:
- Click the star icon (★) to toggle favorite status (yellow = favorited)
- Click the heart icon (♥) to toggle saved status
- Click the X icon (✕) to remove the course from your saved list
- Course names are hyperlinked to the official Harvard course catalog page
- In the saved classes list, you can:
-
Multi-Semester Ranking:
- If you've selected multiple terms, courses from different semesters are ranked separately
- You can only compare courses from the same semester (prevents meaningless cross-semester comparisons)
- Each semester has its own progress tracking and ranking
- Click on your username in the navigation bar to access Settings
- Update any preferences - changes take effect immediately on the Discover page
- Use the "Reset All Choices" button at the bottom to clear all your swipes and comparisons and start fresh
The recommendation algorithm uses a multi-stage weighted selection process:
-
Term Filtering: Only shows courses from your selected terms
-
Year-Level Appropriate Weighting:
- Freshmen: Heavy preference for introductory courses (1-99, 1000-1099), moderate for mid-level (100-199, 1100-1999). Cannot see graduate courses, tutorials, or reading/research classes. First Year Seminars weighted at 1 (low frequency).
- Sophomores: Balanced preference for intro and mid-level courses. Can access sophomore tutorials, special seminars (96, 960), and reading/research (91, 910).
- Juniors: Low preference for intro courses, high preference for mid-level and graduate-level courses (200-299, 2000-2999). Can access junior tutorials, special seminars, and reading/research.
- Seniors: Very low preference for intro courses, high preference for mid-level and graduate courses. Can access senior tutorials, special seminars, and reading/research.
-
Concentration Filtering: If selected, shows courses from those departments. Can select multiple concentrations or use "Select all".
-
Gen Ed Categories: Uses external JSON files (
data/json/*_Geneds.json) to find specific course codes (e.g., "GENED 1145") matching selected categories. Important: Gen Eds are open to all grade levels - grade-level restrictions are bypassed for Gen Ed courses. -
Divisional Distribution: Filters by Arts & Humanities, Social Sciences, or Science & Engineering. Excludes Tutorial courses. Click the (i) info icon to see that selecting a division will show ALL concentrations within that division.
-
Language Requirement: Only shows introductory language courses identified by:
- Department prefixes (FRENCH, SPANISH, CHINESE, GERMAN, etc.)
- Course number patterns: 1, 2, 3, A, B, AA, BA
- Note: Some departments have different language requirement metrics (e.g., Chinese requires scoring above 130, which may require numbered classes). Advanced language courses can be found by selecting specific language concentrations.
-
First Year Seminar: Special filter for courses with
catalogSubject = "FYSEMR". Only freshmen can see these. Uses union logic - if you select FYSEMR + a concentration, you'll see all FYSEMR courses OR all courses in that concentration (not just FYSEMR courses in that concentration). -
Alpha Courses: Language courses, composition courses, and Gen Eds that are open to all years. Weighted consistently across all years.
The algorithm uses completely different school-specific filtering (no year-based weighting):
-
Graduate School of Arts and Sciences: Shows courses where:
catalogSchoolDescription = "Faculty of Arts & Sciences"AND- Course number is in ranges: 200-299, 300-399, 2000-2999, or 3000-3999
-
Harvard Business School: Shows courses where
catalogSchoolDescriptionincludes "Business School" (matches both "Business School Doctoral" and "Business School MBA") -
Harvard School of Dental Medicine: Shows courses where
catalogSchoolDescription = "School of Dental Medicine" -
Harvard T.H. Chan School of Public Health: Shows courses where
catalogSchoolDescription = "Harvard Chan School" -
Other Graduate Schools (Design, Divinity, Education, Kennedy, Law, Medical): Exact string match with
catalogSchoolDescription
Once you swipe a course (heart, star, or discard), it is permanently removed from your recommendation pool. This ensures you never see the same course twice. The course will only reappear if you:
- Use the "Reset All Choices" button in Settings, or
- The database is reset
The application uses SQLite with SQLAlchemy ORM for the read-only course catalog only:
catalog.db # project root; built by scripts/build_catalog.py
- courses: Course catalog (multiple rows per catalog course ID when offered in different terms)
Swipes, comparisons, and profile fields are stored in the signed Flask session cookie (not in SQLite). Clearing site data or using another browser resets them.
Back up or restore the catalog file as needed:
cp catalog.db backups/catalog_backup_$(date +%Y%m%d_%H%M%S).db
cp backups/catalog_backup_YYYYMMDD_HHMMSS.db catalog.db- Set the environment variable
SECRET_KEYto a long random string in the Vercel project settings. - Connect the repo and deploy. The
buildCommandinvercel.jsoninstalls dependencies and runsscripts/build_catalog.py, which createscatalog.dbfrom the JSON underdata/json/. - Vercel detects the Flask app in root
app.pyautomatically (see Flask on Vercel).
If the build times out, build catalog.db locally, commit it, and adjust .gitignore to allow that file (or attach it via another storage strategy).
-
Check Preferences: Make sure you've selected at least one term. Open Preferences in the nav and verify your selections.
-
Check course data: Verify that the catalog was built:
sqlite3 catalog.db "SELECT COUNT(*) FROM courses;" -
Check your preferences: Very specific filtering (e.g., single concentration + multiple requirements) may result in few or no matches. Try expanding your selections.
-
All courses already swiped: If you've swiped through all available courses, you'll see a message prompting you to either check Matches or update your Settings.
Session data lives in cookies. If SECRET_KEY changes between deploys, or you clear cookies, your preferences and swipe history reset. Set a stable SECRET_KEY in production.
- File not found: Make sure the JSON file is in the
data/json/directory and use the full path:flask import-courses data/json/FILENAME.json - JSON parsing errors: Verify the JSON file is valid JSON format
- Database locked: Stop the Flask application, then try importing again
If port 5000 is busy, Flask will automatically use another port. Check the terminal output for the actual port number, or specify a different port:
flask --app app run --port 5001If you pull a schema change (new columns on courses), delete catalog.db and run python scripts/build_catalog.py again (or import with flask --app app import-courses ...).
class_cupidv1/
├── app.py # Main Flask application (routes, algorithms, CLI commands)
├── models.py # Database models (Course catalog only)
├── helpers.py # profile_complete decorator, apology helper
├── vercel.json # Vercel build command (catalog build)
├── scripts/build_catalog.py # Build catalog.db from data/json course exports
├── catalog.db # SQLite catalog (created by build script; gitignored)
├── requirements.txt # Python dependencies
├── README.md # This file (user manual)
├── DESIGN.md # Technical design document
├── templates/ # HTML templates
│ ├── base.html # Base template with navigation, footer, flash messages
│ ├── profile.html # Preferences (no accounts)
│ ├── discover.html # Course discovery (swipe interface)
│ └── matches.html # Comparison game and saved courses list
├── static/
│ ├── css/
│ │ └── style.css # Global styles (Apple system font, responsive layout)
│ ├── js/
│ │ └── main.js # Client-side JavaScript (tooltips, popup dismissal)
│ └── images/
│ ├── icons/ # Navigation icons (fire, compass, user-circle)
│ ├── Class Cupid Logo V3-3.png # Header logo and favicon
│ ├── Class Cupid Text-4.png # Header text logo
│ └── Class Cupid Logo.png # Footer logo
├── data/ # Organized data files
│ ├── images/ # Logo files and icons (duplicates from root)
│ └── json/ # Course catalogs and reference data
│ ├── 2026_Fall_courses.json
│ ├── 2027_Spring_courses.json
│ ├── 2025_Fall_Geneds.json
│ ├── 2026_Fall_Geneds.json
│ ├── 2026_Spring_Geneds.json
│ ├── 2027_Spring_Geneds.json
│ ├── harvard_college_concentrations.json
│ └── harvard_schools.json
└── backups/ # Optional local backups of catalog.db
-
Course Data: Course information is imported from Harvard's course catalog JSON files. These files should be updated each semester and placed in
data/json/. -
Gen Ed Categories: Gen Ed category mappings are stored in separate JSON files (
data/json/*_Geneds.json). These files map categories to specific course codes (e.g., "GENED 1145"). -
Font and Styling: The application uses the Apple system font family for consistent typography across devices. The background features a subtle pattern for visual interest.
-
Session Storage: User sessions are stored in the
flask_session/directory as files. You can clear all sessions by deleting files in this directory. -
Logo and Branding: The Class Cupid logo appears in the header (with text) and as a favicon. A footer logo appears at the bottom of pages with copyright information.
-
Error Handling: Flash messages for user-visible errors; API-style errors return plain text from
apology().
For detailed technical implementation information, algorithms, and architecture decisions, see DESIGN.md.
Code © 2025 Carissa Chen and Haotian Pan
Course metadata © 2025-2026 President and Fellows of Harvard College