# csss590-attendance A small Python tool for instructors of [CSSS 590](https://myplan.uw.edu/course/#/courses/CSSS590) (or any similar attend-N-of-M seminar that uses Canvas Roll Call) to keep track of attendance week to week, to warn students at risk of falling below the required count, and to print a final pass/fail summary at the end of the quarter. CSSS 590 is the course that provides credit to folks attending the weekly [CSSS seminar series](https://csss.uw.edu/seminars) at the [University of Washington](https://www.uw.edu/). Students must attend a set number of those seminars to get credit. The script reads the weekly attendance export that the [Canvas Roll Call](FIXME) tool emails you, cross-references it against the live Canvas enrollment, and sends warning DMs through the Canvas Conversations API. A small Python script writes a short note to each student—scoped to the course—in the same language you would have written yourself. This started as a one-quarter, one-instructor tool. It is published in case it is useful to others teaching CSSS 590 or a similar course. ## What it does The single entry point is `review_attendance.py`. It has three modes: - **Review (default)**—print a students × sessions attendance grid and a sorted X/N totals list. No Canvas writes; no email; just a picture of where the class stands. Useful for the weekly look. - **`--send-warning`**—same review output, plus a preview of which students are at or past the warning threshold (derived as `sessions_expected − sessions_required`) and the rendered text of the DM each one would receive. Still no Canvas writes. This is the inspect-before-you-commit step. - **`--send-warning -f`**—actually POSTs each warning to the Canvas Conversations API as a 1:1 DM scoped to the course, and appends each recipient to `students_contacted.tsv` so subsequent runs skip them. The `-f` is intentional friction: nothing here can be un-sent. A separate `--final-report` mode (mutually exclusive with `--send-warning`) prints a PASS/FAIL breakdown at the end of the quarter, annotated with the warning dates from the contact log. ## Requirements - Python 3.11 or newer (the script uses `tomllib` from the standard library, which appeared in 3.11). - The `requests` library (`pip install requests` or `apt install python3-requests`). Everything else is in the stdlib. - A Canvas instance with the Roll Call (Attendance) LTI tool and personal API access enabled for you as the instructor. ## Setup 1. Clone this repository: ``` git clone https://gitea.communitydata.science/mako/csss590-attendance.git cd csss590-attendance ``` 2. Install the one Python dependency: ``` pip install requests ``` 3. Get a Canvas API access token. In Canvas: **Account → Settings → Approved Integrations → "+ New Access Token"**. Give it a label so you can find it later. Copy the token immediately—Canvas only shows it once. 4. Copy the example config and fill it in: ``` cp config.toml.example config.toml ``` Open `config.toml` in your editor and set `canvas_id` (the number in your course URL), `base_url` if you are not at `canvas.uw.edu`, and the `token` field. The token can also come from `token_command` (a shell command that prints the token—handy if you keep it in a password manager) or from the `CANVAS_TOKEN` environment variable; see comments in `config.toml.example` for details. 5. Drop the Roll Call CSV in this directory whenever a new one arrives. The script picks the latest matching `attendance_reports_*.csv` automatically. You can also pass `--csv FILE` to point at a specific file. ## Weekly workflow The script is one piece of a wider weekly rhythm. The full process: 1. **Collect attendance on paper during class.** A printed sign-in sheet beats fumbling with a laptop and is what students see you doing. 2. **Transcribe to Roll Call** in Canvas after class. Open the Roll Call (Attendance) tool and mark each student present or absent for that day's session. 3. **Request the attendance report** in Canvas. Roll Call's "Settings" gear has an "Attendance Report" link; leave all the options at their defaults. Canvas will email the CSV to you, usually within a few minutes. 4. **Save the emailed CSV** into this directory. The script picks the lexicographically last `attendance_reports_*.csv` it finds, so renaming the file to start with a date (e.g. `attendance_reports_20260520-.csv`) ensures the newest week's file is the one the script reads. If you have a reason to use a different file for a given run, pass `--csv FILE`. 5. **Review the week**: ``` python3 review_attendance.py ``` Look at the grid and the X/N totals. Confirm the new column matches your records and that nobody's count is surprising. If a student is at the warning threshold, preview what the warning DM would say: ``` python3 review_attendance.py --send-warning ``` This still does not write to Canvas—it renders the message each at-risk student would receive so you can read them before they leave your machine. 6. **Send the warnings** when the preview looks right: ``` python3 review_attendance.py --send-warning -f ``` Each warning becomes a 1:1 Canvas conversation tagged with the course, and the student is appended to `students_contacted.tsv` so subsequent runs skip them. ## End of quarter ``` python3 review_attendance.py --final-report ``` Prints PASS/FAIL groupings against `sessions_required`. Students who were warned earlier in the quarter still carry their warning date in the output so you can see the full story at a glance. For students who finished below the line, `failed_message.txt` is a hand-substituted template you can use to write each of them a note explaining their options. It is not wired into the script; see "Templates" below. ## Templates `email_template.txt` is rendered as a Python f-string for each warned student. The template author has the following names in scope: | name | meaning | | --- | --- | | `name` | the student's full name from Canvas | | `first_name` | first whitespace-separated token of the name | | `attended` | how many sessions the student has attended | | `sessions_held` | how many sessions have been held so far | | `sessions_remaining` | sessions still to come this quarter | | `sessions_required` | from config | | `sessions_expected` | from config | Because the template is evaluated as an f-string, any expression that references these names is valid. The shipped template uses one conditional to render either "all" or a number depending on how many of the remaining sessions a student still needs to attend. `failed_message.txt` is a separate template intended for hand-use at the end of the quarter when a student has missed too many sessions to pass. It uses `{{double-brace}}` markers (not f-string syntax) and is not wired into the script—substitute by hand and send through the Canvas inbox or by re-using the small POST snippet at the bottom of `review_attendance.py`. ## Roll Call quirks A few things worth knowing if you start poking at the data yourself: - The Roll Call CSV has a trailing empty field on every data row but not on the header—15 columns of data versus a 14-column header. Naive `csv.DictReader` (or R's `read.csv`) gets confused. The script supplies explicit field names with an extra "Extra" column to work around this. - Sessions where a student wasn't marked at all have no row in the CSV—they are not silently recorded as "absent". The script derives absences as `sessions_held − attended`, which counts an unmarked session the same as an explicit absence. This is the right behavior for grading purposes but can surprise you if you trust the row count. - Per-session attendance is not reachable through the Canvas API using a personal access token. Roll Call's own backend (at `rollcall.instructure.com`) requires an LTI launch JWT, not the Canvas token. The Canvas Submissions API can give you the cumulative attendance percentage for the Roll Call assignment, but not the per-day breakdown. The emailed CSV remains the only easy source of per-session detail. ## Source and contributing The repository lives at [gitea.communitydata.science/mako/csss590-attendance](https://gitea.communitydata.science/mako/csss590-attendance). Issues and pull requests are welcome there—a tweak to a template, a better config field, a wrinkle in a different Canvas instance. If you are adapting this for a different course, a PR that generalizes a class-specific assumption (instead of forking quietly) helps everyone who comes after you. **Please don't publish any student records in git!** To help prevent this, `.gitignore` keeps `config.toml`, the Roll Call CSVs, the contact log, the final summary CSV, and incidental archival material (screenshots, PDFs, SVGs) out of git. The intent is that nothing you commit could identify a student. Before pushing, run `git status` to confirm only the script, templates, and example config are tracked. ## Credit Written by [Benjamin Mako Hill](https://mako.cc/academic/) for CSSS 590 at the University of Washington in spring 2026, with substantial help from Claude. The Roll Call API exploration was constrained by what a personal Canvas access token can reach. If Instructure ever opens the Roll Call backend up to instructor tokens, the workflow could be tightened considerably.