Skip to content

Commit 4a1276c

Browse files
committed
[Meta] Rework class population data
1 parent 5d601d3 commit 4a1276c

File tree

24 files changed

+574
-513
lines changed

24 files changed

+574
-513
lines changed
Lines changed: 5 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
import { SISStudyRight } from '@oodikone/shared/models'
2-
import { FormattedStudent, CriteriaYear, Name, ProgressCriteria } from '@oodikone/shared/types'
2+
import { FormattedStudent, Name } from '@oodikone/shared/types'
33
import { StudentData, StudentTags } from '@oodikone/shared/types/studentData'
4-
import { dateYearsFromNow, dateDaysFromNow, dayInMilliseconds } from '@oodikone/shared/util/datetime'
5-
import { CreditModel } from '../../models'
4+
import { dateDaysFromNow, dayInMilliseconds } from '@oodikone/shared/util/datetime'
65
import { hasTransferredFromOrToProgramme } from '../studyProgramme/studyProgrammeHelpers'
7-
import type { StudentStudyPlan, StudentStudyRight } from './getStudentData'
6+
import type { StudentStudyRight } from './getStudentData'
87
import { getCurriculumVersion } from './shared'
9-
import type { AnonymousCredit, AnonymousEnrollment } from './statisticsOf'
10-
11-
const yearMap: [string, keyof ProgressCriteria['courses']][] = [
12-
['year1', 'yearOne'],
13-
['year2', 'yearTwo'],
14-
['year3', 'yearThree'],
15-
['year4', 'yearFour'],
16-
['year5', 'yearFive'],
17-
['year6', 'yearSix'],
18-
]
198

