api/app/services/grade_service.ts
2025-08-20 18:18:03 +02:00

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
}