Skip to content

feat: keep banner's link identifier and publish paired sections - #535

Merged
jaspermayone merged 4 commits into
mainfrom
feature-link-identifier
Aug 18, 2026
Merged

feat: keep banner's link identifier and publish paired sections#535
jaspermayone merged 4 commits into
mainfrom
feature-link-identifier

Conversation

@jaspermayone

@jaspermayone jaspermayone commented Aug 18, 2026

Copy link
Copy Markdown
Member

Banner tells us which sections a student must register together. We were throwing that away and had no way to pair a lecture with its lab.

What Banner sends

Every search result carries linkIdentifier and isSectionLinked. The identifier reads as <slot><key>. The first character is the slot, the rest is the key that pairs the sections.

flowchart LR
  A1["CHEM 1000-1A<br/>lecture · A1"] --- B1a["CHEM 1000-2A<br/>lab · B1"]
  A1 --- B1b["CHEM 1000-3A<br/>lab · B1"]
  A2["CHEM 1000-4B<br/>lecture · A2"] --- B2a["CHEM 1000-5B<br/>lab · B2"]
  A2 --- B2b["CHEM 1000-6B<br/>lab · B2"]
Loading

A lecture A1 goes with every lab B1 of the same course. In Fall 2026 Banner marks 684 of 1302 sections as linked.

Changes

  • Migration adds link_identifier and is_section_linked to courses, plus an index on (term_id, subject, course_number, link_identifier).
  • Course#link_slot, Course#link_key, and Course#linked_sections read the pairing. linked_sections returns the active sections of the same course that share the key and sit in a different slot.
  • CatalogImportService stores both fields. It writes them on update as well as on create, so a re-import teaches existing rows their identifier.
  • The public catalog API publishes a linked object on every section: required, identifier, and crns. REST and GraphQL both carry it, and docs/public-catalog-api.md explains it.
  • lib/tasks/backfill.rake holds two one-time repairs. Both are safe to run again, and both take an optional term UID.
    • backfill:seats fills seats_capacity and seats_available for rows that have none. It uses the getEnrollmentInfo fix from fix: send term and crn to banner getenrollmentinfo #534.
    • backfill:link_identifiers reads the Banner catalog and writes only the two link columns. It does not touch meeting times or faculty.

Without a term UID both tasks work on the terms that hold courses and are not over.

Testing

  • 161 examples, 0 failures. New specs cover the model helpers, the importer, both rake tasks, and the linked field on REST and GraphQL.
  • backfill:link_identifiers[202710] run against a copy of the catalog: 672 rows updated, 583 already correct, 47 CRNs not in the database.
  • backfill:seats[202710] run against the same copy.

Public ids

pub_id is the key we already use across our own systems, so the API now publishes it and filters on it.

  • linked.pub_ids sits next to linked.crns and holds the same partner sections, in the same order.
  • GET /api/v1/catalog/sections?pub_id=crs_kw7coe30 and the GraphQL pubIds filter both select sections by public id. A public id is unique across every term, so the filter needs no term. A CRN is only unique inside one term.
  • Course.public_id_for and Course.id_from_public_id do the work. The public id is derived from the numeric id, not a column, so the filter decodes rather than matches.
  • A public id that does not decode is a 400, not an empty page.

Seat counts that Banner reports as negative

Banner reports a negative seat count when a section holds more students than its cap, for example -8 of 15. The courses table forbids a negative count, so 222 rows failed the seat backfill and CourseDataSyncJob failed the same way every night under a log-only rescue.

LeopardWebService now floors the count at zero. A student only needs to read that no seat is left.

API reference on the site

GET /docs/api renders docs/public-catalog-api.md, so the site and the repository cannot say different things. The page carries its own layout and style, so it needs no asset build, and it draws the mermaid diagrams in the source. /docs sends a client to it.

@jaspermayone jaspermayone added the migration Includes a database migration label Aug 18, 2026
The page renders docs/public-catalog-api.md with redcarpet, so the site
and the repository cannot say different things. It carries its own layout
and style, which keeps it free of an asset build, and it draws the
mermaid diagrams in the source.

Claude-Session: https://claude.ai/code/session_01843pqxWRkyjz4ai2wZYebw
@jaspermayone
jaspermayone merged commit 6839ac8 into main Aug 18, 2026
4 checks passed
@jaspermayone
jaspermayone deleted the feature-link-identifier branch August 18, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

migration Includes a database migration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant