import Colle from '#models/colle' import { DateTime } from 'luxon' export class GradeService { private calculateAverage(colles: Colle[]) { const total = colles.reduce((sum, colle) => sum + colle.grade, 0) return parseFloat((total / colles.length).toFixed(2)) } private calculateSubjectAverage(colles: Colle[], subject: string) { const subjectColles = colles.filter((colle) => colle.subject.name === subject) const total = subjectColles.reduce((sum, colle) => sum + colle.grade, 0) return parseFloat((total / subjectColles.length).toFixed(2)) } 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) } private 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()) .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()) return colles.length ? this.calculateAverage(colles) : 0 } public async getMonthGrade(userId: number, startDate: DateTime) { const colles = await this.getColles(userId, startDate, 1) const subjects = this.getSubjects(colles) const results: PeriodResult[] = [] 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 { grades: results, subjects } } 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: PeriodResult[] = [] let periodStartDate = startDate let index = 1 while (periodStartDate < startDate.plus({ months })) { const periodEndDate = periodStartDate.endOf('month') const periodColles = this.getPeriodColles(colles, periodStartDate, periodEndDate) const periodAverage = this.calculateAverage(periodColles) const subjectAverages = await this.getSubjectAverages( subjects, periodColles, colles, results, index, userId, periodStartDate ) console.log(this.reduce(subjectAverages), subjectAverages) results.push({ period: periodStartDate.toFormat('MMM'), average: periodAverage, ...this.reduce(subjectAverages), }) periodStartDate = periodEndDate.plus({ days: 1 }) index++ } return { grades: results, subjects } } 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: PeriodResult[], unitIndex: number, userId: number, periodStart: DateTime ) { const results = await Promise.all( subjects.map(async (subject) => { if (periodColles.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].subjectAverages?.find( (s) => s.subject === subject ) if (prev) { return { subject, average: prev.average } } } // 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: number } interface PeriodResult { period: string average: number subjectAverages: SubjectPerformance[] }