Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 117 additions & 1 deletion api/lib/redisHelper.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,120 @@ export async function getStudents() {

await client.quit();
return students;
}
}

/**
* 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<object>} 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<object>} 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);
}
60 changes: 60 additions & 0 deletions api/testAssignmentQueries.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* 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,
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();