185 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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())
 | |
|       .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: 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 { 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: 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 { 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: Record<string, number>[],
 | |
|     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
 | |
| }
 | 
