semGrid.js Core logic
Generates the ordered list of semesters for a student's cohort window and computes fast lookup maps. Called whenever entry or graduation dates change.
Parent
Called by
PlannerContext.jsx (useMemo on entry/grad dates)
Output consumed by
SemRow, SummerRow, prereqEval, planModel
Semester ID format
Semester IDs are human-readable strings. They're used as stable keys across the whole app.
"fall2026" → Fall 2026 (regular semester)
"spring2027" → Spring 2027
"sumA2027" → Summer I 2027
"sumB2027" → Summer II 2027
"incoming" → Pre-matriculation (placed-out / AP credit)
Exported functions
buildCohortSemesters(entSem, entYear, gradSem, gradYear) → Semester[]
Main entry point. Generates the full ordered list of semesters between entry and graduation (inclusive). Prepends the special
"incoming" slot. Calls generateSemesters() internally and trims to the cohort window.
Returns: Semester[] — each is
{id, label, type, year}generateSemesters(startYear, numYears) → Semester[]
Creates the raw full grid: 4 semesters per year (fall, spring, sumA, sumB) × numYears. Used internally by
buildCohortSemesters().deriveSemMaps(semesters) → { SEM_INDEX, SEM_NEXT, SEM_PREV }
Computes three O(1) lookup maps from the semester array. All are plain objects:
- SEM_INDEX:
{ semId → ordinal }— used to compare "is A before B?" - SEM_NEXT:
{ semId → nextSemId }— used to advance one semester - SEM_PREV:
{ semId → prevSemId }— used to go back one semester
Returns: { SEM_INDEX, SEM_NEXT, SEM_PREV }
Example: 5-year CS cohort (Fall 2026 → Spring 2031)
graph LR
A["incoming\n(ordinal 0)"] --> B["fall2026\n(1)"] --> C["spring2027\n(2)"] --> D["sumA2027\n(3)"] --> E["sumB2027\n(4)"] --> F["fall2027\n(5)"] --> G["...\n..."] --> Z["spring2031\n(last)"]
style A fill:#f6f8fa,stroke:#d0d7de
SEMESTERS array for a Fall 2026 entry, Spring 2031 graduation. Ordinals assigned left-to-right.
Sticky courses: remapping on cohort change
When the user adjusts entry/graduation dates, all placed courses need to stay in their "relative" semester position if stickyCourses = true. PlannerContext handles this: it reads SEM_INDEX for the old grid, then maps each course to the same ordinal position in the new grid.