Part of the Semesters epic.
Goal
Replace the free-text courses.semester with a structured, orderable semester entity so enrollments, gradebook, and graph-context can be grouped/filtered by term and a "current" term can be derived by date.
Background
courses is a shared, per-offering catalog (no user_id) with semester TEXT DEFAULT 'Spring 2026' (backend/db/supabase_schema.sql:37). Everything downstream FKs to course_id (user_courses, course_categories, assignments), so semester is inherited transitively — only the course needs the term, and the same class in two terms is already two courses rows.- The graph layer already keys on a free-text
semester (course_concept_context / course_context, schema lines 211/221/227/235; course_context_service.py:265on_conflict="course_id,concept_name,semester").
Changes
- New
semesters table:id TEXT PK, term TEXT (Fall/Spring/Summer/Winter), year INT, label TEXT ('Fall 2025'), start_date DATE, end_date DATE, sort_key INT (orderable, e.g. year*10 + term ordinal), created_at; UNIQUE(term, year). courses: add semester_id TEXT REFERENCES semesters(id). Backfill by creating one semesters row per distinct existing courses.semester string (map 'Spring 2026' etc. with reasonable start/end dates), then set courses.semester_id. Keep the legacy semester text column for one release (the graph-context path still reads it) and drop it in a later cleanup.- Seed the canonical recent terms with start/end dates so date-derived "current" works.
- Migration file under
backend/db/ following the existing migration_*.sql naming; applied via the documented Supabase SQL-editor flow.
Out of scope
- Repointing
course_concept_context / course_context at semester_id (they keep their free-text semester for now).
Acceptance
semesters exists with seeded, ordered terms incl. start/end dates.- Every
courses row has a valid semester_id; legacy semester text still matches. - A date-derived "current semester" query (term where today ∈ [start, end]) returns exactly one row for the seeded data.
Conventions: all DB access via db/connection.py::table(); no raw httpx.
Part of the Semesters epic.
Goal
Replace the free-text
courses.semesterwith a structured, orderable semester entity so enrollments, gradebook, and graph-context can be grouped/filtered by term and a "current" term can be derived by date.Background
coursesis a shared, per-offering catalog (nouser_id) withsemester TEXT DEFAULT 'Spring 2026'(backend/db/supabase_schema.sql:37). Everything downstream FKs tocourse_id(user_courses,course_categories,assignments), so semester is inherited transitively — only the course needs the term, and the same class in two terms is already twocoursesrows.semester(course_concept_context/course_context, schema lines 211/221/227/235;course_context_service.py:265on_conflict="course_id,concept_name,semester").Changes
semesterstable:id TEXT PK,term TEXT(Fall/Spring/Summer/Winter),year INT,label TEXT('Fall 2025'),start_date DATE,end_date DATE,sort_key INT(orderable, e.g. year*10 + term ordinal),created_at;UNIQUE(term, year).courses: addsemester_id TEXT REFERENCES semesters(id). Backfill by creating onesemestersrow per distinct existingcourses.semesterstring (map 'Spring 2026' etc. with reasonable start/end dates), then setcourses.semester_id. Keep the legacysemestertext column for one release (the graph-context path still reads it) and drop it in a later cleanup.backend/db/following the existingmigration_*.sqlnaming; applied via the documented Supabase SQL-editor flow.Out of scope
course_concept_context/course_contextatsemester_id(they keep their free-text semester for now).Acceptance
semestersexists with seeded, ordered terms incl. start/end dates.coursesrow has a validsemester_id; legacysemestertext still matches.Conventions: all DB access via
db/connection.py::table(); no raw httpx.