209
const getTransferSource = (code: string, studyRights: StudentStudyRight[]): [boolean, string | undefined] => {
2110
if (code) {
@@ -48,138 +37,18 @@ const getTransferSource = (code: string, studyRights: StudentStudyRight[]): [boo
4837
return [false, undefined]
4938
}
5039

51-
const getCriteriaBase = (criteria: ProgressCriteria): [boolean, Record<string, CriteriaYear>] => {
52-
const thereAreCriteriaCourses = !!Object.values(criteria.courses).flatMap(val => val).length
53-
const thereAreCriteriaCredits = !!Object.values(criteria.credits).reduce((acc, cur) => acc + cur, 0)
54-
55-
const createEmptyCriteriaYear = (criteria: ProgressCriteria, year: keyof ProgressCriteria['courses']) => ({
56-
credits: false,
57-
totalSatisfied: 0,
58-
coursesSatisfied: Object.fromEntries(criteria.courses[year].map(course => [course, null])),
59-
})
60-
61-
const criteriaChecked: Record<string, CriteriaYear> = {
62-
year1: createEmptyCriteriaYear(criteria, 'yearOne'),
63-
year2: createEmptyCriteriaYear(criteria, 'yearTwo'),
64-
year3: createEmptyCriteriaYear(criteria, 'yearThree'),
65-
year4: createEmptyCriteriaYear(criteria, 'yearFour'),
66-
year5: createEmptyCriteriaYear(criteria, 'yearFive'),
67-
year6: createEmptyCriteriaYear(criteria, 'yearSix'),
68-
}
69-
70-
return [thereAreCriteriaCourses || thereAreCriteriaCredits, criteriaChecked]
71-
}
72-
73-
const getProgressCriteria = (
74-
startDate: string,
75-
criteria: ProgressCriteria,
76-
credits: AnonymousCredit[],
77-
hops: StudentStudyPlan | undefined
78-
) => {
79-
const [thereAreCriteria, criteriaChecked] = getCriteriaBase(criteria)
80-
if (!thereAreCriteria) return criteriaChecked
81-
82-
const startDateFromISO = new Date(startDate)
83-
84-
const criteriaCoursesBySubstitutionMap = new Map<string, string>()
85-
for (const [courseCode, substitutionCodes] of Object.entries(criteria.allCourses)) {
86-
criteriaCoursesBySubstitutionMap.set(courseCode, courseCode)
87-
88-
for (const substitutionCode of substitutionCodes) {
89-
criteriaCoursesBySubstitutionMap.set(substitutionCode, courseCode)
90-
}
91-
}
92-
93-
const academicYears = { year1: 0, year2: 0, year3: 0, year4: 0, year5: 0, year6: 0 }
94-
95-
const courses = credits.map(({ attainment_date, course_code, credits, credittypecode }) => ({
96-
course_code,
97-
credits,
98-
credittypecode,
99-
date:
100-
attainment_date < startDateFromISO
101-
? dateDaysFromNow(startDateFromISO, 1).toISOString()
102-
: attainment_date.toISOString(),
103-
}))
104-
105-
courses
106-
.filter(({ course_code }) => !!criteriaCoursesBySubstitutionMap.get(course_code))
107-
.filter(({ credittypecode }) => CreditModel.passed({ credittypecode }) || CreditModel.improved({ credittypecode }))
108-
.forEach(course => {
109-
const courseDate = new Date(course.date)
110-
const correctCode = criteriaCoursesBySubstitutionMap.get(course.course_code)!
111-
112-
yearMap.forEach(([yearToAdd, criteriaYear]) => {
113-
if (criteria.courses[criteriaYear].includes(correctCode)) {
114-
const currentDate = criteriaChecked[yearToAdd].coursesSatisfied[correctCode]
115-
if (!currentDate || courseDate < new Date(currentDate)) {
116-
criteriaChecked[yearToAdd].coursesSatisfied[correctCode] = course.date
117-
}
118-
}
119-
})
120-
})
121-
122-
courses.forEach(course => {
123-
const courseDate = new Date(course.date)
124-
const correctCode = criteriaCoursesBySubstitutionMap.get(course.course_code)!
125-
126-
if (
127-
startDateFromISO < courseDate &&
128-
!!hops &&
129-
(hops.included_courses.includes(course.course_code) || hops.included_courses.includes(correctCode))
130-
)
131-
Object.keys(academicYears)
132-
.filter((_, index) => courseDate < dateYearsFromNow(startDateFromISO, index + 1))
133-
.forEach(year => (academicYears[year] += course.credits))
134-
})
135-
136-
yearMap.forEach(([yearToAdd, criteriaYear]) => {
137-
criteriaChecked[yearToAdd].totalSatisfied +=
138-
Object.values(criteriaChecked[yearToAdd].coursesSatisfied).filter(course => !!course).length ?? 0
139-
// UPDATE CREDIT CRITERIA
140-
if (!!criteria.credits[criteriaYear] && criteria.credits[criteriaYear] < academicYears[yearToAdd]) {
141-
criteriaChecked[yearToAdd].credits = true
142-
criteriaChecked[yearToAdd].totalSatisfied += 1
143-
}
144-
})
145-
146-
return criteriaChecked
147-
}
148-
14940
export const formatStudentForAPI = (
15041
code: string,
15142
startDate: string,
15243
student: StudentData,
15344
tags: StudentTags[],
154-
credits: AnonymousCredit[],
155-
enrollments: AnonymousEnrollment[],
156-
optionData: Name | undefined,
157-
criteria: ProgressCriteria
158-
): FormattedStudent => {
45+
optionData: Name | undefined
46+
): Omit<FormattedStudent, 'criteriaProgress' | 'courses' | 'enrollments'> => {
15947
const { studentnumber, studyRights, studyplans } = student
16048

16149
const hops = studyplans.find(plan => plan.programme_code === code)
16250
const [transferredStudyright, transferSource] = getTransferSource(code, studyRights)
16351

164-
const courses = credits.map(credit => {
165-
const attainmentDateNormalized = credit.attainment_date.toISOString()
166-
const passed =
167-
CreditModel.passed({ credittypecode: credit.credittypecode }) ||
168-
CreditModel.improved({ credittypecode: credit.credittypecode })
169-
170-
return {
171-
course_code: credit.course_code,
172-
date: attainmentDateNormalized,
173-
passed,
174-
grade: passed ? credit.grade : 'Hyl.',
175-
credits: credit.credits,
176-
isStudyModuleCredit: credit.isStudyModule,
177-
credittypecode: credit.credittypecode,
178-
language: credit.language,
179-
studyright_id: credit.studyright_id,
180-
}
181-
})
182-
18352
return {
18453
firstnames: student.firstnames,
18554
lastname: student.lastname,
@@ -199,15 +68,12 @@ export const formatStudentForAPI = (
19968
birthdate: student.birthdate,
20069
sis_person_id: student.sis_person_id,
20170
citizenships: student.citizenships,
202-
criteriaProgress: getProgressCriteria(startDate, criteria, credits, hops),
20371
curriculumVersion: getCurriculumVersion(hops?.curriculum_period_id),
20472

20573
tags,
20674
transferredStudyright,
20775
transferSource,
20876
studyRights,
20977
studyplans,
210-
courses,
211-
enrollments,
21278
}
21379
}
Lines changed: 3 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { orderBy } from 'lodash'
22
import { Op } from 'sequelize'
33

4-
import type { CourseStats } from '@oodikone/shared/routes/populations'
5-
import { Name, DegreeProgrammeType, EnrollmentState } from '@oodikone/shared/types'
6-
import { SISStudyRightElementModel, CreditModel, CourseModel } from '../../models'
7-
import { getPassingSemester, SemesterStart } from '../../util/semester'
8-
import { StudentCredit, StudentEnrollment } from './getStudentData'
4+
import { Name, DegreeProgrammeType } from '@oodikone/shared/types'
5+
import { SISStudyRightElementModel, CourseModel } from '../../models'
6+
import { SemesterStart } from '../../util/semester'
97

108
type QueryParams = {
119
semesters: string[]
@@ -85,195 +83,3 @@ export const getCourses = (courses: string[]): Promise<Array<Pick<CourseModel, '
8583
},
8684
raw: true,
8785
})
88-
89-
const defaultCourse = {
90-
attempts: 0,
91-
enrollments: {
92-
[EnrollmentState.ENROLLED]: new Set<string>(),
93-
[EnrollmentState.REJECTED]: new Set<string>(),
94-
semesters: {
95-
BEFORE: {
96-
[EnrollmentState.ENROLLED]: new Set<string>(),
97-
[EnrollmentState.REJECTED]: new Set<string>(),
98-
},
99-
'0-FALL': {
100-
[EnrollmentState.ENROLLED]: new Set<string>(),
101-
[EnrollmentState.REJECTED]: new Set<string>(),
102-
},
103-
'0-SPRING': {
104-
[EnrollmentState.ENROLLED]: new Set<string>(),
105-
[EnrollmentState.REJECTED]: new Set<string>(),
106-
},
107-
'1-FALL': {
108-
[EnrollmentState.ENROLLED]: new Set<string>(),
109-
[EnrollmentState.REJECTED]: new Set<string>(),
110-
},
111-
'1-SPRING': {
112-
[EnrollmentState.ENROLLED]: new Set<string>(),
113-
[EnrollmentState.REJECTED]: new Set<string>(),
114-
},
115-
'2-FALL': {
116-
[EnrollmentState.ENROLLED]: new Set<string>(),
117-
[EnrollmentState.REJECTED]: new Set<string>(),
118-
},
119-
'2-SPRING': {
120-
[EnrollmentState.ENROLLED]: new Set<string>(),
121-
[EnrollmentState.REJECTED]: new Set<string>(),
122-
},
123-
'3-FALL': {
124-
[EnrollmentState.ENROLLED]: new Set<string>(),
125-
[EnrollmentState.REJECTED]: new Set<string>(),
126-
},
127-
'3-SPRING': {
128-
[EnrollmentState.ENROLLED]: new Set<string>(),
129-
[EnrollmentState.REJECTED]: new Set<string>(),
130-
},
131-
'4-FALL': {
132-
[EnrollmentState.ENROLLED]: new Set<string>(),
133-
[EnrollmentState.REJECTED]: new Set<string>(),
134-
},
135-
'4-SPRING': {
136-
[EnrollmentState.ENROLLED]: new Set<string>(),
137-
[EnrollmentState.REJECTED]: new Set<string>(),
138-
},
139-
'5-FALL': {
140-
[EnrollmentState.ENROLLED]: new Set<string>(),
141-
[EnrollmentState.REJECTED]: new Set<string>(),
142-
},
143-
'5-SPRING': {
144-
[EnrollmentState.ENROLLED]: new Set<string>(),
145-
[EnrollmentState.REJECTED]: new Set<string>(),
146-
},
147-
LATER: {
148-
[EnrollmentState.ENROLLED]: new Set<string>(),
149-
[EnrollmentState.REJECTED]: new Set<string>(),
150-
},
151-
},
152-
},
153-
grades: {},
154-
students: {
155-
all: new Set<string>(),
156-
passed: new Set<string>(),
157-
failed: new Set<string>(),
158-
improvedPassedGrade: new Set<string>(),
159-
markedToSemester: new Set<string>(),
160-
enrolledNoGrade: new Set<string>(),
161-
},
162-
163-
stats: {
164-
passingSemesters: {
165-
BEFORE: 0,
166-
'0-FALL': 0,
167-
'0-SPRING': 0,
168-
'1-FALL': 0,
169-
'1-SPRING': 0,
170-
'2-FALL': 0,
171-
'2-SPRING': 0,
172-
'3-FALL': 0,
173-
'3-SPRING': 0,
174-
'4-FALL': 0,
175-
'4-SPRING': 0,
176-
'5-FALL': 0,
177-
'5-SPRING': 0,
178-
LATER: 0,
179-
},
180-
},
181-
}
182-
183-
export const parseCourseData = async (
184-
studentStartingYears: Map<string, number>,
185-
enrollments: StudentEnrollment[],
186-
credits: StudentCredit[]
187-
): Promise<CourseStats[]> => {
188-
const getYear = (studentnumber: string) => studentStartingYears.get(studentnumber)!
189-
190-
const coursestats = new Map<string, typeof defaultCourse>()
191-
for (const enrollment of enrollments) {
192-
const { course_code, studentnumber, state, enrollment_date_time } = enrollment
193-
194-
// We cannot display these
195-
if (course_code === null) continue
196-
197-
if (!coursestats.has(course_code)) coursestats.set(course_code, structuredClone(defaultCourse))
198-
const course = coursestats.get(course_code)!
199-
200-
const initialDate = new Date(enrollment_date_time)
201-
const semester = getPassingSemester(getYear(studentnumber), initialDate)
202-
203-
course.students.all.add(studentnumber)
204-
course.students.enrolledNoGrade.add(studentnumber)
205-
course.enrollments[state].add(studentnumber)
206-
course.enrollments.semesters[semester][state].add(studentnumber)
207-
}
208-
209-
for (const credit of credits) {
210-
const { course_code, student_studentnumber: studentnumber, grade, attainment_date: date } = credit
211-
212-
// We cannot display these
213-
if (course_code === null) continue
214-
215-
const passingGrade = CreditModel.passed(credit)
216-
const failingGrade = CreditModel.failed(credit)
217-
const improvedGrade = CreditModel.improved(credit)
218-
219-
if (!coursestats.has(course_code)) coursestats.set(course_code, structuredClone(defaultCourse))
220-
const course = coursestats.get(course_code)!
221-
222-
course.attempts += 1
223-
const gradeCount = course.grades[grade]?.count ?? 0
224-
course.grades[grade] = { count: gradeCount + 1, status: { passingGrade, failingGrade, improvedGrade } }
225-
226-
course.students.all.add(studentnumber)
227-
course.students.enrolledNoGrade.delete(studentnumber)
228-
if (passingGrade) {
229-
if (!course.students.markedToSemester.has(studentnumber)) {
230-
course.students.markedToSemester.add(studentnumber)
231-
232-
const semester = getPassingSemester(getYear(studentnumber), new Date(date))
233-
course.stats.passingSemesters[semester]++
234-
}
235-
236-
course.students.passed.add(studentnumber)
237-
course.students.failed.delete(studentnumber)
238-
} else if (improvedGrade) {
239-
course.students.improvedPassedGrade.add(studentnumber)
240-
course.students.passed.add(studentnumber)
241-
course.students.failed.delete(studentnumber)
242-
} else if (failingGrade && !course.students.passed.has(studentnumber)) {
243-
course.students.failed.add(studentnumber)
244-
}
245-
}
246-
247-
const courses = await getCourses(Array.from(coursestats.keys()))
248-
const courseSubMap = new Map(courses.map(({ code, name, substitutions }) => [code, { name, substitutions }]))
249-
250-
return Array.from(coursestats.entries()).map(([code, { attempts, enrollments, grades, students, stats }]) => {
251-
const courseMapObj = courseSubMap.get(code)
252-
return {
253-
course: {
254-
code,
255-
name: courseMapObj?.name ?? { en: code },
256-
substitutions: courseMapObj?.substitutions ?? [],
257-
},
258-
attempts,
259-
enrollments: {
260-
[EnrollmentState.ENROLLED]: Array.from(enrollments[EnrollmentState.ENROLLED]),
261-
[EnrollmentState.REJECTED]: Array.from(enrollments[EnrollmentState.REJECTED]),
262-
semesters: Object.fromEntries(
263-
Object.entries(enrollments.semesters).map(([key, val]) => [
264-
key,
265-
{
266-
[EnrollmentState.ENROLLED]: Array.from(val[EnrollmentState.ENROLLED]),
267-
[EnrollmentState.REJECTED]: Array.from(val[EnrollmentState.REJECTED]),
268-
},
269-
])
270-
),
271-
},
272-
grades,
273-
students: Object.fromEntries(
274-
Object.entries(students).map(([key, val]) => [key, Array.from(val)])
275-
) as CourseStats['students'],
276-
stats,
277-
}
278-
})
279-
}

0 commit comments

Comments
 (0)