gradRequirements.js Core logic

Graduation requirement checking and allocation. Validates a set of placed courses against a Major2 JSON requirement tree. The most algorithmically complex module in the codebase. Also handles the General Electives section and NUPath coverage.

Parent
Consumed by
PlannerContext.jsx, GradPanel.jsx
Imports
constants.js (NUPATH_LABELS)

Requirement type hierarchy

classDiagram class RequirementNode { +type: string } class SECTION { +title: string +requirements: RequirementNode[] +minRequirementCount: number +creditHoursRequired?: number } class XOM { +numCreditsMin: number +courses: RequirementNode[] } class AND { +courses: RequirementNode[] } class OR { +courses: RequirementNode[] } class COURSE { +subject: string +classId: number } class RANGE { +subject: string +idRangeStart: number +idRangeEnd: number } RequirementNode <|-- SECTION RequirementNode <|-- XOM RequirementNode <|-- AND RequirementNode <|-- OR RequirementNode <|-- COURSE RequirementNode <|-- RANGE

checkReq — core validation algorithm

flowchart TD A["checkReq(req, placedSet, courseMap)"] --> B{req.type?} B -->|COURSE| C{"placedSet.has\n(subject+classId)?"} C -->|yes| T1["✓ true"] C -->|no| F1["✗ false"] B -->|AND| D["checkReq() for ALL children"] D --> D2{"all return true?"} D2 -->|yes| T2["✓ true"] D2 -->|no| F2["✗ false"] B -->|OR| E["checkReq() for each child"] E --> E2{"any returns true?"} E2 -->|yes| T3["✓ true"] E2 -->|no| F3["✗ false"] B -->|XOM| G["sum credits of satisfied children"] G --> G2{"sum ≥ numCreditsMin?"} G2 -->|yes| T4["✓ true"] G2 -->|no| F4["✗ false"] B -->|RANGE| H{"any placed course\nmatches subject + ID range?"} H -->|yes| T5["✓ true"] H -->|no| F5["✗ false"] B -->|SECTION| I["count children where checkReq = true"] I --> I2{"count ≥ minRequirementCount?"} I2 -->|yes| T6["✓ true"] I2 -->|no| F6["✗ false"]
checkReq dispatches by type and recurses for compound nodes (AND, OR, XOM, SECTION).

Exported functions

buildPlacedKeySet(placements, placedOut, courseMap) → Set<string>
Builds the canonical key set of all placed courses (e.g. Set{"CS3500", "CS1800"}). Includes both semester-placed and placed-out courses. This set is the sole input to all requirement checking — the structure guarantees O(1) lookup.
Returns: Set<string> — keys are subject+classId with no space
checkReq(req, placedSet, courseMap) → boolean
Pure recursive validator. See algorithm diagram above. For XOM nodes, credit hours come from courseMap lookups (respecting shOverrides). For RANGE nodes, iterates placedSet to find any matching course.
Returns: boolean — true if requirement is satisfied by the placed set
allocateMajor(major, placedSet, courseMap) → SectionResult[]
Maps placed courses to major sections. For each section in the major's requirementSections, determines which placed courses "count toward" it. Uses a greedy first-match strategy — a course is assigned to the earliest section it satisfies.

Internally calls isOrWrappedSection() and normalizeOrWrapped() to fix parser-generated OR-wrapped patterns on-the-fly (no JSON modification needed).
Returns: SectionResult[] — [{title, requirements, allocated: string[], satisfied: boolean}]
allocateMajorWithElectives(major, placedSet, courseMap, placements) → object
Wrapper around allocateMajor(). After allocating to major sections, finds the remaining unallocated placed courses and builds a synthetic General Electives section for them.
Returns: { sections: SectionResult[], generalElectives: Section, allocatedSet: Set<string> }
buildGeneralElectivesSection(placedSet, allocatedSet, courseMap, creditHoursRequired?) → Section
Creates a synthetic SECTION for courses not allocated to any major requirement. Computes total credit hours of unallocated courses. The creditHoursRequired comes from the major JSON's creditHoursRequired field (if present). Returns a section object compatible with the GradPanel SectionBlock renderer.
getNuPathCoverage(placements, courseMap, workPl) → Set<string>
Returns the set of NUPath attribute codes satisfied by all placed courses. Co-op placements do not contribute NUPath. Used by GradPanel to render the 12-cell coverage grid.
Returns: Set<string> — subset of {"ND","EI","IC","FQ","SI","AD","DD","ER","WF","WD","WI","EX"}

Dynamic normalization helpers (private)

isOrWrappedSection(section) → boolean
Detects the parser-generated OR-wrapped pattern: a SECTION with exactly one OR child containing 2+ courses. This appears in "Supporting Courses" and "Technical Electives" sections across many major files.
normalizeOrWrapped(section) → section
Expands an OR-wrapped section on-the-fly: extracts courses from the OR, makes them direct COURSE children of the section, sets minRequirementCount = 1. This makes the section render as "One of (X/N)" instead of "All of: (One of (0/N))".

Known parser bugs and fixes

⚠️
Bug 1 — Broken pool structure (297 files, fixed in data):
Some catalog years encode elective pools as SECTION { minReq: N, requirements: [N RANGEs] } instead of XOM { numCreditsMin, courses: [RANGEs] }. Fixed by running fix-pooled-requirements.js against the JSON files.

Bug 2 — OR-wrapped sections (all years, fixed in code):
Sections like "Supporting Courses" are encoded as SECTION { requirements: [OR { courses: [8 courses] }] }. Fixed dynamically by normalizeOrWrapped() — no JSON changes needed. Applied in allocateMajor() before any processing.