193 lines
5.7 KiB
TypeScript
193 lines
5.7 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
|
|
}
|
|
|
|
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<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
|
|
}
|