feat: implement percentage calculation utility and refactor analytics to use it

- Added `calcPercentageWithRounding` function for consistent percentage calculations with rounding.
- Updated course and user analytics to utilize the new utility for progress and average grade calculations.
- Removed redundant rounding logic from analytics functions.
This commit is contained in:
Rotimi Best
2025-09-18 10:34:54 +01:00
parent bcd17dd0df
commit a6880f1b54
3 changed files with 31 additions and 19 deletions
@@ -0,0 +1,10 @@
export function calcPercentageWithRounding(a: number, b: number) {
if (b === 0) {
return 0;
}
const rawPercentage = (a / b) * 100;
const fixedString = rawPercentage.toFixed(1);
const roundedNumber = parseFloat(fixedString);
return isNaN(roundedNumber) ? 0 : roundedNumber;
}
@@ -1,6 +1,7 @@
import type { CourseAnalytics, StudentOverview } from '$lib/utils/types/analytics';
import type { UserExercisesStats } from '$lib/utils/types/analytics';
import { calcPercentageWithRounding } from '$lib/utils/functions/number.js';
import { fetchProfileCourseProgress } from '$lib/utils/services/courses';
import { getServerSupabase } from '$lib/utils/functions/supabase.server';
import { json } from '@sveltejs/kit';
@@ -193,15 +194,14 @@ async function getStudentOverview(courseId: string, member: any): Promise<Studen
// Calculate progress percentage
const lessonsCompleted = courseProgress.lessons_completed || 0;
const totalLessons = courseProgress.lessons_count || 0;
const progressPercentage =
totalLessons > 0 ? Math.round((lessonsCompleted / totalLessons) * 100) : 0;
const progressPercentage = calcPercentageWithRounding(lessonsCompleted, totalLessons);
// Calculate average grade using the same logic as the people page
const totalEarnedPoints =
userExercisesStats?.reduce((sum, exercise) => sum + exercise.score, 0) || 0;
const totalPoints =
userExercisesStats?.reduce((sum, exercise) => sum + exercise.totalPoints, 0) || 0;
const averageGrade = totalPoints > 0 ? Math.round((totalEarnedPoints / totalPoints) * 100) : 0;
const averageGrade = calcPercentageWithRounding(totalEarnedPoints, totalPoints);
return {
id: member.profile_id,
@@ -6,6 +6,7 @@ import type {
} from '$lib/utils/types/analytics';
import { fetchCourses, fetchProfileCourseProgress } from '$lib/utils/services/courses';
import { calcPercentageWithRounding } from '$lib/utils/functions/number';
import { getServerSupabase } from '$lib/utils/functions/supabase.server';
import { json } from '@sveltejs/kit';
@@ -42,10 +43,6 @@ function sumArrObject<T>(arr: T[], key: keyof T) {
return arr.reduce((sum, item) => sum + ((item[key] as number) || 0), 0);
}
function roundNumber(a: number, b: number) {
return Math.round(a / b) * 100 || 0;
}
async function getLastLogin(userId: string): Promise<string | undefined> {
try {
const { data, error } = await supabase
@@ -103,14 +100,14 @@ async function getAudienceData(userId: string, orgId: string): Promise<UserAnaly
const totalEarnedPoints = sumArrObject(exercisesStats, 'score');
const totalPoints = sumArrObject(exercisesStats, 'totalPoints');
const averageGrade = roundNumber(totalEarnedPoints, totalPoints);
const averageGrade = calcPercentageWithRounding(totalEarnedPoints, totalPoints);
const lessonsCompleted = courseProgress?.lessons_completed || 0;
const lessonsCount = courseProgress?.lessons_count || 0;
audienceAnalytics.courses.push({
...course,
...courseProgress,
progress_percentage: roundNumber(lessonsCompleted, lessonsCount),
progress_percentage: calcPercentageWithRounding(lessonsCompleted, lessonsCount),
average_grade: averageGrade
});
}
@@ -123,10 +120,13 @@ async function getAudienceData(userId: string, orgId: string): Promise<UserAnaly
(acc, course) => acc + course.lessons_completed,
0
);
const overallCourseProgress = roundNumber(completedLessons, totalLessons);
const overallCourseProgress = calcPercentageWithRounding(completedLessons, totalLessons);
const allGrades = sumArrObject(audienceAnalytics.courses, 'average_grade');
const overallAverageGrade = roundNumber(allGrades, audienceAnalytics.courses.length);
const overallAverageGrade = calcPercentageWithRounding(
allGrades,
audienceAnalytics.courses.length
);
audienceAnalytics.overallCourseProgress = overallCourseProgress;
audienceAnalytics.overallAverageGrade = overallAverageGrade || 0;
@@ -168,30 +168,32 @@ async function getStudentAnalyticsData(
userCourseAnalytics.user.lastSeen = await getLastLogin(userId);
// fetch marks, lessons, and exercise progress
const [userExercisesStats, lessonCompletions, exerciseResponse] = await Promise.all([
const [userExercisesStats, lessons, exerciseResponse] = await Promise.all([
fetchUserExercisesStats(courseId, userId),
fetchLessonCompletion(courseId, userId),
fetchLessonsWithCompletion(courseId, userId),
fetchProfileCourseProgress(courseId, userId)
]);
if (!userExercisesStats || !lessonCompletions || !exerciseResponse.data) {
if (!userExercisesStats || !lessons || !exerciseResponse.data) {
throw new Error('Failed to fetch course analytics data');
}
const totalEarnedPoints = sumArrObject(userExercisesStats, 'score');
const totalPoints = sumArrObject(userExercisesStats, 'totalPoints');
userCourseAnalytics.averageGrade = roundNumber(totalEarnedPoints, totalPoints);
userCourseAnalytics.averageGrade = calcPercentageWithRounding(totalEarnedPoints, totalPoints);
userCourseAnalytics.userExercisesStats = userExercisesStats;
const completedLessons = lessonCompletions.filter((lesson) => lesson.completed);
const completedLessons = lessons.filter((lesson) => lesson.completed);
userCourseAnalytics.totalExercises = exerciseResponse.data[0].exercises_count;
userCourseAnalytics.completedExercises = exerciseResponse.data[0].exercises_completed;
userCourseAnalytics.progressPercentage = roundNumber(
console.log('completedLessons.length', completedLessons.length);
console.log('lessons.length', lessons.length);
userCourseAnalytics.progressPercentage = calcPercentageWithRounding(
completedLessons.length,
lessonCompletions.length
lessons.length
);
return userCourseAnalytics;
@@ -250,7 +252,7 @@ async function fetchUserExercisesStats(
}
}
async function fetchLessonCompletion(courseId, userId) {
async function fetchLessonsWithCompletion(courseId, userId) {
try {
const { data: lessons, error: lessonsError } = await supabase
.from('lesson')