From 091c6679beb4c0e9946870cb9a74e9435caaf5e0 Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Tue, 16 Nov 2021 22:29:26 -0500 Subject: [PATCH 1/2] No longer mark engineering liberal studies data as allow double counting (#575) --- src/requirements/data/colleges/en.ts | 1 - src/requirements/decorated-requirements.json | 1 - 2 files changed, 2 deletions(-) diff --git a/src/requirements/data/colleges/en.ts b/src/requirements/data/colleges/en.ts index 1058797a9..3a177fd95 100644 --- a/src/requirements/data/colleges/en.ts +++ b/src/requirements/data/colleges/en.ts @@ -113,7 +113,6 @@ const engineeringRequirements: readonly CollegeOrMajorRequirement[] = [ ) || courseIsForeignLang(course), ], fulfilledBy: 'courses', - allowCourseDoubleCounting: true, perSlotMinCount: [6], slotNames: ['Course'], additionalRequirements: { diff --git a/src/requirements/decorated-requirements.json b/src/requirements/decorated-requirements.json index 2df5b1954..2101be4d9 100644 --- a/src/requirements/decorated-requirements.json +++ b/src/requirements/decorated-requirements.json @@ -24792,7 +24792,6 @@ "description": "A minimum of six courses must be taken.", "source": "https://www.engineering.cornell.edu/students/undergraduate-students/advising/liberal-studies", "fulfilledBy": "courses", - "allowCourseDoubleCounting": true, "perSlotMinCount": [ 6 ], From a418c6d0b267407fb30ba5fbe634e48d246c88fe Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Tue, 16 Nov 2021 23:13:54 -0500 Subject: [PATCH 2/2] [easy] Delete migration scripts (#586) Finally all migration scripts are run! --- .../migrate-engineering-liberal-arts-data.ts | 122 ------------------ .../admin/opt-out-data-migration.ts | 62 --------- 2 files changed, 184 deletions(-) delete mode 100644 src/requirements/admin/migrate-engineering-liberal-arts-data.ts delete mode 100644 src/requirements/admin/opt-out-data-migration.ts diff --git a/src/requirements/admin/migrate-engineering-liberal-arts-data.ts b/src/requirements/admin/migrate-engineering-liberal-arts-data.ts deleted file mode 100644 index 70b00360a..000000000 --- a/src/requirements/admin/migrate-engineering-liberal-arts-data.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* eslint-disable no-console */ - -import { - usernameCollection, - selectableRequirementChoicesCollection, -} from '../../firebase-admin-config'; -import { requirementAllowDoubleCounting } from '../requirement-frontend-utils'; -import getUserRequirementDataOnAdmin from './requirement-graph-admin-utils'; - -const REQUIREMENT_ID = 'College-EN-Liberal Studies: 6 courses'; - -/** - * Perform migration of user's engineering liberal studies data. - * - * 1. Find all courses that are connected to this engineering liberal studies requirement. - * 2. For each course, find requirements that connects to it - * a. Check if there all requirements connected to it are either - * - liberal study requirement - * - allow double counting - * b. If the above check returns true, it means that user has not chosen other requirement, - * then we can bind it to liberal study requirement (sort of) - * 3. For all such courses found above, select liberal study as requirement, except that - * - The user might already made a choice on it. It happens maybe when the old requirement data is - * not the same as the new one, or maybe due to override. - * - In those cases, we skip and print a warning. - */ -async function runOnUser(userEmail: string, runOnDB: boolean) { - const { - selectableRequirementChoices, - onboardingData, - userRequirements, - requirementFulfillmentGraph, - } = await getUserRequirementDataOnAdmin(userEmail); - const userRequirementMap = Object.fromEntries(userRequirements.map(it => [it.id, it])); - - const connectedCourses = requirementFulfillmentGraph.getConnectedCoursesFromRequirement( - REQUIREMENT_ID - ); - const connectedCoursesOnlyConnectedToDoubleCountedReqs = connectedCourses.filter(course => { - const connectedRequirementIDs = requirementFulfillmentGraph.getConnectedRequirementsFromCourse( - course - ); - return connectedRequirementIDs.every( - id => - id === REQUIREMENT_ID || - requirementAllowDoubleCounting(userRequirementMap[id], onboardingData.major) - ); - }); - console.group('coursesNotConnectedToOtherRequirementThatDoesNotAllowDoubleCounting'); - connectedCoursesOnlyConnectedToDoubleCountedReqs.forEach(it => console.log(it)); - console.groupEnd(); - - const choicesOld: Record = {}; - const choicesUpdates: Record = {}; - connectedCoursesOnlyConnectedToDoubleCountedReqs.forEach(course => { - const stringId = String(course.uniqueId); - const choice = selectableRequirementChoices[stringId]; - if (choice === REQUIREMENT_ID) { - console.log( - `[INFO] Course with unique ID ${course.uniqueId} (${course.code}) is already bind to ${REQUIREMENT_ID}. Skip.` - ); - return; - } - if (choice) { - const requirementOfChoice = userRequirementMap[choice]; - if (requirementOfChoice == null) { - // Happens when - // - user switch college/major - // - leftover like other old liberal studies requirement (18 credits, 3 category stuff) - console.log(`[INFO] Non-existing requirement: ${choice}.`); - } else if (requirementAllowDoubleCounting(requirementOfChoice, onboardingData.major)) { - // Not sure how it happens, but it's mostly harmless. - // We can still make the requirement choose the liberal studies one. - console.log( - `[INFO] Somehow a requirement allow double counting is recorded as choice: ${choice}` - ); - } else { - // Seen in staging but not in prod: probably some corrupted data during dev. - console.log( - `[INFO] Connected to ${choice} that can no longer be satisfied by course: ${course.uniqueId}.` - ); - } - } - console.log( - `Now binding course with unique ID ${course.uniqueId} (${course.code}) to ${REQUIREMENT_ID}.` - ); - if (choicesUpdates[stringId]) choicesOld[stringId] = choicesUpdates[stringId]; - choicesUpdates[stringId] = REQUIREMENT_ID; - }); - - console.log('Relevant Choices Old:'); - console.log(choicesOld); - - console.log('Choices Updates:'); - console.log(choicesUpdates); - - if (runOnDB && Object.keys(choicesUpdates).length > 0) { - await selectableRequirementChoicesCollection - .doc(userEmail) - .set({ ...selectableRequirementChoices, ...choicesUpdates }); - } -} - -async function main() { - let userEmail = process.argv[2]; - const runOnDB = process.argv.includes('--run-on-db'); - if (userEmail != null && userEmail !== '--run-on-db') { - await runOnUser(userEmail, runOnDB); - return; - } - const collection = await usernameCollection.get(); - const userEmails = collection.docs.map(it => it.id); - for (userEmail of userEmails) { - console.group(`Running on ${userEmail}...`); - // Intentionally await in a loop to have no interleaved console logs. - // eslint-disable-next-line no-await-in-loop - await runOnUser(userEmail, runOnDB); - console.groupEnd(); - } -} - -main(); diff --git a/src/requirements/admin/opt-out-data-migration.ts b/src/requirements/admin/opt-out-data-migration.ts deleted file mode 100644 index 5dde710ff..000000000 --- a/src/requirements/admin/opt-out-data-migration.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable no-console */ - -import { usernameCollection } from '../../firebase-admin-config'; -import { getFirestoreCourseOptInOptOutChoicesBuilder } from '../requirement-graph-builder-from-user-data'; -import { getUserDataOnAdmin } from './requirement-graph-admin-utils'; - -/** Compute opt-out choices for given user using their existing choices. */ -async function runOnUser(userEmail: string) { - const { - courses, - onboardingData, - toggleableRequirementChoices, - selectableRequirementChoices, - } = await getUserDataOnAdmin(userEmail); - - const builder = getFirestoreCourseOptInOptOutChoicesBuilder( - courses, - onboardingData, - toggleableRequirementChoices, - selectableRequirementChoices, - {} - ); - - console.log(userEmail); - courses.forEach(course => { - const choices = builder(course); - - console.log(`${course.code} (${course.uniqueId}):`); - console.log( - `- selected requirement: ${selectableRequirementChoices[course.uniqueId] || 'None'}` - ); - console.log(`- optOut = [${choices.optOut.join(', ')}]`); - console.log( - `- acknowledgedCheckerWarningOptIn = [${choices.acknowledgedCheckerWarningOptIn.join(', ')}]` - ); - }); - - console.log('\n'); -} - -async function main() { - const userEmailFromArgument = process.argv[2]; - if (userEmailFromArgument != null) { - await runOnUser(userEmailFromArgument); - return; - } - const collection = await usernameCollection.get(); - const userEmails = collection.docs.map(it => it.id); - for (const userEmail of userEmails) { - console.group(`Running on ${userEmail}...`); - try { - // Intentionally await in a loop to have no interleaved console logs. - // eslint-disable-next-line no-await-in-loop - await runOnUser(userEmail); - } catch (e) { - console.log(e); - } - console.groupEnd(); - } -} - -main();