Instructor participation report: per-student histograms, outcome mix by class day, and a sortable table including the fairness ratio (answered calls over questions present for, with opt-out days out of the denominator), plus CSV exports of students, calls, and opt-outs. Assessment scales are now per-course data: ordered levels with labels and points out of 100 (defaults carry the old R grading values), with calls referencing levels by id so renames follow through to history. Renaming, re-pointing, reordering, and adding levels are always allowed; deleting a level in use by recorded calls is blocked. Pronouns and course term dates come from Canvas custom variable substitutions, at launch and roster-wide via rlid-scoped NRPS; the student page notes that names/pronouns are Canvas-sourced. Rosters can also be refreshed outside launches: a "Sync roster now" button and a sync-rosters CLI command for an hourly cron job, skipping ended courses. Alembic now runs SQLite-compatible batch migrations with a constraint naming convention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
134 lines
5.3 KiB
Markdown
134 lines
5.3 KiB
Markdown
# coldcall-lti
|
|
|
|
A Canvas external tool (LTI 1.3) for managing cold calls in case-based
|
|
classes: it selects students to call using weighted randomness, records
|
|
what happened with each call, lets students report planned absences, and
|
|
reports participation data back to both instructor and students. It
|
|
replaces a manual workflow built on exported rosters, Google Forms, and
|
|
local scripts.
|
|
|
|
## How it works
|
|
|
|
The tool is a Flask application that Canvas launches over LTI 1.3. A
|
|
single URL serves everyone: Canvas identifies the person and course on
|
|
each launch, so instructors get the call-list and reporting views while
|
|
students get the absence form and their own history. The roster comes
|
|
from Canvas through the Names and Role Provisioning Service, which means
|
|
adds and drops are picked up automatically rather than reconciled by
|
|
hand.
|
|
|
|
Selection uses the same weighting as the manual system it replaces: each
|
|
answered call divides a student's weight by the course's weight factor
|
|
(default 2), so students who have answered more questions become
|
|
progressively less likely to be called. Each course can instead use
|
|
"cycle" mode, which shuffles the roster and calls everyone exactly once.
|
|
These, along with whether students can see their own assessments, are
|
|
per-course settings.
|
|
|
|
## Layout
|
|
|
|
- `coldcall_lti/models.py` — SQLAlchemy models: courses (with their
|
|
settings), students, enrollments, opt-outs, and calls, plus the query
|
|
helpers that feed selection.
|
|
- `coldcall_lti/selection.py` — the weighted and cycle selection logic.
|
|
Kept free of database and web dependencies so it can be tested and
|
|
reasoned about on its own.
|
|
- `coldcall_lti/__init__.py` — the Flask application factory.
|
|
- `migrations/` — alembic migrations. The schema avoids
|
|
database-specific types so the same migrations run on SQLite (the
|
|
default) and MariaDB/MySQL; switching is a matter of changing
|
|
`COLDCALL_DATABASE_URL`.
|
|
- `tests/` — pytest suite covering selection behavior and the model
|
|
helpers.
|
|
|
|
## Setup
|
|
|
|
Development uses a virtualenv that shares the system's Debian-packaged
|
|
libraries (Flask, SQLAlchemy, alembic, pytest) and adds the one
|
|
PyPI-only dependency, the maintained `pylti1p3next` fork of PyLTI1p3:
|
|
|
|
```
|
|
python3 -m venv --system-site-packages .venv
|
|
.venv/bin/pip install pylti1p3next
|
|
.venv/bin/pip install -e .
|
|
```
|
|
|
|
Create the database and run the tests:
|
|
|
|
```
|
|
.venv/bin/python -m alembic upgrade head
|
|
.venv/bin/python -m pytest tests/
|
|
```
|
|
|
|
Configuration is by environment variable: `COLDCALL_DATABASE_URL` (any
|
|
SQLAlchemy URL; defaults to an SQLite file under `instance/`),
|
|
`COLDCALL_SECRET_KEY` for Flask sessions, `COLDCALL_LTI_CONFIG` (path
|
|
to the LTI platform configuration), and `COLDCALL_DEV_MODE=1` to enable
|
|
the fake-launch pages.
|
|
|
|
## Connecting to Canvas
|
|
|
|
The tool speaks LTI 1.3, which requires a Developer Key created by a
|
|
Canvas account admin. The key points Canvas at three endpoints here:
|
|
`/lti/login` (OIDC initiation), `/lti/launch` (the launch target), and
|
|
`/lti/jwks` (this tool's public keys). The platform side is described
|
|
in a JSON file — copy `lti_config.example.json` to
|
|
`instance/lti_config.json` and fill in the client id and deployment id
|
|
from the Developer Key. Generate the tool's keypair alongside it:
|
|
|
|
```
|
|
openssl genrsa -out instance/private.key 4096
|
|
openssl rsa -in instance/private.key -pubout -out instance/public.key
|
|
```
|
|
|
|
On each instructor launch the tool refreshes the course roster from
|
|
Canvas through the Names and Role Provisioning Service, so enrollment
|
|
changes appear without any manual step. There is also a "Sync roster
|
|
now" button on the instructor page, and a management command suitable
|
|
for an hourly cron job on the server, which keeps rosters current even
|
|
when nobody has launched the tool (worth having during the add/drop
|
|
churn at the start of a term):
|
|
|
|
```
|
|
17 * * * * cd /path/to/coldcall_lti && .venv/bin/flask --app coldcall_lti sync-rosters
|
|
```
|
|
|
|
Courses whose Canvas end date has passed are skipped automatically.
|
|
|
|
Student names come from Canvas display names, which already reflect
|
|
preferred names. Pronouns require one extra piece of Developer Key
|
|
configuration: add a custom parameter
|
|
|
|
```
|
|
pronouns=$com.instructure.Person.pronouns
|
|
course_start=$Canvas.course.startAt
|
|
course_end=$Canvas.course.endAt
|
|
```
|
|
|
|
The course dates bound the schedule and date pickers; the tool works
|
|
fine without them when a course has no dates set in Canvas.
|
|
|
|
to the key. Canvas then includes each person's pronouns in launches
|
|
and in the roster data (the tool requests memberships scoped to the
|
|
resource link, which is what makes Canvas attach per-member custom
|
|
fields). Pronouns appear on the live call card, printed call lists,
|
|
and each student's own page. If the account has pronouns disabled,
|
|
everything simply shows without them.
|
|
|
|
## Developing without Canvas
|
|
|
|
Because a Developer Key takes institutional approval to get, the app
|
|
has a fake-launch mode for local development:
|
|
|
|
```
|
|
COLDCALL_DEV_MODE=1 .venv/bin/flask --app coldcall_lti run --debug
|
|
```
|
|
|
|
Then open http://localhost:5000/dev and launch as the fake instructor
|
|
or any of the fake students. This sets up exactly the session state a
|
|
real launch would, and the fake roster flows through the same sync code
|
|
as real NRPS data, so everything past the launch behaves identically.
|
|
Dev mode also relaxes the cookie settings that Canvas's iframe
|
|
embedding requires in production (SameSite=None; Secure), which would
|
|
otherwise break plain-http localhost use.
|