The requirements data and its interpreting algorithm is uniquely developed by and for CoursePlan. This document aims to give you a high level overview of how it works.
Requirement computation is split into two phases for efficiency concerns:
-
Pre-computation phase. In this phase, for each requirement (except a few that almost all classes that fulfill it), we enumerate all courses that can fulfill it. We store the enumeration into a "decorated" requirement json.
-
Client-side computation phase. In this phase, the user's course list is compared against requirement data.
- For requirements that can be satisfied by almost any course, we run a simple check in
ifAllEligible
inreq-functions.ts
to see whether it can be satisfied. - For requirements that can be satisfied by a pre-computed list of courses, we check whether the user's course is in that list.
After that, we run some deduplication algorithm to filter away courses that are used to satisfy more than one requirement, but it's actually only allowed to be used to fulfill one.
- For requirements that can be satisfied by almost any course, we run a simple check in
filtered-all-courses.json
contains course information generated byfetcher.ts
.decorated-requirements.json
contains all requirement metadata with additional list of fulfillable courses attached to each requirement. The filefiltered-all-courses.ts
simply reexported a statically-typed version of it.
The metadata of all requirements (name, source, minimum count, etc) is stored in
source-requirements-json.ts
. It's type is specified by the
RequirementsJson
type in types.ts
. The meanings of the field has been documented
as JSDoc directly in type definitions.
The code for precomputing a list of fulfillable courses are in the checkers
folder.
They are organized by colleges and majors. All of those checkers are aggregated together in the
checkers/all-requirements.checker.ts
.
To reiterate, this phase computes a list of satisfying courses for each requirement.
- Setup: each college or major requirement has a field called
checker
. This is a function or an array of function that can be used to check whether a course satisfy a requirement or sub-requirements. - During pre-computation, we run the
checker
against every possible course, to get a list of courses that can satisfy this requirement. - We attach this list to the requirement metadata in
generator.ts
. - Now we created
decorated-requirements.json
. Pre-computation is done.
Each checker is either:
- a TypeScript function
(course: Course) => boolean
that directly tells whether the course satisfies the requirement that this checker correspond to. - or a list of such functions, where each function checks whether the course satisfies one of the sub-requirements that this checker correspond to.
This is how you should use your checkers:
- Case 1: the requirement contains no sub-requirement. Use option 1. Example: CS major practicum.
- Case 2: the requirement contains sub-requirements. Use option 2. Example: CS Core requirement.
Since too many courses can satisfy these requirements, the satisfying course list is not pre-computed to reduce the data sent from server to client.
It's computed on the client side directly in computeUniversityRequirementFulfillments
function
inside reqs-functions.ts
.
- We first naively compute whether a sub-requirement is fulfilled by checking whether any course in user's list is in the pre-computed satisfying course list.
- Then, we run a deduplication pass to remove all courses that have been double-counted that should not be. (NOTE: THIS HAS NOT BEEN IMPLEMENTED YET.)
- Finally, We sum up the total number of sub-requirements/credits already fulfilled for each requirement, to decide whether a requirement as a whole as been satisfied.
If you want to change a requirement, edit relevant files in data
folder.
If you want to add a support for new major or college, add relevant files in data
folder.
For example, if you want to add support for CSGO
major, create a file called csgo.ts
in
data/majors
. Mimic the examples in neighboring files.
Then, import and link your new checkers in data/index.ts
.
For example, if you want to add support for CSGO
major, add
import csgoRequirements from './majors/csgo';
to data/index.ts
's import list, and follow the existing examples to add a new requirement object.
To keep the file clean, you should keep the names alphabetically sorted.
After that, re-run npm run req-gen
to generate a new decorated-requirements.json
.
Without this step, the frontend will keep using the old requirement data and algorithm.