Machine-driven TA office-hour scheduling for CS 61A / Data C88C.
updated fall 2025 by Christian
-
Clone & authenticate
- Clone this repo.
- Log into the CS61A SPA Google account and share both the availabilities and demand spreadsheets with
oh-scheduler@cs-61a-website.iam.gserviceaccount.com. - In Google Cloud Console → IAM & Admin → Service Accounts, create/download a service account key for the scheduler. Save it as
credentials/credentials.json. - Download the OAuth client (used for Calendar pushes) and save it as
credentials/oauth_credentials.json. The OAuth token generated on first run is stored ascredentials/token.json. - If you want to script Google Forms creation, also enable the Forms API + Drive API in the same project and share the form template with the service account.
-
Python dependencies
- Easiest: run
python setup_env.pyto create.venv/and install everything fromconfigs/requirements.txt. - Manual alternative:
python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r configs/requirements.txt
- Easiest: run
-
Configure per-course settings
- Copy one of the existing config templates under
configs/(e.g.61A_config.json). - Update sheet URLs, semester metadata, and calendar defaults.
- Make sure the ranges (
AVAILABILITIES_RANGE,DEMAND_RANGE) match each sheet. You can type the range (e.g.B1:BQ) into the Google Sheets name box to confirm. - Optional fixed TA assignments live under
configs/fixed_tas/. Point thefixed_tasfield at the CSV for your class (fixed_tas/61A_FIXED_TAs.csv, etc.).
- Copy one of the existing config templates under
-
Validate access (optional but recommended)
>>> from src import validation >>> validation.test()
This confirms the API client can read the configured spreadsheets and that values can be parsed.
-
Log into the CS61A SPA account.
-
Make copies of the Availabilities and Demand spreadsheets you will use for the semester.
-
Clone this repo. The project does not ship with Google credentials; you must supply them.
-
Share both sheets with
oh-scheduler@cs-61a-website.iam.gserviceaccount.com. -
In Google Cloud Console:
- Navigate to IAM & Admin → Service Accounts.
- Locate the scheduler service account and download a JSON key (Add Key → Create new key → JSON).
- Save the file as
credentials/credentials.json. (Fun fact: you cannot download that exact key again; regenerate if needed.) - Download the OAuth client for the scheduler (used for Calendar invites) and place it at
credentials/oauth_credentials.json.
-
Confirm the spreadsheet ranges align with your copies:
AVAILABILITIES_RANGE = 'Form Responses 1!B1:BQ' DEMAND_RANGE = 'Demand!A2:E' # adjust if the demand sheet includes more metadata columns
Use the Google Sheets name box to highlight the ranges and make sure they capture all of the relevant cells.
As of Fall 2025, the form responses range is
B1:BQ. If additional columns are added, update the config accordingly. -
Open
src/state.pyand verify the column indices listed underStaffMemberstill match your form responses. If the form changes, update those constants. -
If validations fail, check
src/utils.py:get_availabilities— this is where string values from the spreadsheet are coerced into integers or default values. Adjust fallback logic there if staff input unconventional responses. -
Once confident in the config, run the validation helper (
validation.test()) to ensure the Sheets API credentials are correct and the data can be parsed end-to-end. -
Gather any existing pickle files from teammates (drop them into
.artifacts/pickles/<class>/) if you want to resume mid-semester instead of recomputing earlier weeks. -
Launch the CLI with
python schedule.py(optionally-c CLASSNAMEdefaults to the class in config.json if no -c is passed) to run the optimizer or calendar push.
schedule.py— CLI entrypoint for running the scheduler and optionally creating Google Calendar events.src/runner.py— main scheduler workflow invoked by the CLI.src/algorithm.py— CVXPY optimization that produces weekly assignments.calendar_time.py— OAuth helpers and Calendar API plumbing.config_read.py— shared config loading/validation helpers.paths.py— single source of truth for filesystem locations.state.py— core state machine (staff roster, historical assignments, serialization).utils.py— spreadsheet readers, serialization helpers, misc utilities.validation.py— config/spreadsheet validation utilities.send_email.py— legacy calendar invite script (kept for reference).
configs/— course configs plus supporting assets (fixed_tas/*.csv).outputs/— generated CSV exports (<class>_sum_assignments_weekX.csv, detailed weekly grid underoutputs/weekly_assignments/)..artifacts/numpy/— intermediate.npydumps of demand and assignment tensors..artifacts/pickles/<class>/— serializedStateobjects for each scheduled week (supports out-of-order runs)..artifacts/logs/— timestamped log files capturing run details and solver diagnostics.credentials/— Google service account key, OAuth client, and cached token.setup_env.py— helper script that bootstraps a virtual environment and installs dependencies.
python schedule.py [-c CLASSNAME]The CLI flow:
- Choose the course (
schedule.pywill auto-list every config underconfigs/). Passing-c CLASSNAMEskips the prompt if the class exists. - Choose whether to run the scheduler or send calendar events.
- When running the scheduler:
- Confirm the sheet links/ranges from the config.
- Enter the week number you want to schedule. You can run weeks out of order; the scheduler will reuse previously saved state if available and bootstrap new ones when missing.
- Decide whether to rerun from the start or only compute the requested week. States are serialized to
.artifacts/pickles/<class>/<week>.pkl. - On success the run exports:
outputs/<class>_sum_assignments_week<week>.csv— summary of hours per TA.outputs/weekly_assignments/<class>_assignments_week<week>.csv— detailed grid (hour × weekday, comma-separated emails).
The optimization results and demand tensor are also cached in .artifacts/numpy/.
Use src/forms_helper.py to copy a Google Form template and replace placeholders (e.g., {{NAME}}, {{DATE}}). Example:
from src.forms_helper import create_form_from_template
new_form_id = create_form_from_template(
template_form_id="1AbcTemplateId",
replacements={"{{NAME}}": "Ada", "{{DATE}}": "Oct 12"},
new_title="OH Intake for Ada"
)
print(f"Edit link: https://docs.google.com/forms/d/{new_form_id}/edit")
# or populate placeholders from a scheduler config (class/semester):
from src.forms_helper import create_form_from_template_with_config
new_form_id = create_form_from_template_with_config(
template_form_id="1AbcTemplateId",
config_path="configs/config.json", # or another config file
extra_replacements={"{{NAME}}": "Ada"} # optional
)
print(f"Edit link: https://docs.google.com/forms/d/{new_form_id}/edit")Ensure the template form is shared with the service account and that the Forms API + Drive API are enabled in the same GCP project as credentials/credentials.json.
-
Prepare configuration
- Update the chosen JSON under
configs/with sheet URLs, semester metadata, and calendar defaults. - Confirm the ranges (
AVAILABILITIES_RANGE,DEMAND_RANGE) match the sheets. - Point
fixed_tasto a CSV underconfigs/fixed_tas/if you have guaranteed staffing commitments.
- Update the chosen JSON under
-
Gather spreadsheets
- Ensure the availabilities form responses sheet includes the expected numeric ratings for each day/hour slot.
- Verify the demand sheet lists each week, day, start/end hour, staff count, and any optional metadata columns (location, description, total staff, comments).
-
Run the scheduler
- Execute
python schedule.py(add-c CLASSNAMEto preselect a course). - Select the course and choose to “Run the OH scheduler”.
- Enter the week number you want to schedule. Enable “rerun from the beginning” if you need to rebuild past state.
- Review the resulting CSV files under
outputs/.
- Execute
-
Send calendar events (optional)
- Re-run
python schedule.py, choose the same course, and select “Send calendar events”. - Provide the week number, optionally include fixed TAs, review the preview, and confirm to create events on the configured Google Calendar.
- Re-run
-
Iterate weekly
- Repeat the scheduling process each week, or rerun multiple weeks at once to catch up. The hidden
.artifacts/pickles/<class>/folder stores cumulative state.
- Repeat the scheduling process each week, or rerun multiple weeks at once to catch up. The hidden
Selecting the “Send calendar events” option prompts for a week number and reads the detailed CSV. Before creating events the CLI:
- Optionally merges “fixed TA” assignments.
- If the config’s
fixed_taspath points to a CSV and you opt in, staff listed there are automatically added to the matching (day, hour) slots.
- If the config’s
- Loads metadata from the demand sheet.
src.runner.load_weekly_demand_metadatare-parses the demand sheet for the chosen week.- Additional columns in the demand sheet’s range (after staff counts) can provide per-slot overrides:
- Column 6 (
row[5]): Location override. - Column 7 (
row[6]): Description text. - Column 8 (
row[7]): Suggested “total staff” note (rendered in the event description). - Column 9 (
row[8]): Free-form comments.
- Column 6 (
- When present, these values replace the config defaults for that specific slot.
- Displays a preview of the events (time, location, description, attendees) before asking for final confirmation.
Events are created on the calendar ID configured in the JSON file and inherit the default reminders (24 hr email, 10 min popup).
- Bad spreadsheet values —
src/utils.pyaggressively coerces string inputs to integers. If you see parsing errors, check the relevanttry/exceptblocks to tweak fallbacks. - Weeks out of order —
src/runner.pywill deserialize any prior week stored under.artifacts/pickles/<class>/. If a pickle is missing you’ll see a warning and the run will proceed using fresh demand/availability data. - Regenerating historical data — Delete the relevant pickle files and rerun the scheduler starting from the earliest missing week.
- Validation — Re-run
validation.test()whenever you change sheet links or form questions to ensure the parser still matches the current schema. - Logs — Each run emits a timestamped log under
.artifacts/logs/containing solver diagnostics and progress notes; check these if something goes wrong silently. - Zero-hour staff — TAs with a weekly target of 0 are now hard-blocked from being assigned any hours. The algorithm enforces this and
src/runner.pyraises an error if a violation slips through.
-
State (
src/state.py)- Tracks the current week number, weeks remaining, course staff roster, previous assignments, and demand tensors.
- Stores
StaffMemberobjects (email, availability matrix, hour commitments, preferences) keyed by email with a bidirectional index mapping for stable ordering. - Serializes each week’s state to
.artifacts/pickles/<class>/<week>.pklafter stripping theprev_statepointer to avoid recursive serialization. On load,utils.deserializere-links the chain and transparently converts legacy pickles (pre-srcpackage layout).
-
Assignments
- The optimizer returns a boolean tensor
(staff, remaining_weeks, 5 days, 12 hours); the scheduler keeps only the first week slice. - Derived CSV exports flatten this data for consumption by course staff and the calendar exporter.
- The optimizer returns a boolean tensor
src/runner.pyreads the selected config viasrc/config_read.read_config.src/utils.get_availabilitiesandget_demandfetch spreadsheet data and convert it into numeric numpy arrays.- A new
Stateinstance incorporates the previous week (if any), updates staff information, and assembles algorithm inputs:- Demand window for current/future weeks.
- Historical assignments for “day one” staff to enforce consistency.
- Staff availability, remaining hours, contiguous hours preferences, etc.
src/algorithm.run_algorithmsolves the CVXPY optimization problem. Intermediate tensors are cached to.artifacts/numpy/assignments.npyanddemand.npy.- The resulting assignments update staff hour balances; the state is serialized; CSV exports are produced.
- When requested, calendar export pipelines through
load_weekly_demand_metadataandparse_assignments_from_csvto build rich event payloads (including fixed TA supplements and demand-sheet overrides).
paths.ensure_directories()creates all runtime folders (.artifacts/*,credentials/) on startup.- Pickles and numpy arrays are hidden from version control via
.gitignore. - Config assets (including fixed TA rosters) live alongside the JSON configs for easier sharing between course instances.
- Original CLI + documentation by Shm.
- Subsequent refactors added hidden artifact caches, out-of-order week support, demand metadata integration, and the consolidated documentation you’re reading now by Chris.

