Skip to content

Commit

Permalink
feat: Grade distributions
Browse files Browse the repository at this point in the history
  • Loading branch information
mathhulk committed Oct 24, 2024
1 parent f0d5acb commit 4a9b064
Show file tree
Hide file tree
Showing 21 changed files with 6,582 additions and 4,383 deletions.
7 changes: 6 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"build": "tsc --noEmit",
"lint": "eslint --ext .tsx,.ts src/ --report-unused-disable-directives",
"generate": "graphql-codegen --config codegen.ts",
"update:catalog": "tsx ./src/scripts/update-catalog.ts"
"update:catalog": "tsx ./src/scripts/update-catalog.ts",
"update:grades": "tsx --env-file=.env ./src/scripts/update-grade-distributions.ts"
},
"devDependencies": {
"@babel/core": "^7.24.9",
Expand All @@ -33,12 +34,15 @@
"@apollo/server": "^4.10.5",
"@apollo/server-plugin-response-cache": "^4.1.3",
"@apollo/utils.keyvadapter": "^3.1.0",
"@aws-sdk/client-athena": "^3.679.0",
"@aws-sdk/client-s3": "^3.679.0",
"@escape.tech/graphql-armor": "^3.0.1",
"@graphql-tools/schema": "^10.0.4",
"@graphql-tools/utils": "^10.3.2",
"@keyv/redis": "^3.0.1",
"@repo/common": "*",
"@repo/sis-api": "*",
"@types/papaparse": "^5.3.15",
"compression": "^1.7.4",
"connect-redis": "^7.1.1",
"cors": "^2.8.5",
Expand All @@ -53,6 +57,7 @@
"lodash": "^4.17.21",
"mongodb": "^6.8.0",
"mongoose": "^8.5.1",
"papaparse": "^5.4.1",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"redis": "^4.6.15"
Expand Down
6 changes: 3 additions & 3 deletions apps/backend/src/modules/catalog/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { getCourseKey, getCsCourseId } from "../../utils/course";
import { getChildren } from "../../utils/graphql";
import { formatClass, formatSection } from "../class/formatter";
import { formatCourse } from "../course/formatter";
import { getAverage } from "../grade/controller";
import { CatalogModule } from "./generated-types/module-types";

function matchCsCourseId(id: any) {
Expand All @@ -25,6 +24,7 @@ function matchCsCourseId(id: any) {
};
}

// TODO: Grade distributions
export const getCatalog = async (
year: number,
semester: string,
Expand Down Expand Up @@ -91,7 +91,7 @@ export const getCatalog = async (
const catalog: any = {};

for (const c of courses) {
const key = getCourseKey(c);
// const key = getCourseKey(c);
const id = getCsCourseId(c);

// skip duplicates
Expand All @@ -100,7 +100,7 @@ export const getCatalog = async (
catalog[id] = {
...formatCourse(c),
classes: [],
gradeAverage: getAverage(gradesMap[key]),
gradeAverage: 0, // getAverage(gradesMap[key]),
};
}

Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/modules/class/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type IntermediateClass = Omit<
term: null;
primarySection: null;
sections: null;
gradeDistribution: null;
};

export const formatDate = (date?: string | number | Date | null) => {
Expand All @@ -38,6 +39,7 @@ export const formatClass = (_class: ClassType) => {
session: _class.session?.id as string,

course: null,
gradeDistribution: null,
term: null,
primarySection: null,
sections: null,
Expand Down
17 changes: 17 additions & 0 deletions apps/backend/src/modules/class/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getCourse } from "../course/controller";
import { CourseModule } from "../course/generated-types/module-types";
import { getGradeDistributionByClass } from "../grade-distribution/controller";
import { getTerm } from "../term/controller";
import { TermModule } from "../term/generated-types/module-types";
import {
Expand Down Expand Up @@ -82,6 +83,22 @@ const resolvers: ClassModule.Resolvers = {

return secondarySections as unknown as ClassModule.Section[];
},

gradeDistribution: async (
parent: IntermediateClass | ClassModule.Class
) => {
if (parent.gradeDistribution) return parent.gradeDistribution;

const gradeDistribution = await getGradeDistributionByClass(
parent.subject,
parent.courseNumber,
parent.number,
parent.year,
parent.semester
);

return gradeDistribution;
},
},

Section: {
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/modules/class/typedefs/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default gql`
primarySection: Section!
sections: [Section!]!
term: Term!
gradeDistribution: GradeDistribution!
"Attributes"
gradingBasis: ClassGradingBasis!
Expand Down
102 changes: 37 additions & 65 deletions apps/backend/src/modules/course/controller.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import { GraphQLResolveInfo } from "graphql";
import { ClassModel, CourseModel } from "@repo/common";

import { ClassModel, CourseModel, GradeModel, GradeType } from "@repo/common";

import { getCourseKey } from "../../utils/course";
import { getChildren } from "../../utils/graphql";
import { formatClass } from "../class/formatter";
import { getAverage } from "../grade/controller";
import { IntermediateCourse, formatCourse } from "./formatter";

export const getCourse = async (
subject: string,
number: string,
info?: GraphQLResolveInfo | null
) => {
export const getCourse = async (subject: string, number: string) => {
const course = await CourseModel.findOne({
"subjectArea.code": subject,
"catalogNumber.formatted": number,
Expand All @@ -22,27 +13,7 @@ export const getCourse = async (

if (!course) return null;

const formattedCourse = formatCourse(course);

if (info && getChildren(info).includes("gradeAverage")) {
const grades: GradeType[] = [];

const gradesQuery = await GradeModel.find(
{
CourseSubjectShortNm: course?.subjectArea?.description,
CourseNumber: course?.catalogNumber?.formatted,
},
{ GradeNm: 1, EnrollmentCnt: 1 }
);

for (const grade of gradesQuery) {
grades.push(grade);
}

formattedCourse.gradeAverage = getAverage(grades);
}

return formattedCourse;
return formatCourse(course);
};

export const getClassesByCourse = async (
Expand Down Expand Up @@ -92,7 +63,8 @@ export const getAssociatedCourses = async (courses: string[]) => {
);
};

export const getCourses = async (info: GraphQLResolveInfo) => {
// TODO: Grade distributions
export const getCourses = async () => {
const courses = await CourseModel.aggregate([
{
$match: {
Expand All @@ -119,40 +91,40 @@ export const getCourses = async (info: GraphQLResolveInfo) => {
},
]);

/* Map grades to course keys for easy lookup */
const gradesMap: { [key: string]: GradeType[] } = {};
courses.forEach((c) => (gradesMap[getCourseKey(c)] = []));

const children = getChildren(info);

if (children.includes("gradeAverage")) {
const grades = await GradeModel.find(
{
/*
No filters because an appropriately large filter
is actually significantly slower than no filter.
*/
},
{
CourseSubjectShortNm: 1,
CourseNumber: 1,
GradeNm: 1,
EnrollmentCnt: 1,
}
).lean();

for (const g of grades) {
const key = `${g.CourseSubjectShortNm as string} ${
g.CourseNumber as string
}`;
if (key in gradesMap) {
gradesMap[key].push(g);
}
}
}
// /* Map grades to course keys for easy lookup */
// const gradesMap: { [key: string]: GradeType[] } = {};
// courses.forEach((c) => (gradesMap[getCourseKey(c)] = []));

// const children = getChildren(info);

// if (children.includes("gradeAverage")) {
// const grades = await GradeModel.find(
// {
// /*
// No filters because an appropriately large filter
// is actually significantly slower than no filter.
// */
// },
// {
// CourseSubjectShortNm: 1,
// CourseNumber: 1,
// GradeNm: 1,
// EnrollmentCnt: 1,
// }
// ).lean();

// for (const g of grades) {
// const key = `${g.CourseSubjectShortNm as string} ${
// g.CourseNumber as string
// }`;
// if (key in gradesMap) {
// gradesMap[key].push(g);
// }
// }
// }

return courses.map((c) => ({
...formatCourse(c),
gradeAverage: getAverage(gradesMap[getCourseKey(c)]),
gradeAverage: 0, //getAverage(gradesMap[getCourseKey(c)]),
})) as IntermediateCourse[];
};
4 changes: 3 additions & 1 deletion apps/backend/src/modules/course/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@ import { CourseModule } from "./generated-types/module-types";

export type IntermediateCourse = Omit<
CourseModule.Course,
"classes" | "crossListing" | "requiredCourses"
"classes" | "crossListing" | "requiredCourses" | "gradeDistribution"
> & {
classes: null;
crossListing: string[];
requiredCourses: string[];
gradeDistribution: null;
};

export function formatCourse(course: CourseType) {
return {
subject: course.subjectArea?.code as string,
number: course.catalogNumber?.formatted as string,

gradeDistribution: null,
classes: null,
crossListing: course.crossListing?.courses ?? [],
requiredCourses:
Expand Down
22 changes: 18 additions & 4 deletions apps/backend/src/modules/course/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getGradeDistributionByCourse } from "../grade-distribution/controller";
import {
getAssociatedCourses,
getClassesByCourse,
Expand All @@ -9,14 +10,14 @@ import { CourseModule } from "./generated-types/module-types";

const resolvers: CourseModule.Resolvers = {
Query: {
course: async (_, { subject, number }, _context, info) => {
const course = await getCourse(subject, number, info);
course: async (_, { subject, number }, _context, _info) => {
const course = await getCourse(subject, number);

return course as unknown as CourseModule.Course;
},

courses: async (_, _arguments, _context, info) => {
const courses = getCourses(info);
courses: async (_, _arguments, _context, _info) => {
const courses = getCourses();

return courses as unknown as CourseModule.Course[];
},
Expand Down Expand Up @@ -60,6 +61,19 @@ const resolvers: CourseModule.Resolvers = {

return associatedCourses as unknown as CourseModule.Course[];
},

gradeDistribution: async (
parent: IntermediateCourse | CourseModule.Course
) => {
if (parent.gradeDistribution) return parent.gradeDistribution;

const gradeDistribution = await getGradeDistributionByCourse(
parent.subject,
parent.number
);

return gradeDistribution;
},
},

// @ts-expect-error - Not sure how to type properly
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/modules/course/typedefs/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default gql`
requirements: String
description: String!
fromDate: String!
gradeAverage: Float
gradeDistribution: GradeDistribution!
gradingBasis: CourseGradingBasis!
finalExam: CourseFinalExam
academicCareer: AcademicCareer!
Expand Down
Loading

0 comments on commit 4a9b064

Please sign in to comment.