Skip to content

Commit

Permalink
Requirement Migrations (#874)
Browse files Browse the repository at this point in the history
* Began working on migration structure with ECE core course requirements

* formatting

* All updates with requirement migrations but error with fieldOfStudyReqs function type

* JSON Updated with decorated migrations - still need to change json generator to include all fields of migrations

* Working requirement migrations with ECE core courses migrations

* formatting

* Maintained order of requirements when migrating, fixed format

* Added helper

* Added more inline comments
  • Loading branch information
nidhi-mylavarapu authored and andxu282 committed Apr 25, 2024
1 parent 1576f99 commit 613f843
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 28 deletions.
3 changes: 2 additions & 1 deletion src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import csRequirements, { csAdvisors } from './majors/cs';
import deaRequirements, { deaAdvisors } from './majors/dea';
import easRequirements, { easAdvisors } from './majors/eas';
import economicsRequirements, { economicsAdvisors } from './majors/econ';
import eceRequirements, { eceAdvisors } from './majors/ece';
import eceRequirements, { eceAdvisors, eceMigrations } from './majors/ece';
import essRequirements, { essAdvisors } from './majors/ess';
import englishRequirements, { englishAdvisors } from './majors/engl';
import envEngineeringRequirements, { envEngineeringAdvisors } from './majors/envE';
Expand Down Expand Up @@ -266,6 +266,7 @@ const json: RequirementsJson = {
schools: ['EN'],
requirements: eceRequirements,
advisors: eceAdvisors,
migrations: eceMigrations,
abbrev: 'ECE',
},
ENGL: {
Expand Down
28 changes: 23 additions & 5 deletions src/data/majors/ece.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Course, CollegeOrMajorRequirement } from '../../requirements/types';
import { Course, CollegeOrMajorRequirement, RequirementMigration } from '../../requirements/types';
import {
ifCodeMatch,
includesWithSubRequirements,
Expand Down Expand Up @@ -33,13 +33,13 @@ const eceRequirements: readonly CollegeOrMajorRequirement[] = [
},
{
name: 'Core Courses',
description: 'ECE 2100, ECE 2200 ECE 3400',
description: 'ECE 2100, ECE 2720',
source:
'https://www.ece.cornell.edu/ece/programs/undergraduate-programs/majors/program-requirements',
checker: includesWithSubRequirements(['ECE 2100'], ['ECE 2200'], ['ECE 3400']),
checker: includesWithSubRequirements(['ECE 2100'], ['ECE 2720']),
fulfilledBy: 'courses',
perSlotMinCount: [1, 1, 1],
slotNames: ['ECE 2100', 'ECE 2200', 'ECE 3400'],
perSlotMinCount: [1, 1],
slotNames: ['ECE 2100', 'ECE 2720'],
},
{
name: 'Foundation Courses',
Expand Down Expand Up @@ -175,3 +175,21 @@ export default eceRequirements;
export const eceAdvisors: AdvisorGroup = {
advisors: [{ name: 'Sharif Ewais-Orozco', email: 'ugrad-coordinator@ece.cornell.edu' }],
};

export const eceMigrations: RequirementMigration[] = [
{
entryYear: 2020, // For students with an entry year of 2020 or earlier, this requirement applies. For students with an entry year of 2021 or later, the above Core Courses requirement applies
type: 'Modify',
fieldName: 'Core Courses',
newValue: {
name: 'Core Courses',
description: 'ECE 2100, ECE 2200, & ECE 3400',
source:
'https://www.ece.cornell.edu/ece/programs/undergraduate-programs/majors/program-requirements',
checker: includesWithSubRequirements(['ECE 2100'], ['ECE 2200'], ['ECE 3400']),
fulfilledBy: 'courses',
perSlotMinCount: [1, 1, 1],
slotNames: ['ECE 2100', 'ECE 2200', 'ECE 3400'],
},
},
];
4 changes: 4 additions & 0 deletions src/requirement-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ type DecoratedCollegeOrMajorRequirement = RequirementCommon &
readonly conditions?: Readonly<RequirementCourseConditions>;
}>;

type MigrationWithDecoratedRequirement = RequirementMigration & {
newValue?: DecoratedCollegeOrMajorRequirement;
};

/**
* CourseTaken is the data type used in requirement computation.
* It's a significantly simplified version of FirestoreSemesterCourse to make it easy to mock for
Expand Down
45 changes: 37 additions & 8 deletions src/requirements/decorated-requirements.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 74 additions & 12 deletions src/requirements/requirement-frontend-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ export function allowCourseDoubleCountingBetweenRequirements(
requirementA: RequirementWithIDSourceType,
requirementB: RequirementWithIDSourceType
): boolean {
// console.log("Entered allow course double counting thing")
if (!requirementA || !requirementB) {
// If requirement is undefined, handle accordingly
return false; // Or any other value, or skip this item
}
// console.log(requirementA)
// console.log(requirementB)
const allowCourseDoubleCounting =
requirementA.allowCourseDoubleCounting || requirementB.allowCourseDoubleCounting || false;

Expand Down Expand Up @@ -118,27 +125,79 @@ export function allowCourseDoubleCountingBetweenRequirements(
return false;
}

const reqWithSourceInfo = (
req: DecoratedCollegeOrMajorRequirement,
sourceType: 'Major' | 'Minor',
sourceSpecificName: string
) => ({
...req,
id: `${sourceType}-${sourceSpecificName}-${req.name}`,
sourceType,
sourceSpecificName,
});

/**
* Get the requirements for a provided collection of majors/minors
*
* @param sourceType The type of the field of study, e.g. 'Major' or 'Minor'
* @param fields the names of the majors/minors
* @returns An array of requirements corresponding to every field of study in `fields`
*/
const fieldOfStudyReqs = (sourceType: 'Major' | 'Minor', fields: readonly string[]) => {
const fieldOfStudyReqs = (
sourceType: 'Major' | 'Minor',
fields: readonly string[],
entryYear: string
) => {
const jsonKey = sourceType.toLowerCase() as 'major' | 'minor';
const fieldRequirements = requirementJson[jsonKey];

return fields
.map(field => {
const fieldRequirement = fieldRequirements[field];
return fieldRequirement?.requirements.map(
it =>
(({
...it,
id: `${sourceType}-${field}-${it.name}`,
sourceType,
sourceSpecificName: field,
} as const) ?? [])
const requirementMigrations = fieldRequirement?.migrations || [];

// Filter migrations based on entryYear
const filteredMigrations = requirementMigrations.filter(
migration => parseInt(entryYear, 10) <= migration.entryYear
);

// Collect all requirements corresponding to 'Add' migrations in one array
const addMigrationNewValues = filteredMigrations
.filter(migration => migration.type === 'Add')
.map(migration => migration.newValue);

return (
fieldRequirement?.requirements
// Find migrations that match existing requirements - must be 'Delete' or 'Modify'
.filter(it => {
const matchingMigration = filteredMigrations.find(
migration => migration.fieldName === it.name
);

// If a reqiurement matches a 'Delete' migration, return false (filter it out of overall requirements)
if (matchingMigration) {
if (matchingMigration.type === 'Delete') {
return false;
}
}
return true;
})
.map(it => {
const matchingMigration = filteredMigrations.find(
migration => migration.fieldName === it.name
);

// If a requirement matches a 'Modify' migration, map it to it's new value
if (matchingMigration) {
if (matchingMigration.type === 'Modify') {
return matchingMigration.newValue as DecoratedCollegeOrMajorRequirement;
}
}
return it;
})
.concat(addMigrationNewValues.filter(Boolean) as DecoratedCollegeOrMajorRequirement[])
// Use helper to map requirements to requirements with source info
.map(it => reqWithSourceInfo(it, sourceType, field))
);
})
.flat();
Expand Down Expand Up @@ -181,6 +240,7 @@ export function getUserRequirements({
college,
major: majors,
minor: minors,
entranceYear,
grad,
}: AppOnboardingData): readonly RequirementWithIDSourceType[] {
// check university & college & major & minor requirements
Expand All @@ -201,8 +261,8 @@ export function getUserRequirements({
)
: [];
const collegeReqs = college ? specializedForCollege(college, majors) : [];
const majorReqs = fieldOfStudyReqs('Major', majors);
const minorReqs = fieldOfStudyReqs('Minor', minors);
const majorReqs = fieldOfStudyReqs('Major', majors, entranceYear);
const minorReqs = fieldOfStudyReqs('Minor', minors, entranceYear);
const gradReqs = grad
? requirementJson.grad[grad].requirements.map(
it =>
Expand All @@ -215,7 +275,9 @@ export function getUserRequirements({
)
: [];
// flatten all requirements into single array
return [uniReqs, collegeReqs, majorReqs, minorReqs, gradReqs].flat();
return [uniReqs, collegeReqs, majorReqs, minorReqs, ...gradReqs]
.flat()
.filter(Boolean) as RequirementWithIDSourceType[];
}

/**
Expand Down
34 changes: 32 additions & 2 deletions src/requirements/requirement-json-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
RequirementChecker,
Course,
MutableMajorRequirements,
RequirementMigration,
} from './types';
import sourceRequirements, { colleges } from '../data';
import { NO_FULFILLMENTS_COURSE_ID, SPECIAL_COURSES } from '../data/constants';
Expand Down Expand Up @@ -36,6 +37,9 @@ type InitialRequirementDecorator = (
type RequirementDecorator = (
requirement: DecoratedCollegeOrMajorRequirement
) => DecoratedCollegeOrMajorRequirement;
type MigrationRequirementDecorator = (
migration: RequirementMigration
) => MigrationWithDecoratedRequirement;

const getEligibleCoursesFromRequirementCheckers = (
checkers: readonly RequirementChecker[]
Expand Down Expand Up @@ -294,6 +298,22 @@ const sortRequirementCourses: RequirementDecorator = requirement => {
}
};

const decorateMigrationValue: MigrationRequirementDecorator = migration => {
if (migration.newValue) {
const decoratedValue = decorateRequirementWithCourses(migration.newValue);
const fullyDecoratedMigration = {
...migration,
newValue: decorateRequirementWithExams(decoratedValue),
};
return fullyDecoratedMigration;
}
return migration;
};

const decorateMigrations = (
migrations: readonly RequirementMigration[]
): readonly MigrationWithDecoratedRequirement[] => migrations.map(decorateMigrationValue);

const generateDecoratedRequirementsJson = (): DecoratedRequirementsJson => {
const { university, college, major, minor, grad } = sourceRequirements;
type MutableDecoratedJson = {
Expand Down Expand Up @@ -355,15 +375,25 @@ const generateDecoratedRequirementsJson = (): DecoratedRequirementsJson => {
};
});
Object.entries(major).forEach(([majorName, majorRequirement]) => {
const { requirements, advisors, specializations, abbrev: abbr, ...rest } = majorRequirement;
const {
requirements,
migrations,
advisors,
specializations,
abbrev: abbr,
...rest
} = majorRequirement;
decoratedJson.major[majorName] = {
...rest,
requirements: decorateRequirements(requirements),
migrations: migrations
? (decorateMigrations(migrations) as RequirementMigration[])
: undefined,
specializations: specializations && decorateRequirements(specializations),
};
});
Object.entries(minor).forEach(([minorName, minorRequirement]) => {
const { requirements, advisors, abbrev: abbr, ...rest } = minorRequirement;
const { requirements, migrations, advisors, abbrev: abbr, ...rest } = minorRequirement;
decoratedJson.minor[minorName] = {
...rest,
requirements: decorateRequirements(requirements),
Expand Down
10 changes: 10 additions & 0 deletions src/requirements/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,23 @@ export type CollegeRequirements<R> = {
};
};

export type typeOfMigration = 'Modify' | 'Delete' | 'Add';

export type RequirementMigration = {
entryYear: number /** This migration applies to students with an entryYear equal to or EARLIER this entry year */;
type: typeOfMigration /** Modify or Delete Migration? This field must already exist in requirements file */;
fieldName: string;
newValue?: CollegeOrMajorRequirement /** Required for modify and add migrations */;
};

export type Major<R> = Readonly<{
name: string;
schools: readonly string[];
requirements: readonly R[];
/** College requirements that have been "specialized" for this major */
specializations?: readonly R[];
advisors?: AdvisorGroup;
migrations?: RequirementMigration[];
readonly abbrev?: string;
}>;

Expand Down

0 comments on commit 613f843

Please sign in to comment.