From ca6a39de535f64d7d0ee2774b3d5f3e37f0d32d7 Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Wed, 13 Sep 2023 08:32:05 -0400 Subject: [PATCH 1/9] Adding new file in scripts and debugging credentials --- scripts/gen-req-full-stats.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 scripts/gen-req-full-stats.ts diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts new file mode 100644 index 000000000..509cabee0 --- /dev/null +++ b/scripts/gen-req-full-stats.ts @@ -0,0 +1,10 @@ +import { usernameCollection } from './firebase-config'; + +const main = async () => { + const collection = await usernameCollection.get(); + for (const { id } of collection.docs) { + console.log(id); + } +}; + +main(); \ No newline at end of file From f11b11ef6e76dc6335839e81838269506da94ace Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Wed, 13 Sep 2023 13:17:56 -0400 Subject: [PATCH 2/9] Implementing the script to determine course frequencies to fulfill given requirements - Outputs to console the JSON object --- scripts/gen-req-full-stats.ts | 97 ++++++++++++++++++++++++++++++++--- src/requirement-types.d.ts | 4 +- 2 files changed, 92 insertions(+), 9 deletions(-) diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts index 509cabee0..617f0dd99 100644 --- a/scripts/gen-req-full-stats.ts +++ b/scripts/gen-req-full-stats.ts @@ -1,10 +1,93 @@ -import { usernameCollection } from './firebase-config'; +import { + onboardingDataCollection, + semestersCollection, + toggleableRequirementChoicesCollection, + overriddenFulfillmentChoicesCollection, +} from './firebase-config'; -const main = async () => { - const collection = await usernameCollection.get(); - for (const { id } of collection.docs) { - console.log(id); +import computeGroupedRequirementFulfillmentReports from '../src/requirements/requirement-frontend-computation'; + +import { RequirementFulfillment, CourseTaken } from '../src/requirement-types'; + +const idRequirementFrequency = new Map[]>(); + +/** + * Prints to console.out the json string of frequencies each course take to + * fulfill a given requirement + */ +async function computeRequirementFullfillmentStatistics() { + await semestersCollection.get().then(semQuerySnapshot => { + // Query a user's semesters + semQuerySnapshot.forEach(async doc => { + // obtain the user's semesters, onboarding data, etc... + let { semesters, _ } = doc.data(); + let onboardingData = await onboardingDataCollection.doc(doc.id).get(); + let toggleableRequirementChoices = await toggleableRequirementChoicesCollection + .doc(doc.id) + .get(); + let overriddenFulfillmentChoices = await overriddenFulfillmentChoicesCollection + .doc(doc.id) + .get(); + + // For the given users semesters compute the requirements they fulfill + // and which course it took for them to fulfill them + let res = computeGroupedRequirementFulfillmentReports( + semesters, + onboardingData, + toggleableRequirementChoices, + overriddenFulfillmentChoices + ); + + res.groupedRequirementFulfillmentReport.forEach(currentGroup => { + let currentGroupReqs: RequirementFulfillment[] = currentGroup.reqs; + + // For a given group of requirements, we iterate through all courses + // that were needed to fulfill each slot + currentGroupReqs.forEach(reqFulfillment => { + let key: string = reqFulfillment.requirement.id; + + let fulfillment: readonly CourseTaken[][] = reqFulfillment.fulfillment + .safeCourses as readonly CourseTaken[][]; + + // Obtain the frequency list for this particular group's requirements + let freqList: Map[] = idRequirementFrequency.has(key) + ? (idRequirementFrequency.get(key) as Map[]) + : []; + + // Iterate over all slots in the requirement group + for (let slotNumber = 0; slotNumber < fulfillment.length; slotNumber++) { + if (freqList.length == slotNumber) { + freqList.push(new Map()); + } + let currentCourseSlot: CourseTaken[] = fulfillment[slotNumber]; + let currentRequirementSlotFreq = freqList[slotNumber]; + + // Iterate over all courses taken to fulfill the req-slot + for (let j = 0; j < currentCourseSlot.length; j++) { + let currentCourseId = currentCourseSlot[j].courseId; + + let pastFreq = currentRequirementSlotFreq.has(currentCourseId) + ? (currentRequirementSlotFreq.get(currentCourseId) as number) + : 0; + // Increase frequency each time a course is used to fulfill + // a specific requirement slot + currentRequirementSlotFreq.set(currentCourseId, pastFreq + 1); + } + } + }); + }); + }); + }); + + // Serialize the large hashmap into a JSON object + let returnObject = {}; + for (const [key, value] of idRequirementFrequency) { + returnObject[key] = value.map(item => ({ ...item })); } -}; -main(); \ No newline at end of file + // Turn the JSON object into a string to output to the console + let returnString = JSON.stringify(returnObject, null, 2); + console.log(returnString); +} + +computeRequirementFullfillmentStatistics(); diff --git a/src/requirement-types.d.ts b/src/requirement-types.d.ts index a0eea0b02..ee5d60601 100644 --- a/src/requirement-types.d.ts +++ b/src/requirement-types.d.ts @@ -101,7 +101,7 @@ type DecoratedCollegeOrMajorRequirement = RequirementCommon & * It's a significantly simplified version of FirestoreSemesterCourse to make it easy to mock for * the purpose of requirement computation. */ -type CourseTaken = { +export type CourseTaken = { /** The course ID from course roster, or our dummy id to denote special courses like FWS equiv. */ readonly courseId: number; /** Using the unique ID of firestore course for real course, string for swim test and AP/IB. */ @@ -154,7 +154,7 @@ type MixedRequirementFulfillmentStatisticsWithAdditionalRequirements = MixedRequ }; }; -type RequirementFulfillment = { +export type RequirementFulfillment = { /** The original requirement object. */ readonly requirement: RequirementWithIDSourceType; readonly fulfillment: MixedRequirementFulfillmentStatisticsWithAdditionalRequirements; From 06a026f077f9cbbae0d5d520f38cafe2b6288cc3 Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Wed, 13 Sep 2023 14:14:35 -0400 Subject: [PATCH 3/9] Fixing formatting and linting errors --- scripts/gen-req-full-stats.ts | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts index 617f0dd99..124ee525b 100644 --- a/scripts/gen-req-full-stats.ts +++ b/scripts/gen-req-full-stats.ts @@ -7,7 +7,7 @@ import { import computeGroupedRequirementFulfillmentReports from '../src/requirements/requirement-frontend-computation'; -import { RequirementFulfillment, CourseTaken } from '../src/requirement-types'; +import { RequirementFulfillment, CourseTaken } from '../src/requirement-types.d'; const idRequirementFrequency = new Map[]>(); @@ -20,53 +20,53 @@ async function computeRequirementFullfillmentStatistics() { // Query a user's semesters semQuerySnapshot.forEach(async doc => { // obtain the user's semesters, onboarding data, etc... - let { semesters, _ } = doc.data(); - let onboardingData = await onboardingDataCollection.doc(doc.id).get(); - let toggleableRequirementChoices = await toggleableRequirementChoicesCollection + const semestersAndPlans = doc.data(); + const onboardingData = await onboardingDataCollection.doc(doc.id).get(); + const toggleableRequirementChoices = await toggleableRequirementChoicesCollection .doc(doc.id) .get(); - let overriddenFulfillmentChoices = await overriddenFulfillmentChoicesCollection + const overriddenFulfillmentChoices = await overriddenFulfillmentChoicesCollection .doc(doc.id) .get(); // For the given users semesters compute the requirements they fulfill // and which course it took for them to fulfill them - let res = computeGroupedRequirementFulfillmentReports( - semesters, + const res = computeGroupedRequirementFulfillmentReports( + semestersAndPlans.semester, onboardingData, toggleableRequirementChoices, overriddenFulfillmentChoices ); res.groupedRequirementFulfillmentReport.forEach(currentGroup => { - let currentGroupReqs: RequirementFulfillment[] = currentGroup.reqs; + const currentGroupReqs: RequirementFulfillment[] = currentGroup.reqs; // For a given group of requirements, we iterate through all courses // that were needed to fulfill each slot currentGroupReqs.forEach(reqFulfillment => { - let key: string = reqFulfillment.requirement.id; + const key: string = reqFulfillment.requirement.id; - let fulfillment: readonly CourseTaken[][] = reqFulfillment.fulfillment + const fulfillment: readonly CourseTaken[][] = reqFulfillment.fulfillment .safeCourses as readonly CourseTaken[][]; // Obtain the frequency list for this particular group's requirements - let freqList: Map[] = idRequirementFrequency.has(key) + const freqList: Map[] = idRequirementFrequency.has(key) ? (idRequirementFrequency.get(key) as Map[]) : []; // Iterate over all slots in the requirement group - for (let slotNumber = 0; slotNumber < fulfillment.length; slotNumber++) { - if (freqList.length == slotNumber) { + for (let slotNumber = 0; slotNumber < fulfillment.length; slotNumber += 1) { + if (freqList.length === slotNumber) { freqList.push(new Map()); } - let currentCourseSlot: CourseTaken[] = fulfillment[slotNumber]; - let currentRequirementSlotFreq = freqList[slotNumber]; + const currentCourseSlot: CourseTaken[] = fulfillment[slotNumber]; + const currentRequirementSlotFreq = freqList[slotNumber]; // Iterate over all courses taken to fulfill the req-slot - for (let j = 0; j < currentCourseSlot.length; j++) { - let currentCourseId = currentCourseSlot[j].courseId; + for (let j = 0; j < currentCourseSlot.length; j += 1) { + const currentCourseId = currentCourseSlot[j].courseId; - let pastFreq = currentRequirementSlotFreq.has(currentCourseId) + const pastFreq = currentRequirementSlotFreq.has(currentCourseId) ? (currentRequirementSlotFreq.get(currentCourseId) as number) : 0; // Increase frequency each time a course is used to fulfill @@ -80,13 +80,13 @@ async function computeRequirementFullfillmentStatistics() { }); // Serialize the large hashmap into a JSON object - let returnObject = {}; + const returnObject = {}; for (const [key, value] of idRequirementFrequency) { returnObject[key] = value.map(item => ({ ...item })); } // Turn the JSON object into a string to output to the console - let returnString = JSON.stringify(returnObject, null, 2); + const returnString = JSON.stringify(returnObject, null, 2); console.log(returnString); } From d06218c1f4aa578c669537b069be5104966d7f98 Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Sun, 22 Oct 2023 18:19:24 -0400 Subject: [PATCH 4/9] Implmenting gen-req-full-stats - Storing data in firestore --- scripts/firebase-config.ts | 17 ++- scripts/gen-req-full-stats.ts | 147 ++++++++++++++------------ src/requirement-types.d.ts | 4 +- src/requirements/fulfillment-stats.ts | 36 +++++++ 4 files changed, 128 insertions(+), 76 deletions(-) create mode 100644 src/requirements/fulfillment-stats.ts diff --git a/scripts/firebase-config.ts b/scripts/firebase-config.ts index c67644d2b..c861c7f42 100644 --- a/scripts/firebase-config.ts +++ b/scripts/firebase-config.ts @@ -43,6 +43,14 @@ const userCollections = { onboarding: 'user-onboarding-data', }; +const helperCollections = { + track : 'track-users', + courses : 'courses', + availableRostersForCourse : 'available-rosters-for-course', + crseIdToCatalogNbr : 'crseid-to-catalognbr', + courseFulfillmentStats : 'course-fulfillment-stats', +} + export const userCollectionNames = Object.values(userCollections); export const usernameCollection = db.collection(userCollections.name); @@ -52,7 +60,8 @@ export const overriddenFulfillmentChoicesCollection = db.collection(userCollecti export const subjectColorsCollection = db.collection(userCollections.colors); export const uniqueIncrementerCollection = db.collection(userCollections.unique); export const onboardingDataCollection = db.collection(userCollections.onboarding); -export const trackUsersCollection = db.collection('track-users'); -export const coursesCollection = db.collection('courses'); -export const availableRostersForCourseCollection = db.collection('available-rosters-for-course'); -export const crseIdToCatalogNbrCollection = db.collection('crseid-to-catalognbr'); +export const trackUsersCollection = db.collection(helperCollections.track); +export const coursesCollection = db.collection(helperCollections.courses); +export const availableRostersForCourseCollection = db.collection(helperCollections.availableRostersForCourse); +export const crseIdToCatalogNbrCollection = db.collection(helperCollections.crseIdToCatalogNbr); +export const courseFulfillmentStats = db.collection(helperCollections.courseFulfillmentStats); diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts index 124ee525b..825b79509 100644 --- a/scripts/gen-req-full-stats.ts +++ b/scripts/gen-req-full-stats.ts @@ -3,91 +3,98 @@ import { semestersCollection, toggleableRequirementChoicesCollection, overriddenFulfillmentChoicesCollection, + courseFulfillmentStats, } from './firebase-config'; import computeGroupedRequirementFulfillmentReports from '../src/requirements/requirement-frontend-computation'; +import { computeFulfillmentStats } from '../src/requirements/fulfillment-stats'; +import { createAppOnboardingData } from '../src/user-data-converter'; -import { RequirementFulfillment, CourseTaken } from '../src/requirement-types.d'; -const idRequirementFrequency = new Map[]>(); +import '../src/requirements/decorated-requirements.json'; +let idRequirementFrequency = new Map[]>(); +let counter = 0; /** * Prints to console.out the json string of frequencies each course take to * fulfill a given requirement */ -async function computeRequirementFullfillmentStatistics() { - await semestersCollection.get().then(semQuerySnapshot => { - // Query a user's semesters - semQuerySnapshot.forEach(async doc => { +async function computeRequirementFullfillmentStatistics(_callback) { + const semQuerySnapshot = await semestersCollection.get(); + semQuerySnapshot.forEach(async doc => { // obtain the user's semesters, onboarding data, etc... - const semestersAndPlans = doc.data(); - const onboardingData = await onboardingDataCollection.doc(doc.id).get(); - const toggleableRequirementChoices = await toggleableRequirementChoicesCollection - .doc(doc.id) - .get(); - const overriddenFulfillmentChoices = await overriddenFulfillmentChoicesCollection - .doc(doc.id) - .get(); + const semestersAndPlans = await doc.data(); + const onboardingData = (await onboardingDataCollection.doc(doc.id).get()).data(); + const toggleableRequirementChoices = (await toggleableRequirementChoicesCollection.doc(doc.id).get()).data() ?? {}; + const overriddenFulfillmentChoices = (await overriddenFulfillmentChoicesCollection.doc(doc.id).get()).data() ?? {}; - // For the given users semesters compute the requirements they fulfill - // and which course it took for them to fulfill them - const res = computeGroupedRequirementFulfillmentReports( - semestersAndPlans.semester, - onboardingData, - toggleableRequirementChoices, - overriddenFulfillmentChoices - ); - - res.groupedRequirementFulfillmentReport.forEach(currentGroup => { - const currentGroupReqs: RequirementFulfillment[] = currentGroup.reqs; - - // For a given group of requirements, we iterate through all courses - // that were needed to fulfill each slot - currentGroupReqs.forEach(reqFulfillment => { - const key: string = reqFulfillment.requirement.id; - - const fulfillment: readonly CourseTaken[][] = reqFulfillment.fulfillment - .safeCourses as readonly CourseTaken[][]; - - // Obtain the frequency list for this particular group's requirements - const freqList: Map[] = idRequirementFrequency.has(key) - ? (idRequirementFrequency.get(key) as Map[]) - : []; - - // Iterate over all slots in the requirement group - for (let slotNumber = 0; slotNumber < fulfillment.length; slotNumber += 1) { - if (freqList.length === slotNumber) { - freqList.push(new Map()); - } - const currentCourseSlot: CourseTaken[] = fulfillment[slotNumber]; - const currentRequirementSlotFreq = freqList[slotNumber]; - - // Iterate over all courses taken to fulfill the req-slot - for (let j = 0; j < currentCourseSlot.length; j += 1) { - const currentCourseId = currentCourseSlot[j].courseId; - - const pastFreq = currentRequirementSlotFreq.has(currentCourseId) - ? (currentRequirementSlotFreq.get(currentCourseId) as number) - : 0; - // Increase frequency each time a course is used to fulfill - // a specific requirement slot - currentRequirementSlotFreq.set(currentCourseId, pastFreq + 1); - } - } - }); - }); + + if (onboardingData !== undefined && + semestersAndPlans.semesters !== undefined && + toggleableRequirementChoices !== undefined && + overriddenFulfillmentChoices !== undefined) { // if the user has onboarding data + try { + const newOnboardingData = await createAppOnboardingData(onboardingData); // convert to new format + const res = await computeGroupedRequirementFulfillmentReports( // compute fulfillment stats + semestersAndPlans.semesters, + newOnboardingData, + toggleableRequirementChoices, + overriddenFulfillmentChoices + ); + idRequirementFrequency = await computeFulfillmentStats(res.groupedRequirementFulfillmentReport, idRequirementFrequency); // compute fulfillment stats + } catch { + console.log(counter, " : Error in computing fulfillment stats for user: " + doc.id); + counter++; + } + } }); - }); - // Serialize the large hashmap into a JSON object - const returnObject = {}; - for (const [key, value] of idRequirementFrequency) { - returnObject[key] = value.map(item => ({ ...item })); + setTimeout (_callback, 3600*1000); // wait for all the data to be processed +} + +async function storeComputedRequirementFullfillmentStatistics() { + console.log("Keeping only the top fifty courses for each slot"); + // iterate through all the keys in the idRequirementFrequency hashmap + for (let [reqID, slots] of idRequirementFrequency) { + let newSlots : Map[] = []; + for (let slot of slots) { + let newSlot = new Map(); + let sorted = [...slot.entries()].sort((a, b) => b[1] - a[1]); + let count = 0; + for (let [course, freq] of sorted) { + if (count === 50) { + break; + } + newSlot.set(course, freq); + count++; + } + newSlots.push(newSlot); + } + idRequirementFrequency.set(reqID, newSlots); } - // Turn the JSON object into a string to output to the console - const returnString = JSON.stringify(returnObject, null, 2); - console.log(returnString); + console.log("Storing fulfillment stats in firestore"); + // iterate through all the keys in the idRequirementFrequency hashmap + for (let [reqID, slots] of idRequirementFrequency) { + let json = {}; + + let i = 0; + for (let slot of slots) { + let tempJson = {}; + for (let [course, freq] of slot) { + tempJson[course] = freq; + } + json[i] = [tempJson]; + i++; + } + + const data = { + reqID: reqID, + slots: json, + } + await courseFulfillmentStats.doc().set(data); + } + } -computeRequirementFullfillmentStatistics(); +computeRequirementFullfillmentStatistics(storeComputedRequirementFullfillmentStatistics); diff --git a/src/requirement-types.d.ts b/src/requirement-types.d.ts index ee5d60601..a0eea0b02 100644 --- a/src/requirement-types.d.ts +++ b/src/requirement-types.d.ts @@ -101,7 +101,7 @@ type DecoratedCollegeOrMajorRequirement = RequirementCommon & * It's a significantly simplified version of FirestoreSemesterCourse to make it easy to mock for * the purpose of requirement computation. */ -export type CourseTaken = { +type CourseTaken = { /** The course ID from course roster, or our dummy id to denote special courses like FWS equiv. */ readonly courseId: number; /** Using the unique ID of firestore course for real course, string for swim test and AP/IB. */ @@ -154,7 +154,7 @@ type MixedRequirementFulfillmentStatisticsWithAdditionalRequirements = MixedRequ }; }; -export type RequirementFulfillment = { +type RequirementFulfillment = { /** The original requirement object. */ readonly requirement: RequirementWithIDSourceType; readonly fulfillment: MixedRequirementFulfillmentStatisticsWithAdditionalRequirements; diff --git a/src/requirements/fulfillment-stats.ts b/src/requirements/fulfillment-stats.ts new file mode 100644 index 000000000..85845e854 --- /dev/null +++ b/src/requirements/fulfillment-stats.ts @@ -0,0 +1,36 @@ +export function computeFulfillmentStats(groups : readonly GroupedRequirementFulfillmentReport[], idRequirementFrequency : Map[]>) { + let res = idRequirementFrequency; + groups.forEach(currentGroup => { + const { reqs } = currentGroup; + reqs.forEach(reqFulfillment => { + let current = []; + const key: string = reqFulfillment.requirement.id; + const { safeCourses } = reqFulfillment.fulfillment; + + // Obtain the frequency list for this particular group's requirements + let freqList = res.get(key) ?? []; + + // Iterate over all slots in the requirement group + // console.log(safeCourses.length); + for (let slotNumber = 0; slotNumber < safeCourses.length; slotNumber += 1) { + if (freqList.length === slotNumber) { + freqList.push(new Map()); + } + const currentCourseSlot = safeCourses[slotNumber]; + const currentRequirementSlotFreq = freqList[slotNumber]; + + // Iterate over all courses taken to fulfill the req-slot + for (let j = 0; j < currentCourseSlot.length; j += 1) { + const currentCourseId = currentCourseSlot[j].courseId; + const pastFreq = currentRequirementSlotFreq.get(currentCourseId) ?? 0; + currentRequirementSlotFreq.set(currentCourseId, pastFreq + 1); + } + freqList[slotNumber] = currentRequirementSlotFreq; // Update the frequency list + } + res.set(key, freqList); // Update the hashmap with the new frequency list + + }); + }); + + return res; // return the hashmap +} \ No newline at end of file From 6aab6fee1c66c045c2e23bc3cc042cc53d69690f Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Sun, 22 Oct 2023 18:27:15 -0400 Subject: [PATCH 5/9] Ran format --- package.json | 3 +- scripts/firebase-config.ts | 16 ++-- scripts/gen-req-full-stats.ts | 109 ++++++++++++++------------ src/requirements/fulfillment-stats.ts | 17 ++-- 4 files changed, 79 insertions(+), 66 deletions(-) diff --git a/package.json b/package.json index dd1bcdf91..7917d1059 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "test": "jest", "cypress:open": "cypress open", "cypress:run": "cypress run", - "cypress:e2e": "npm run build:dev && concurrently \"npm run serve:build\" \"wait-on http-get://localhost:8080 && npm run cypress:run\" --kill-others --success first" + "cypress:e2e": "npm run build:dev && concurrently \"npm run serve:build\" \"wait-on http-get://localhost:8080 && npm run cypress:run\" --kill-others --success first", + "all-tests" : "npm run lint && npm run type-check && npm run test && npm run format:check" }, "dependencies": { "@types/intro.js": "^3.0.0", diff --git a/scripts/firebase-config.ts b/scripts/firebase-config.ts index c861c7f42..46c1de8e5 100644 --- a/scripts/firebase-config.ts +++ b/scripts/firebase-config.ts @@ -44,12 +44,12 @@ const userCollections = { }; const helperCollections = { - track : 'track-users', - courses : 'courses', - availableRostersForCourse : 'available-rosters-for-course', - crseIdToCatalogNbr : 'crseid-to-catalognbr', - courseFulfillmentStats : 'course-fulfillment-stats', -} + track: 'track-users', + courses: 'courses', + availableRostersForCourse: 'available-rosters-for-course', + crseIdToCatalogNbr: 'crseid-to-catalognbr', + courseFulfillmentStats: 'course-fulfillment-stats', +}; export const userCollectionNames = Object.values(userCollections); @@ -62,6 +62,8 @@ export const uniqueIncrementerCollection = db.collection(userCollections.unique) export const onboardingDataCollection = db.collection(userCollections.onboarding); export const trackUsersCollection = db.collection(helperCollections.track); export const coursesCollection = db.collection(helperCollections.courses); -export const availableRostersForCourseCollection = db.collection(helperCollections.availableRostersForCourse); +export const availableRostersForCourseCollection = db.collection( + helperCollections.availableRostersForCourse +); export const crseIdToCatalogNbrCollection = db.collection(helperCollections.crseIdToCatalogNbr); export const courseFulfillmentStats = db.collection(helperCollections.courseFulfillmentStats); diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts index 825b79509..9f60db02e 100644 --- a/scripts/gen-req-full-stats.ts +++ b/scripts/gen-req-full-stats.ts @@ -7,10 +7,9 @@ import { } from './firebase-config'; import computeGroupedRequirementFulfillmentReports from '../src/requirements/requirement-frontend-computation'; -import { computeFulfillmentStats } from '../src/requirements/fulfillment-stats'; +import computeFulfillmentStats from '../src/requirements/fulfillment-stats'; import { createAppOnboardingData } from '../src/user-data-converter'; - import '../src/requirements/decorated-requirements.json'; let idRequirementFrequency = new Map[]>(); @@ -22,79 +21,89 @@ let counter = 0; async function computeRequirementFullfillmentStatistics(_callback) { const semQuerySnapshot = await semestersCollection.get(); semQuerySnapshot.forEach(async doc => { - // obtain the user's semesters, onboarding data, etc... - const semestersAndPlans = await doc.data(); - const onboardingData = (await onboardingDataCollection.doc(doc.id).get()).data(); - const toggleableRequirementChoices = (await toggleableRequirementChoicesCollection.doc(doc.id).get()).data() ?? {}; - const overriddenFulfillmentChoices = (await overriddenFulfillmentChoicesCollection.doc(doc.id).get()).data() ?? {}; + // obtain the user's semesters, onboarding data, etc... + const semestersAndPlans = await doc.data(); + const onboardingData = (await onboardingDataCollection.doc(doc.id).get()).data(); + const toggleableRequirementChoices = + (await toggleableRequirementChoicesCollection.doc(doc.id).get()).data() ?? {}; + const overriddenFulfillmentChoices = + (await overriddenFulfillmentChoicesCollection.doc(doc.id).get()).data() ?? {}; - - if (onboardingData !== undefined && - semestersAndPlans.semesters !== undefined && - toggleableRequirementChoices !== undefined && - overriddenFulfillmentChoices !== undefined) { // if the user has onboarding data - try { - const newOnboardingData = await createAppOnboardingData(onboardingData); // convert to new format - const res = await computeGroupedRequirementFulfillmentReports( // compute fulfillment stats - semestersAndPlans.semesters, - newOnboardingData, - toggleableRequirementChoices, - overriddenFulfillmentChoices - ); - idRequirementFrequency = await computeFulfillmentStats(res.groupedRequirementFulfillmentReport, idRequirementFrequency); // compute fulfillment stats - } catch { - console.log(counter, " : Error in computing fulfillment stats for user: " + doc.id); - counter++; - } - } - }); + if ( + onboardingData !== undefined && + semestersAndPlans.semesters !== undefined && + toggleableRequirementChoices !== undefined && + overriddenFulfillmentChoices !== undefined + ) { + // if the user has onboarding data + try { + const newOnboardingData = await createAppOnboardingData(onboardingData); // convert to new format + const res = await computeGroupedRequirementFulfillmentReports( + // compute fulfillment stats + semestersAndPlans.semesters, + newOnboardingData, + toggleableRequirementChoices, + overriddenFulfillmentChoices + ); + idRequirementFrequency = await computeFulfillmentStats( + res.groupedRequirementFulfillmentReport, + idRequirementFrequency + ); // compute fulfillment stats + } catch { + console.log(counter, ' : Error in computing fulfillment stats for user: ', doc.id); + counter += 1; + } + } + }); - setTimeout (_callback, 3600*1000); // wait for all the data to be processed + setTimeout(_callback, 3600 * 1000); // wait for all the data to be processed } +/** + * Stores the computed requirement fulfillment statistics in firestore + */ async function storeComputedRequirementFullfillmentStatistics() { - console.log("Keeping only the top fifty courses for each slot"); + console.log('Keeping only the top fifty courses for each slot'); // iterate through all the keys in the idRequirementFrequency hashmap - for (let [reqID, slots] of idRequirementFrequency) { - let newSlots : Map[] = []; - for (let slot of slots) { - let newSlot = new Map(); - let sorted = [...slot.entries()].sort((a, b) => b[1] - a[1]); + for (const [reqID, slots] of idRequirementFrequency) { + const newSlots: Map[] = []; + for (const slot of slots) { + const newSlot = new Map(); + const sorted = [...slot.entries()].sort((a, b) => b[1] - a[1]); let count = 0; - for (let [course, freq] of sorted) { + for (const [course, freq] of sorted) { if (count === 50) { break; } newSlot.set(course, freq); - count++; + count += 1; } newSlots.push(newSlot); } idRequirementFrequency.set(reqID, newSlots); } - console.log("Storing fulfillment stats in firestore"); + console.log('Storing fulfillment stats in firestore'); // iterate through all the keys in the idRequirementFrequency hashmap - for (let [reqID, slots] of idRequirementFrequency) { - let json = {}; - + for (const [reqID, slots] of idRequirementFrequency) { + const json = {}; + let i = 0; - for (let slot of slots) { - let tempJson = {}; - for (let [course, freq] of slot) { + for (const slot of slots) { + const tempJson = {}; + for (const [course, freq] of slot) { tempJson[course] = freq; } - json[i] = [tempJson]; - i++; + json[i] = tempJson; + i += 1; } - + const data = { - reqID: reqID, + reqID, slots: json, - } - await courseFulfillmentStats.doc().set(data); + }; + courseFulfillmentStats.doc().set(data); } - } computeRequirementFullfillmentStatistics(storeComputedRequirementFullfillmentStatistics); diff --git a/src/requirements/fulfillment-stats.ts b/src/requirements/fulfillment-stats.ts index 85845e854..64e5848d6 100644 --- a/src/requirements/fulfillment-stats.ts +++ b/src/requirements/fulfillment-stats.ts @@ -1,14 +1,16 @@ -export function computeFulfillmentStats(groups : readonly GroupedRequirementFulfillmentReport[], idRequirementFrequency : Map[]>) { - let res = idRequirementFrequency; +export default function computeFulfillmentStats( + groups: readonly GroupedRequirementFulfillmentReport[], + idRequirementFrequency: Map[]> +) { + const res = idRequirementFrequency; groups.forEach(currentGroup => { const { reqs } = currentGroup; reqs.forEach(reqFulfillment => { - let current = []; const key: string = reqFulfillment.requirement.id; const { safeCourses } = reqFulfillment.fulfillment; // Obtain the frequency list for this particular group's requirements - let freqList = res.get(key) ?? []; + const freqList = res.get(key) ?? []; // Iterate over all slots in the requirement group // console.log(safeCourses.length); @@ -23,14 +25,13 @@ export function computeFulfillmentStats(groups : readonly GroupedRequirementFulf for (let j = 0; j < currentCourseSlot.length; j += 1) { const currentCourseId = currentCourseSlot[j].courseId; const pastFreq = currentRequirementSlotFreq.get(currentCourseId) ?? 0; - currentRequirementSlotFreq.set(currentCourseId, pastFreq + 1); + currentRequirementSlotFreq.set(currentCourseId, pastFreq + 1); } freqList[slotNumber] = currentRequirementSlotFreq; // Update the frequency list } res.set(key, freqList); // Update the hashmap with the new frequency list - }); }); - + return res; // return the hashmap -} \ No newline at end of file +} From ccbfee3a8423dfa057d5e47591ea270db346f597 Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Sun, 22 Oct 2023 20:20:12 -0400 Subject: [PATCH 6/9] Adding documention and removing console.log --- scripts/gen-req-full-stats.ts | 60 +++++++++++++++++---------- src/requirements/fulfillment-stats.ts | 28 +++++++++++++ 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts index 9f60db02e..fde4201d2 100644 --- a/scripts/gen-req-full-stats.ts +++ b/scripts/gen-req-full-stats.ts @@ -12,11 +12,21 @@ import { createAppOnboardingData } from '../src/user-data-converter'; import '../src/requirements/decorated-requirements.json'; +// idRequirementFrequency is a hashmap where the key is the requirement ID and the value is +// an array of maps. Each element in the array represents a slot in the requirement. +// The map is a hashmap where the key is the course ID and the value is the frequency of the course +// in the slot. let idRequirementFrequency = new Map[]>(); -let counter = 0; /** - * Prints to console.out the json string of frequencies each course take to - * fulfill a given requirement + * Computes the requirement fulfillment statistics for all users. This is done by iterating through + * all the users and computing the computeGroupedRequirementFulfillmentReports for each user. + * This returns groupedRequirementFulfillmentReport which is then passed to computeFulfillmentStats. + * GroupedRequirementFulfillmentReport is a list of RequirementFulfillmentReport where each + * RequirementFulfillmentReport represents a requirement and a list of courses that fulfill the + * requirement. computeFulfillmentStats then computes the frequency of each course in each slot + * of the requirement and stores it in idRequirementFrequency. + * @param _callback + * @throws Error when computeGroupedRequirementFulfillmentReports fails to compute the fulfillment stats */ async function computeRequirementFullfillmentStatistics(_callback) { const semQuerySnapshot = await semestersCollection.get(); @@ -29,62 +39,67 @@ async function computeRequirementFullfillmentStatistics(_callback) { const overriddenFulfillmentChoices = (await overriddenFulfillmentChoicesCollection.doc(doc.id).get()).data() ?? {}; + // if the user has obboarding data, semesters, etc... if ( onboardingData !== undefined && semestersAndPlans.semesters !== undefined && toggleableRequirementChoices !== undefined && overriddenFulfillmentChoices !== undefined ) { - // if the user has onboarding data + // Attempt to compute the fulfillment stats for the user try { - const newOnboardingData = await createAppOnboardingData(onboardingData); // convert to new format + // use createAppOnboardingData to convert the onboarding data to the format used by the frontend + const newOnboardingData = await createAppOnboardingData(onboardingData); + + // compute the fulfillment stats const res = await computeGroupedRequirementFulfillmentReports( - // compute fulfillment stats semestersAndPlans.semesters, newOnboardingData, toggleableRequirementChoices, overriddenFulfillmentChoices ); + idRequirementFrequency = await computeFulfillmentStats( res.groupedRequirementFulfillmentReport, idRequirementFrequency - ); // compute fulfillment stats + ); } catch { - console.log(counter, ' : Error in computing fulfillment stats for user: ', doc.id); - counter += 1; + // There was an error computing the fulfillment stats for the user } } }); - setTimeout(_callback, 3600 * 1000); // wait for all the data to be processed + setTimeout(_callback, 120 * 1000); // wait 2 minutes before storing the computed stats } /** - * Stores the computed requirement fulfillment statistics in firestore + * Stores the computed requirement fulfillment statistics in firestore. This is done by iterating + * through all the keys in the idRequirementFrequency hashmap and storing the fulfillment stats + * for each requirement in firestore. We have to keep only the top fifty courses for each slot + * to reduce the size of the data stored in firestore. This is done by sorting the hashmap by + * frequency and keeping only the top fifty courses for each slot. + * @throws Error when courseFulfillmentStats.doc().set(data) fails to store the data in firestore */ async function storeComputedRequirementFullfillmentStatistics() { - console.log('Keeping only the top fifty courses for each slot'); - // iterate through all the keys in the idRequirementFrequency hashmap + // Change the hashmap to only keep the top fifty courses for each slot + for (const [reqID, slots] of idRequirementFrequency) { const newSlots: Map[] = []; for (const slot of slots) { const newSlot = new Map(); const sorted = [...slot.entries()].sort((a, b) => b[1] - a[1]); - let count = 0; - for (const [course, freq] of sorted) { - if (count === 50) { - break; - } + + for (let i = 0; i < 50; i += 1) { + const [course, freq] = sorted[i]; newSlot.set(course, freq); - count += 1; } + newSlots.push(newSlot); } idRequirementFrequency.set(reqID, newSlots); } - console.log('Storing fulfillment stats in firestore'); - // iterate through all the keys in the idRequirementFrequency hashmap + // Storing fulfillment stats in firestore by iterating through the hashmap for (const [reqID, slots] of idRequirementFrequency) { const json = {}; @@ -102,8 +117,9 @@ async function storeComputedRequirementFullfillmentStatistics() { reqID, slots: json, }; - courseFulfillmentStats.doc().set(data); + courseFulfillmentStats.doc().set(data); // store the data in firestore } } +// Run the script computeRequirementFullfillmentStatistics(storeComputedRequirementFullfillmentStatistics); diff --git a/src/requirements/fulfillment-stats.ts b/src/requirements/fulfillment-stats.ts index 64e5848d6..5b47fe01c 100644 --- a/src/requirements/fulfillment-stats.ts +++ b/src/requirements/fulfillment-stats.ts @@ -1,11 +1,39 @@ +/** + * @brief This function computes the frequency of courses taken to fulfill a requirement + * + * @param groups A list of requirement groups containing the courses taken to fulfill the requirements + * @param idRequirementFrequency A hashmap of requirement id to a list of frequency maps + * @returns A hashmap of requirement id to a list of frequency maps + * + * @details + * This function computes the frequency of courses taken to fulfill a requirement. + * The hashmap is of the form: requirement id -> list of frequency maps + * The list of frequency maps is of the form: slot number -> course id -> frequency + * + * @note + * The hashmap is passed in as a parameter to avoid creating a new hashmap every time this function is called. + * This function is called multiple times in the main algorithm. + * + * @warning + * This function assumes that the hashmap passed in is empty. + * + * @bug + * None known + * + * @todo + * None + */ export default function computeFulfillmentStats( groups: readonly GroupedRequirementFulfillmentReport[], idRequirementFrequency: Map[]> ) { + // Iterate over all groups const res = idRequirementFrequency; groups.forEach(currentGroup => { + // Iterate over all requirements in the group const { reqs } = currentGroup; reqs.forEach(reqFulfillment => { + // Obtain the requirement ID and the list of courses taken to fulfill the requirement const key: string = reqFulfillment.requirement.id; const { safeCourses } = reqFulfillment.fulfillment; From 7b5ba5cea9445e57443b9b0683425d445995202b Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Sun, 22 Oct 2023 20:22:58 -0400 Subject: [PATCH 7/9] Ran format --- scripts/gen-req-full-stats.ts | 10 +++++----- src/requirements/fulfillment-stats.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts index fde4201d2..b655ca343 100644 --- a/scripts/gen-req-full-stats.ts +++ b/scripts/gen-req-full-stats.ts @@ -26,7 +26,7 @@ let idRequirementFrequency = new Map[]>(); * requirement. computeFulfillmentStats then computes the frequency of each course in each slot * of the requirement and stores it in idRequirementFrequency. * @param _callback - * @throws Error when computeGroupedRequirementFulfillmentReports fails to compute the fulfillment stats + * @throws Error when computeGroupedRequirementFulfillmentReports fails to compute the fulfillment stats */ async function computeRequirementFullfillmentStatistics(_callback) { const semQuerySnapshot = await semestersCollection.get(); @@ -50,7 +50,7 @@ async function computeRequirementFullfillmentStatistics(_callback) { try { // use createAppOnboardingData to convert the onboarding data to the format used by the frontend const newOnboardingData = await createAppOnboardingData(onboardingData); - + // compute the fulfillment stats const res = await computeGroupedRequirementFulfillmentReports( semestersAndPlans.semesters, @@ -82,18 +82,18 @@ async function computeRequirementFullfillmentStatistics(_callback) { */ async function storeComputedRequirementFullfillmentStatistics() { // Change the hashmap to only keep the top fifty courses for each slot - + for (const [reqID, slots] of idRequirementFrequency) { const newSlots: Map[] = []; for (const slot of slots) { const newSlot = new Map(); const sorted = [...slot.entries()].sort((a, b) => b[1] - a[1]); - + for (let i = 0; i < 50; i += 1) { const [course, freq] = sorted[i]; newSlot.set(course, freq); } - + newSlots.push(newSlot); } idRequirementFrequency.set(reqID, newSlots); diff --git a/src/requirements/fulfillment-stats.ts b/src/requirements/fulfillment-stats.ts index 5b47fe01c..3571c6f87 100644 --- a/src/requirements/fulfillment-stats.ts +++ b/src/requirements/fulfillment-stats.ts @@ -1,25 +1,25 @@ /** * @brief This function computes the frequency of courses taken to fulfill a requirement - * + * * @param groups A list of requirement groups containing the courses taken to fulfill the requirements * @param idRequirementFrequency A hashmap of requirement id to a list of frequency maps * @returns A hashmap of requirement id to a list of frequency maps - * + * * @details * This function computes the frequency of courses taken to fulfill a requirement. * The hashmap is of the form: requirement id -> list of frequency maps * The list of frequency maps is of the form: slot number -> course id -> frequency - * + * * @note * The hashmap is passed in as a parameter to avoid creating a new hashmap every time this function is called. * This function is called multiple times in the main algorithm. - * + * * @warning * This function assumes that the hashmap passed in is empty. - * + * * @bug * None known - * + * * @todo * None */ From a8a5630fab3bc366804ebfce9d57704571dd2e92 Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Sun, 5 Nov 2023 11:01:46 -0500 Subject: [PATCH 8/9] Cleaning code and replacing backslash with doc id --- scripts/gen-req-full-stats.ts | 82 ++++++++++++--------------- src/requirements/fulfillment-stats.ts | 19 +------ 2 files changed, 39 insertions(+), 62 deletions(-) diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts index b655ca343..602498f69 100644 --- a/scripts/gen-req-full-stats.ts +++ b/scripts/gen-req-full-stats.ts @@ -16,7 +16,7 @@ import '../src/requirements/decorated-requirements.json'; // an array of maps. Each element in the array represents a slot in the requirement. // The map is a hashmap where the key is the course ID and the value is the frequency of the course // in the slot. -let idRequirementFrequency = new Map[]>(); +const idRequirementFrequency = new Map[]>(); /** * Computes the requirement fulfillment statistics for all users. This is done by iterating through * all the users and computing the computeGroupedRequirementFulfillmentReports for each user. @@ -25,51 +25,46 @@ let idRequirementFrequency = new Map[]>(); * RequirementFulfillmentReport represents a requirement and a list of courses that fulfill the * requirement. computeFulfillmentStats then computes the frequency of each course in each slot * of the requirement and stores it in idRequirementFrequency. - * @param _callback + * @param _callback is a function that is called after the fulfillment stats have been computed * @throws Error when computeGroupedRequirementFulfillmentReports fails to compute the fulfillment stats */ async function computeRequirementFullfillmentStatistics(_callback) { + let numberOfErrors = 0; const semQuerySnapshot = await semestersCollection.get(); - semQuerySnapshot.forEach(async doc => { + await semQuerySnapshot.forEach(async doc => { // obtain the user's semesters, onboarding data, etc... - const semestersAndPlans = await doc.data(); - const onboardingData = (await onboardingDataCollection.doc(doc.id).get()).data(); + const semesters = (await doc.data()).semesters ?? {}; + const onboardingData = (await onboardingDataCollection.doc(doc.id).get()).data() ?? {}; const toggleableRequirementChoices = (await toggleableRequirementChoicesCollection.doc(doc.id).get()).data() ?? {}; const overriddenFulfillmentChoices = (await overriddenFulfillmentChoicesCollection.doc(doc.id).get()).data() ?? {}; - // if the user has obboarding data, semesters, etc... - if ( - onboardingData !== undefined && - semestersAndPlans.semesters !== undefined && - toggleableRequirementChoices !== undefined && - overriddenFulfillmentChoices !== undefined - ) { - // Attempt to compute the fulfillment stats for the user - try { - // use createAppOnboardingData to convert the onboarding data to the format used by the frontend - const newOnboardingData = await createAppOnboardingData(onboardingData); + // Attempt to compute the fulfillment stats for the user + try { + // use createAppOnboardingData to convert the onboarding data to the format used by the frontend + const newOnboardingData = await createAppOnboardingData(onboardingData); - // compute the fulfillment stats - const res = await computeGroupedRequirementFulfillmentReports( - semestersAndPlans.semesters, - newOnboardingData, - toggleableRequirementChoices, - overriddenFulfillmentChoices - ); + // compute the fulfillment stats + const res = await computeGroupedRequirementFulfillmentReports( + semesters, + newOnboardingData, + toggleableRequirementChoices, + overriddenFulfillmentChoices + ); - idRequirementFrequency = await computeFulfillmentStats( - res.groupedRequirementFulfillmentReport, - idRequirementFrequency - ); - } catch { - // There was an error computing the fulfillment stats for the user - } + await computeFulfillmentStats( + res.groupedRequirementFulfillmentReport, + idRequirementFrequency + ); + } catch { + // There was an error computing the fulfillment stats for the user + console.log(`${numberOfErrors} : Error computing fulfillment stats for ${doc.id}`); + numberOfErrors += 1; } }); - setTimeout(_callback, 120 * 1000); // wait 2 minutes before storing the computed stats + setTimeout(_callback, 120 * 1000); } /** @@ -89,7 +84,8 @@ async function storeComputedRequirementFullfillmentStatistics() { const newSlot = new Map(); const sorted = [...slot.entries()].sort((a, b) => b[1] - a[1]); - for (let i = 0; i < 50; i += 1) { + const numberOfCourses = sorted.length > 50 ? 50 : sorted.length; + for (let i = 0; i < numberOfCourses; i += 1) { const [course, freq] = sorted[i]; newSlot.set(course, freq); } @@ -101,23 +97,17 @@ async function storeComputedRequirementFullfillmentStatistics() { // Storing fulfillment stats in firestore by iterating through the hashmap for (const [reqID, slots] of idRequirementFrequency) { - const json = {}; - - let i = 0; - for (const slot of slots) { - const tempJson = {}; + const reqFrequenciesJson = {}; + for (let i = 0; i < slots.length; i += 1) { + const slot = slots[i]; + const slotFrequenciesJson = {}; for (const [course, freq] of slot) { - tempJson[course] = freq; + slotFrequenciesJson[course] = freq; } - json[i] = tempJson; - i += 1; + reqFrequenciesJson[i] = slotFrequenciesJson; } - - const data = { - reqID, - slots: json, - }; - courseFulfillmentStats.doc().set(data); // store the data in firestore + const ID = reqID.replace('/', '[FORWARD_SLASH]'); + courseFulfillmentStats.doc(ID).set(reqFrequenciesJson); // store the data in firestore } } diff --git a/src/requirements/fulfillment-stats.ts b/src/requirements/fulfillment-stats.ts index 3571c6f87..d6bec18a4 100644 --- a/src/requirements/fulfillment-stats.ts +++ b/src/requirements/fulfillment-stats.ts @@ -3,7 +3,6 @@ * * @param groups A list of requirement groups containing the courses taken to fulfill the requirements * @param idRequirementFrequency A hashmap of requirement id to a list of frequency maps - * @returns A hashmap of requirement id to a list of frequency maps * * @details * This function computes the frequency of courses taken to fulfill a requirement. @@ -13,22 +12,12 @@ * @note * The hashmap is passed in as a parameter to avoid creating a new hashmap every time this function is called. * This function is called multiple times in the main algorithm. - * - * @warning - * This function assumes that the hashmap passed in is empty. - * - * @bug - * None known - * - * @todo - * None */ export default function computeFulfillmentStats( groups: readonly GroupedRequirementFulfillmentReport[], idRequirementFrequency: Map[]> ) { // Iterate over all groups - const res = idRequirementFrequency; groups.forEach(currentGroup => { // Iterate over all requirements in the group const { reqs } = currentGroup; @@ -38,13 +27,13 @@ export default function computeFulfillmentStats( const { safeCourses } = reqFulfillment.fulfillment; // Obtain the frequency list for this particular group's requirements - const freqList = res.get(key) ?? []; + const freqList = idRequirementFrequency.get(key) ?? []; // Iterate over all slots in the requirement group // console.log(safeCourses.length); for (let slotNumber = 0; slotNumber < safeCourses.length; slotNumber += 1) { if (freqList.length === slotNumber) { - freqList.push(new Map()); + freqList.push(new Map()); } const currentCourseSlot = safeCourses[slotNumber]; const currentRequirementSlotFreq = freqList[slotNumber]; @@ -57,9 +46,7 @@ export default function computeFulfillmentStats( } freqList[slotNumber] = currentRequirementSlotFreq; // Update the frequency list } - res.set(key, freqList); // Update the hashmap with the new frequency list + idRequirementFrequency.set(key, freqList); // Update the hashmap with the new frequency list }); }); - - return res; // return the hashmap } From 95acbc130b1a8780229158aca35f3c831055e47b Mon Sep 17 00:00:00 2001 From: Pablo Raigoza Date: Tue, 28 Nov 2023 13:10:31 -0500 Subject: [PATCH 9/9] Resolving all promises before callback --- scripts/gen-req-full-stats.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/gen-req-full-stats.ts b/scripts/gen-req-full-stats.ts index 602498f69..3a839165c 100644 --- a/scripts/gen-req-full-stats.ts +++ b/scripts/gen-req-full-stats.ts @@ -17,6 +17,7 @@ import '../src/requirements/decorated-requirements.json'; // The map is a hashmap where the key is the course ID and the value is the frequency of the course // in the slot. const idRequirementFrequency = new Map[]>(); + /** * Computes the requirement fulfillment statistics for all users. This is done by iterating through * all the users and computing the computeGroupedRequirementFulfillmentReports for each user. @@ -31,7 +32,7 @@ const idRequirementFrequency = new Map[]>(); async function computeRequirementFullfillmentStatistics(_callback) { let numberOfErrors = 0; const semQuerySnapshot = await semestersCollection.get(); - await semQuerySnapshot.forEach(async doc => { + const promises = semQuerySnapshot.docs.map(async doc => { // obtain the user's semesters, onboarding data, etc... const semesters = (await doc.data()).semesters ?? {}; const onboardingData = (await onboardingDataCollection.doc(doc.id).get()).data() ?? {}; @@ -64,7 +65,8 @@ async function computeRequirementFullfillmentStatistics(_callback) { } }); - setTimeout(_callback, 120 * 1000); + await Promise.all(promises); + _callback(); } /** @@ -77,7 +79,6 @@ async function computeRequirementFullfillmentStatistics(_callback) { */ async function storeComputedRequirementFullfillmentStatistics() { // Change the hashmap to only keep the top fifty courses for each slot - for (const [reqID, slots] of idRequirementFrequency) { const newSlots: Map[] = []; for (const slot of slots) {