import Colle from '#models/colle' import { DateTime } from 'luxon' export class GradeService { private calculateAverage(colles: Colle[]) { const total = colles .map((colle) => parseFloat(colle.grade?.toString())) .filter(Boolean) .reduce((sum, grade) => sum + grade, 0) return (total / colles.length).toFixed(2) } private calculateSubjectAverage(colles: Colle[], subject: string) { const subjectColles = colles.filter((colle) => colle.subject.name === subject && colle.grade) return this.calculateAverage(subjectColles) } private getSubjects(colles: Colle[]) { return Array.from(new Set(colles.map((colle) => colle.subject.name))) } private getPeriodColles(colles: Colle[], startDate: DateTime, endDate: DateTime) { return colles.filter((colle) => colle.date >= startDate && colle.date < endDate) } public async getSubjectsAverages(userId: number, startDate: DateTime, months: number = 0) { const colles = await this.getColles(userId, startDate, months) const subjects = this.getSubjects(colles) const subjectAverages: SubjectPerformance[] = [] for (const subject of subjects) { const average = this.calculateSubjectAverage(colles, subject) subjectAverages.push({ subject, average }) } const globalAverage = this.calculateAverage(colles) return { globalAverage, subjectAverages, } } public getColles(userId: number, startDate: DateTime, months: number = 0) { const endDate = startDate.plus({ months }) return Colle.query() .where('studentId', userId) .where('date', '>=', startDate.toJSDate()) .where('date', '<', endDate.toJSDate()) .whereNotNull('garde') .preload('subject') .orderBy('date', 'asc') } private async getGlobalAverage(userId: number, subjectId: number, beforeDate: DateTime) { const colles = await Colle.query() .where('studentId', userId) .where('subjectId', subjectId) .where('date', '<', beforeDate.toJSDate()) .whereNotNull('garde') return colles.length ? this.calculateAverage(colles) : 0 } private serializeGrades(colles: Colle[]) { return colles.map((colle) => ({ date: colle.date.toMillis(), subject: colle.subject.name, grade: colle.grade, })) } public async getMonthGrade(userId: number, startDate: DateTime) { const colles = await this.getColles(userId, startDate, 1) const subjects = this.getSubjects(colles) const results: any = [] let periodStartDate = startDate for (let week = 1; week <= 4; week++) { const periodEndDate = periodStartDate.plus({ weeks: 1 }) const periodColles = this.getPeriodColles(colles, periodStartDate, periodEndDate) const periodAverage = this.calculateAverage(periodColles) const subjectAverages = await this.getSubjectAverages( subjects, periodColles, colles, results, week, userId, periodStartDate ) results.push({ period: `Week ${week}`, average: periodAverage, ...this.reduce(subjectAverages), }) periodStartDate = periodEndDate } return { averages: results, subjects, grades: this.serializeGrades(colles) } } public async getPeriodGrade(userId: number, startDate: DateTime, months: number = 0) { const colles = await this.getColles(userId, startDate, months) const subjects = this.getSubjects(colles) const results: any = [] let periodStartDate = startDate let index = 1 while (periodStartDate < startDate.plus({ months })) { const periodEndDate = periodStartDate.endOf('month') const periodColles = this.getPeriodColles(colles, startDate, periodEndDate) const periodAverage = this.calculateAverage(periodColles) const subjectAverages = await this.getSubjectAverages( subjects, periodColles, colles, results, index, userId, periodStartDate ) results.push({ period: periodStartDate.toFormat('MMM'), average: periodAverage, ...this.reduce(subjectAverages), }) periodStartDate = periodEndDate.plus({ days: 1 }) index++ } return { averages: results, subjects, grades: this.serializeGrades(colles) } } private reduce(subjectAverages: SubjectPerformance[]) { return subjectAverages.reduce((acc: any, { subject, average }) => { acc[subject] = average return acc }, {}) } private async getSubjectAverages( subjects: string[], periodColles: Colle[], allColles: Colle[], previousResults: Record[], unitIndex: number, userId: number, periodStart: DateTime ) { const results = await Promise.all( subjects.map(async (subject) => { const subjectColles = periodColles.filter( (colle) => colle.subject.name === subject && colle.grade ) if (subjectColles.length > 0) { return { subject, average: this.calculateSubjectAverage(periodColles, subject), } } // Try to use the previous unit's average if (unitIndex > 1) { const prev = previousResults[unitIndex - 2]?.[subject] if (prev) { return { subject, average: prev } } } // Otherwise fall back to global average before this period const subjectId = allColles.find((colle) => colle.subject.name === subject)!.subjectId const beforeAverage = await this.getGlobalAverage(userId, subjectId, periodStart) if (beforeAverage) { return { subject, average: beforeAverage } } return undefined }) ) return results.filter((s): s is SubjectPerformance => Boolean(s)) } } interface SubjectPerformance { subject: string average: string }