Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculating and Storing Requirement Statistics #866

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
19 changes: 15 additions & 4 deletions scripts/firebase-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -52,7 +60,10 @@ 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);
109 changes: 109 additions & 0 deletions scripts/gen-req-full-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
onboardingDataCollection,
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 '../src/requirements/decorated-requirements.json';

let idRequirementFrequency = new Map<string, Map<number, number>[]>();
let counter = 0;
/**
* Prints to console.out the json string of frequencies each course take to
* fulfill a given requirement
*/
async function computeRequirementFullfillmentStatistics(_callback) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this _callback argument?

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() ?? {};

if (
onboardingData !== undefined &&
semestersAndPlans.semesters !== undefined &&
toggleableRequirementChoices !== undefined &&
overriddenFulfillmentChoices !== undefined
PabloRaigoza marked this conversation as resolved.
Show resolved Hide resolved
) {
// 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);

Check warning on line 53 in scripts/gen-req-full-stats.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
PabloRaigoza marked this conversation as resolved.
Show resolved Hide resolved
counter += 1;
}
}
});

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');

Check warning on line 66 in scripts/gen-req-full-stats.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
PabloRaigoza marked this conversation as resolved.
Show resolved Hide resolved
// iterate through all the keys in the idRequirementFrequency hashmap
for (const [reqID, slots] of idRequirementFrequency) {
const newSlots: Map<number, number>[] = [];
for (const slot of slots) {
const newSlot = new Map<number, number>();
const sorted = [...slot.entries()].sort((a, b) => b[1] - a[1]);
let count = 0;
for (const [course, freq] of sorted) {
PabloRaigoza marked this conversation as resolved.
Show resolved Hide resolved
if (count === 50) {
break;
}
newSlot.set(course, freq);
count += 1;
}
newSlots.push(newSlot);
}
idRequirementFrequency.set(reqID, newSlots);
}

console.log('Storing fulfillment stats in firestore');

Check warning on line 86 in scripts/gen-req-full-stats.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
PabloRaigoza marked this conversation as resolved.
Show resolved Hide resolved
// iterate through all the keys in the idRequirementFrequency hashmap
for (const [reqID, slots] of idRequirementFrequency) {
const json = {};

let i = 0;
for (const slot of slots) {
const tempJson = {};
for (const [course, freq] of slot) {
tempJson[course] = freq;
}
json[i] = tempJson;
i += 1;
}
PabloRaigoza marked this conversation as resolved.
Show resolved Hide resolved

const data = {
reqID,
slots: json,
};
courseFulfillmentStats.doc().set(data);
}
}

computeRequirementFullfillmentStatistics(storeComputedRequirementFullfillmentStatistics);
37 changes: 37 additions & 0 deletions src/requirements/fulfillment-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export default function computeFulfillmentStats(
groups: readonly GroupedRequirementFulfillmentReport[],
idRequirementFrequency: Map<string, Map<number, number>[]>
) {
const res = idRequirementFrequency;
PabloRaigoza marked this conversation as resolved.
Show resolved Hide resolved
groups.forEach(currentGroup => {
const { reqs } = currentGroup;
reqs.forEach(reqFulfillment => {
const key: string = reqFulfillment.requirement.id;
const { safeCourses } = reqFulfillment.fulfillment;

// Obtain the frequency list for this particular group's requirements
const 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<number, number>());
PabloRaigoza marked this conversation as resolved.
Show resolved Hide resolved
}
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);
}
Comment on lines +42 to +46
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you convert this into a for each loop?

freqList[slotNumber] = currentRequirementSlotFreq; // Update the frequency list
}
res.set(key, freqList); // Update the hashmap with the new frequency list
});
});

return res; // return the hashmap
}
Loading