From 149aa358688bc2b79ca077c85c0e2ab4a235b497 Mon Sep 17 00:00:00 2001 From: Eemon Qayumi Date: Thu, 10 Apr 2025 06:08:00 -0700 Subject: [PATCH 1/2] Created new database queries, and test file --- api/lib/redisHelper.mjs | 118 +++++++++++++++++++++++++++++++++- api/testAssignmentQueries.mjs | 44 +++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 api/testAssignmentQueries.mjs diff --git a/api/lib/redisHelper.mjs b/api/lib/redisHelper.mjs index aa7ecda..190c40b 100644 --- a/api/lib/redisHelper.mjs +++ b/api/lib/redisHelper.mjs @@ -155,4 +155,120 @@ export async function getStudents() { await client.quit(); return students; -} \ No newline at end of file +} + +/** + * Gets the average score for a specific assignment across all students. + * @param {string} section - The category of the assignment (e.g., "Projects", "Labs"). + * @param {string} assignmentName - The name of the assignment. + * @returns {number|null} The average score, or null if no valid scores are found. + */ +export async function getAverageAssignmentScore(section, assignmentName) { + const students = await getStudents(); + let totalScore = 0; + let count = 0; + + const scores = await Promise.all(students.map(async ([, email]) => { + const student = await getStudentScores(email); + const score = student?.[section]?.[assignmentName]; + return (score != null && score !== "" && !isNaN(score)) ? +score : null; + })); + + for (const score of scores) { + if (score !== null) { + totalScore += score; + count++; + } + } + + return count > 0 ? totalScore / count : null; +} + +/** + * Gets the maximum score for a specific assignment across all students. + * @param {string} section - The category of the assignment. + * @param {string} assignmentName - The name of the assignment. + * @returns {number|null} The highest score found, or null if no scores are found. + */ +export async function getMaxAssignmentScore(section, assignmentName) { + const students = await getStudents(); + + const scores = await Promise.all(students.map(async ([, email]) => { + const student = await getStudentScores(email); + const score = student?.[section]?.[assignmentName]; + return (score != null && score !== "" && !isNaN(score)) ? +score : null; + })); + + const valid = scores.filter(score => score !== null); + return valid.length > 0 ? Math.max(...valid) : null; +} + +/** + * Gets the minimum (non-N/A) score for a specific assignment. + * @param {string} section - The category of the assignment. + * @param {string} assignmentName - The name of the assignment. + * @returns {number|null} The lowest valid score, or null if no valid scores found. + */ +export async function getMinAssignmentScore(section, assignmentName) { + const students = await getStudents(); + + const scores = await Promise.all(students.map(async ([, email]) => { + const student = await getStudentScores(email); + const score = student?.[section]?.[assignmentName]; + return (score != null && score !== "" && !isNaN(score)) ? +score : null; + })); + + const validScores = scores.filter(score => score !== null); + return validScores.length > 0 ? Math.min(...validScores) : null; +} + +/** + * Gets the top K students by score for a specific assignment. + * @param {string} section - The category of the assignment. + * @param {string} assignmentName - The name of the assignment. + * @param {number} k - The number of top students to return. + * @returns {Array} Array of student objects { name, email, score }. + */ +export async function getTopKAssignmentScores(section, assignmentName, k) { + const students = await getStudents(); + + const scored = await Promise.all(students.map(async ([name, email]) => { + const student = await getStudentScores(email); + const score = student?.[section]?.[assignmentName]; + return (score != null && score !== "" && !isNaN(score)) ? + { name, email, score: +score } : null; + })); + + return scored + .filter(Boolean) + .sort((a, b) => b.score - a.score) + .slice(0, k); +} + +/** + * Gets the top K students by total score across all assignments. + * @param {number} k - The number of top students to return. + * @returns {Array} Array of student objects { name, email, total }. + */ +export async function getTopKTotalScores(k) { + const students = await getStudents(); + + const totals = await Promise.all(students.map(async ([name, email]) => { + const scores = await getStudentScores(email); + let total = 0; + + for (const category of Object.values(scores)) { + for (const score of Object.values(category)) { + if (score != null && score !== "" && !isNaN(score)) { + total += +score; + } + } + } + + return { name, email, total }; + })); + + return totals + .sort((a, b) => b.total - a.total) + .slice(0, k); +} diff --git a/api/testAssignmentQueries.mjs b/api/testAssignmentQueries.mjs new file mode 100644 index 0000000..0b4a888 --- /dev/null +++ b/api/testAssignmentQueries.mjs @@ -0,0 +1,44 @@ +import { + getAverageAssignmentScore, + getMaxAssignmentScore, + getMinAssignmentScore, + getTopKAssignmentScores, + getTopKTotalScores +} from './lib/redisHelper.mjs'; + +const SECTION = 'Projects'; +const ASSIGNMENT = 'Project 2: Spelling Bee'; +const K = 3; + +async function runTests() { + try { + console.log('--- Assignment Query Tests ---'); + console.log(`Section: ${SECTION}, Assignment: ${ASSIGNMENT}`); + + const avg = await getAverageAssignmentScore(SECTION, ASSIGNMENT); + console.log(`Average score: ${avg}`); + + const max = await getMaxAssignmentScore(SECTION, ASSIGNMENT); + console.log(`Max score: ${max}`); + + const min = await getMinAssignmentScore(SECTION, ASSIGNMENT); + console.log(`Min score: ${min}`); + + const topK = await getTopKAssignmentScores(SECTION, ASSIGNMENT, K); + console.log(`Top ${K} students for assignment:`); + topK.forEach((s, i) => { + console.log(`${i + 1}. ${s.name} (${s.email}): ${s.score}`); + }); + + const topTotal = await getTopKTotalScores(K); + console.log(`Top ${K} students by total score:`); + topTotal.forEach((s, i) => { + console.log(`${i + 1}. ${s.name} (${s.email}): ${s.total}`); + }); + + } catch (err) { + console.error('Error during tests:', err); + } +} + +runTests(); From a65c89290be4613d7b91cb14d77a6cfb4ebe10f1 Mon Sep 17 00:00:00 2001 From: Eemon Qayumi Date: Thu, 10 Apr 2025 06:18:47 -0700 Subject: [PATCH 2/2] Added comments to testAssignmentQueries.mjs for readability --- api/testAssignmentQueries.mjs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/testAssignmentQueries.mjs b/api/testAssignmentQueries.mjs index 0b4a888..fe238c8 100644 --- a/api/testAssignmentQueries.mjs +++ b/api/testAssignmentQueries.mjs @@ -1,3 +1,19 @@ +/** + * Test script for Redis-based assignment score queries. + * + * This script runs a set of tests against helper functions in redisHelper.mjs + * to evaluate assignment statistics, including: + * - Average score for a given assignment + * - Maximum and minimum score + * - Top K students by assignment score + * - Top K students by total course score + * + * Run inside the gradeview-api Docker container: + * docker exec -it gradeview-api sh + * cd /api + * node testAssignmentQueries.mjs + */ + import { getAverageAssignmentScore, getMaxAssignmentScore,