Cohorts
A cohort is a named group of students with dated membership. Cohorts drive reporting, federal enrollment counts, and — for lockstep programs — a dedicated registration screen.
The three cohort types
Cohort types are an extended lookup: records.lookup_cohort_type carries behavioral columns, not just a display value, so it has its own admin screen at Administration → Cohort Types (ADMIN.COHORT_TYPES, migration 193) rather than being edited through the generic lookup editor.
| Code | Label | Multiple per student? | Requires |
|---|---|---|---|
DEMOGRAPHIC | Demographic | Yes | — |
LOCKSTEP | Lockstep | No | Major, campus, start term |
GOVT | Federal Cohort | Yes, but one per academic level | — |
The behavioral columns are uses_dates, allow_multiple, requires_major, unique_per_level, requires_campus, requires_start_term, supports_auto_sync and allows_bulk_assign (migration 277; on for LOCKSTEP only).
The generic lookup editor refuses to write these
POST, PATCH and DELETE on /core/lookup-values return 409 for COHORT_TYPE, naming the owning endpoint (/roles/cohort-types). That is not tidiness. The generic INSERT leaves allow_multiple at its TRUE default, so a type created generically would silently permit duplicates; the generic UPDATE rewrites code, which migrations and the ODS importer key on; and the generic DELETE is a bare DELETE against a table with NO ACTION inbound foreign keys.
Create and delete for cohort types exist only on the owning endpoint.
Ownership: who wrote this row
Both cohorts and student_cohorts carry a source column, and it decides who may change the row.
source | Meaning |
|---|---|
ODS | Imported from Colleague. Maintained by the nightly student_cohorts importer step |
AUTO | Maintained by lockstep auto-sync |
| (null) | Created by hand in this system |
Current data: 200 of 202 cohorts are ODS; of 14,274 memberships, 14,251 are ODS and 22 are AUTO.
The importer reverts hand-set end dates on ODS rows
If you end-date an ODS membership by hand, the next nightly import puts it back — Colleague is the owner of those rows and the importer reconciles to it. To remove a student from a Colleague-sourced cohort, change it in Colleague.
Auto-sync has the mirror rule: it maintains AUTO rows only and never touches ODS rows. The locally_modified flag marks a row a human has deliberately altered.
Federal cohorts
The federal/IPEDS cohort arrives through the importer as GOVT cohorts with codes prefixed FED-. Its label is "Federal Cohort" (migration 256) — it was previously shown as something less clear.
GOVT is the one type with unique_per_level = true: a student holds one federal cohort per academic level, so an undergraduate who later enrolls as a graduate student correctly holds two.
That is why the membership key includes the level:
CREATE UNIQUE INDEX uq_student_cohorts
ON records.student_cohorts (student_id, cohort_id, start_date, academic_level_id)
NULLS NOT DISTINCT;NULLS NOT DISTINCT matters — without it, two rows with a null level would both be allowed. Migration 256 added academic_level_id to this key.
Membership dates, and reopening on the same day
Membership is dated (start_date, end_date), so a student's cohort history is preserved rather than overwritten. A student can leave a cohort and rejoin it later, and both spells remain on the record.
A same-day reopen needs the date in the key
Because start_date is part of the uniqueness key, ending a membership and starting a new one on the same day would collide. Migration 256 addressed a related failure: an ON CONFLICT DO NOTHING was silently swallowing these collisions and reporting a clean run, so writes that never happened looked successful.
If a cohort write reports success but nothing changed, that is the shape of bug to suspect.
Cohort fields
| Field | Notes |
|---|---|
| Code, Name | cohort_code is what the importer and reports key on |
| Type | One of the three above |
| Start term, Expected graduation term | |
| Academic year | |
| Campus | Required for LOCKSTEP |
| Major | Required for LOCKSTEP; see the note below |
| Max students | |
| Start / end date, Active | |
| Auto-sync membership | Only for types with supports_auto_sync |
A lockstep cohort's majors are a set
cohorts.major_id is the single legacy column, but a lockstep cohort's majors live in records.cohort_majors (migration 246) — a set, because a lockstep group can span more than one major. Read the junction, not the column, when you need the real answer.
Advisors
records.cohort_advisors (migration 246) assigns advisors to a whole cohort, with dates. Advisors are end-dated, not deleted, so who advised a cohort in a given term stays answerable.
Bulk assigning programs and an advisor
For a cohort whose type has Allows bulk program/advisor assignment switched on (allows_bulk_assign, set in Administration → Cohort Types), the Cohorts screen offers a Bulk assign action. It applies, to every open member of the cohort in one transaction:
- Majors and minors to add. Anything a student already holds is left alone. The start date defaults to today (the API also accepts a
bulletin_id, andstudent_idsto narrow the run to some of the members). - Optionally, end each student's current majors / minors. They are end-dated, not deleted. When current majors are ended, the first major chosen becomes the primary major; otherwise the student's existing primary stays primary.
- One advisor, as primary or not. A person who is not yet an advisor can be chosen. With End each student's current primary advisor off, a new primary assignment leaves the old primary in place as a non-primary advisor.
The result reports how many students were updated and what was added or ended, and lists the students for whom nothing changed. Students and their advisors receive the usual program-change emails, sent only after the transaction commits.
The action is POST /roles/cohorts/{cohortID}/bulk_assign. The route needs ADMIN.COHORTS; assigning programs also needs EDIT on STUDENTS.SEARCH, and assigning an advisor EDIT on ADVISING.ADVISORS. A cohort whose type does not allow it is refused with 409.
Screens
| Screen | Route | Permission node |
|---|---|---|
| Cohorts | /admin/cohorts | ADMIN.COHORTS |
| Cohort Types | /admin/cohort-types | ADMIN.COHORT_TYPES (migration 193) |
| Cohort Registration | /registration/cohort | REGISTRATION.COHORT_REGISTRATION (migration 247) |
| Cohort planning | — | REGISTRATION.COHORT_REGISTRATION.PLAN (migration 254) |
| Cohorts tab on a student | — | STUDENTS.TAB.COHORTS |
REGISTRATION.COHORT_REGISTRATION.PLAN and STUDENTS.TAB.COHORTS are not menu items; they gate a capability and a tab rather than a screen of their own.
The sidebar is built from core.permission_nodes, so a screen you have no grant on is absent rather than disabled. Both extended-lookup screens were unreachable for exactly that reason until migration 193 gave them nodes.
History
Cohorts were unified onto records.cohorts over several migrations:
- 181 — cohort types arrive:
student_cohortsandlookup_cohort_typecreated;students.cohort_iddropped - 185 — Colleague's "other cohort groups" folded in as
ODSrows - 186 —
records.lookup_cohort_groupandstudent_cohort_groupsdropped - 191, 205 — multi-level membership, and
GOVTone per academic level - 246, 247 — lockstep cohorts:
cohort_majors,cohort_advisors, campus and start-term requirements, auto-sync; Cohort Registration gets a node - 254 — planning split out as its own capability
- 256 — "Federal Cohort" wording, and
academic_level_idin the membership key - 277 —
allows_bulk_assign: bulk program and advisor assignment for a cohort's members
Related
- Cohort Registration — registering a lockstep cohort together
- Lookup Tables — including the two extended lookups
- Roles API —
/roles/cohorts,/roles/cohort-types