🧠 Context
Course prerequisite data in the Carleton calendar is written in natural language (e.g. "COMP 2401 (minimum grade of C-) and (COMP 2404 or SYSC 3010) and COMP 2406"). The scraper (separate ticket) will capture these strings into a prereqRaw field on each course. This ticket is the parser that converts those raw strings into the structured Prereq AST defined in src/types/course.ts.
The AST supports five node kinds — course, all (AND), any (OR), credits, and raw. The parser only needs to produce course, all, and any nodes for now. Anything it can't handle should fall back to { kind: 'raw', text: '...' } — see src/lib/validatePlan.ts and src/data/loadCourses.ts for how raw nodes are handled gracefully throughout the app (soft warning in the planner, no edges in the explorer).
COMP 3004 is a known test fixture. Its raw prereq string from the Carleton calendar and its expected AST output already exist in src/data/courses.json. Use this as your primary test case — the parser should produce an AST that matches the existing COMP 3004 prereq field exactly.
The parser is a pure function with no React dependency. It lives alongside the other pure lib utilities. It can be enhanced in the future to cover any rare edge cases, get the main functionality working first.
🛠️ Implementation Plan
-
Create src/lib/parsePrereq.ts. The function signature should be:
export function parsePrereq(raw: string): Prereq | null
Returns null for an empty or missing string, a structured Prereq node otherwise.
-
Handle the following patterns from the Carleton calendar:
- Single course reference:
COMP 1405
- Course with minimum grade:
COMP 2401 (minimum grade of C-)
- AND of conditions:
X and Y and Z
- OR of conditions:
(X or Y or Z)
- Nested combinations, as seen in COMP 3004
- Anything unrecognised →
{ kind: 'raw', text: originalString }
-
Write tests in src/lib/parsePrereq.test.ts. At minimum:
- Single course code parses correctly
- Min grade is captured on a
course node
- COMP 3004's raw string produces an AST that matches the existing entry in
courses.json
- An unrecognisable string falls back to
{ kind: 'raw', text: '...' }
-
Run pnpm test and pnpm typecheck to verify.
✅ Acceptance Criteria
🧠 Context
Course prerequisite data in the Carleton calendar is written in natural language (e.g.
"COMP 2401 (minimum grade of C-) and (COMP 2404 or SYSC 3010) and COMP 2406"). The scraper (separate ticket) will capture these strings into aprereqRawfield on each course. This ticket is the parser that converts those raw strings into the structuredPrereqAST defined insrc/types/course.ts.The AST supports five node kinds —
course,all(AND),any(OR),credits, andraw. The parser only needs to producecourse,all, andanynodes for now. Anything it can't handle should fall back to{ kind: 'raw', text: '...' }— seesrc/lib/validatePlan.tsandsrc/data/loadCourses.tsfor howrawnodes are handled gracefully throughout the app (soft warning in the planner, no edges in the explorer).COMP 3004 is a known test fixture. Its raw prereq string from the Carleton calendar and its expected AST output already exist in
src/data/courses.json. Use this as your primary test case — the parser should produce an AST that matches the existing COMP 3004prereqfield exactly.The parser is a pure function with no React dependency. It lives alongside the other pure lib utilities. It can be enhanced in the future to cover any rare edge cases, get the main functionality working first.
🛠️ Implementation Plan
Create
src/lib/parsePrereq.ts. The function signature should be:Returns
nullfor an empty or missing string, a structuredPrereqnode otherwise.Handle the following patterns from the Carleton calendar:
COMP 1405COMP 2401 (minimum grade of C-)X and Y and Z(X or Y or Z){ kind: 'raw', text: originalString }Write tests in
src/lib/parsePrereq.test.ts. At minimum:coursenodecourses.json{ kind: 'raw', text: '...' }Run
pnpm testandpnpm typecheckto verify.✅ Acceptance Criteria
parsePrereq(rawString)returns a validPrereqornull{ kind: 'course', code }minGradeon acoursenode{ kind: 'all', of: [...] }{ kind: 'any', of: [...] }courses.json{ kind: 'raw', text: '...' }— no throwspnpm typecheckandpnpm testpass