mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
[UnitTests] Update course matrix to use courseIds
This commit is contained in:
committed by
Aaro Koivukangas
parent
ca54fba5be
commit
86900b3ad4
@@ -20,8 +20,10 @@ export const CourseMatrixExport = ({
|
||||
const { getTextIn } = useLanguage()
|
||||
|
||||
const getXlsx = () => {
|
||||
const courseNameByCode = new Map(courses.map(({ course }) => [course.code, getTextIn(course.name) ?? '']))
|
||||
const { completedCoursesRows, courseCounterRows } = calculateExcelData(students, courseNameByCode)
|
||||
const courseInfoById = new Map(
|
||||
courses.map(({ course }) => [course.id, { code: course.code, name: getTextIn(course.name) ?? '' }])
|
||||
)
|
||||
const { completedCoursesRows, courseCounterRows } = calculateExcelData(students, courseInfoById)
|
||||
|
||||
// Columns: Student number, Name, student's attainments in the format: "course name (course code)"
|
||||
const completedCoursesSheet = utils.aoa_to_sheet([['Student number', 'Name'], ...completedCoursesRows])
|
||||
|
||||
@@ -21,41 +21,43 @@ export type CourseMatrixAggregate = {
|
||||
|
||||
/**
|
||||
* Passed completions of the student that are included in one of the student's study plans (HOPS).
|
||||
* Returns the latest completion per course code, sorted by course code.
|
||||
* Returns the latest completion per course id, sorted by course id.
|
||||
*/
|
||||
export const getHopsCourses = (student: FormattedStudent): StudentCourse[] => {
|
||||
const hopsCourseCodes = new Set(student.studyplans?.flatMap(plan => plan.included_courses ?? []) ?? [])
|
||||
const passedCoursesInHops = student.courses.filter(course => course.passed && hopsCourseCodes.has(course.course_code))
|
||||
const hopsCourseIds = new Set(student.studyplans?.flatMap(plan => plan.included_courses ?? []) ?? [])
|
||||
const passedCoursesInHops = student.courses.filter(course => course.passed && hopsCourseIds.has(course.course_id))
|
||||
|
||||
const latestByCode = new Map<string, StudentCourse>()
|
||||
const latestById = new Map<string, StudentCourse>()
|
||||
for (const course of passedCoursesInHops) {
|
||||
if (!course.course_code) continue
|
||||
if (!course.course_id) continue
|
||||
|
||||
const existing = latestByCode.get(course.course_code)
|
||||
const existing = latestById.get(course.course_id)
|
||||
if (!existing || new Date(course.date).getTime() > new Date(existing.date).getTime()) {
|
||||
latestByCode.set(course.course_code, course)
|
||||
latestById.set(course.course_id, course)
|
||||
}
|
||||
}
|
||||
|
||||
return [...latestByCode.values()].sort((a, b) => a.course_code.localeCompare(b.course_code))
|
||||
return [...latestById.values()].sort((a, b) => a.course_id.localeCompare(b.course_id))
|
||||
}
|
||||
|
||||
export const calculateExcelData = (students: FormattedStudent[], courseNameByCode: Map<string, string>) => {
|
||||
const counters = new Map<string, { name: string; credits: number; students: Set<string> }>()
|
||||
export const calculateExcelData = (
|
||||
students: FormattedStudent[],
|
||||
courseInfoById: Map<string, { code: string; name: string }>
|
||||
) => {
|
||||
// Keyed by course_id internally so different courses are never accidentally merged;
|
||||
// code/name are only for display.
|
||||
const counters = new Map<string, { code: string; name: string; credits: number; students: Set<string> }>()
|
||||
|
||||
const rows = students.map(student => {
|
||||
const courses = getHopsCourses(student).map(course => {
|
||||
const code = course.course_code
|
||||
const counter = counters.get(code) ?? {
|
||||
name: courseNameByCode.get(code) ?? '',
|
||||
credits: 0,
|
||||
students: new Set(),
|
||||
}
|
||||
counters.set(code, counter)
|
||||
const id = course.course_id
|
||||
const { code, name } = courseInfoById.get(id) ?? { code: id, name: '' }
|
||||
const counter = counters.get(id) ?? { code, name, credits: 0, students: new Set() }
|
||||
counters.set(id, counter)
|
||||
counter.credits += course.credits ?? 0
|
||||
counter.students.add(student.studentNumber)
|
||||
|
||||
return { code, name: courseNameByCode.get(code) ?? '', credits: course.credits ?? 0 }
|
||||
return { code, name, credits: course.credits ?? 0 }
|
||||
})
|
||||
|
||||
return { studentNumber: student.studentNumber, studentName: student.name, courses }
|
||||
@@ -67,8 +69,8 @@ export const calculateExcelData = (students: FormattedStudent[], courseNameByCod
|
||||
...courses.map(course => `${course.name} (${course.code})`),
|
||||
])
|
||||
|
||||
const courseCounterRows = [...counters.entries()]
|
||||
.map(([code, { name, credits, students }]) => [code, name, students.size.toString(), credits.toString()])
|
||||
const courseCounterRows = [...counters.values()]
|
||||
.map(({ code, name, credits, students }) => [code, name, students.size.toString(), credits.toString()])
|
||||
.sort(([_a, __a, aStudents], [_b, __b, bStudents]) => parseInt(bStudents) - parseInt(aStudents))
|
||||
|
||||
return { completedCoursesRows, courseCounterRows }
|
||||
|
||||
@@ -6,12 +6,12 @@ import { CreditTypeCode } from '@oodikone/shared/types'
|
||||
import { createCourse as createBaseCourse, createStudent, createStudyPlan } from '@oodikone/shared/test/utils'
|
||||
|
||||
const createCourse = (
|
||||
course_code: string,
|
||||
course_id: string,
|
||||
credits: number,
|
||||
passed: boolean,
|
||||
date: Date,
|
||||
credittypecode: CreditTypeCode = passed ? CreditTypeCode.PASSED : CreditTypeCode.FAILED
|
||||
) => createBaseCourse({ course_code, date, passed, grade: passed ? '5' : 'Hyl.', credits, credittypecode })
|
||||
) => createBaseCourse({ course_id, date, passed, grade: passed ? '5' : 'Hyl.', credits, credittypecode })
|
||||
|
||||
const createHops = (includedCourses: string[]) => createStudyPlan({ included_courses: includedCourses })
|
||||
|
||||
@@ -29,7 +29,7 @@ void describe('getHopsCourses', () => {
|
||||
const result = getHopsCourses(student)
|
||||
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].course_code, 'A')
|
||||
assert.equal(result[0].course_id, 'A')
|
||||
})
|
||||
|
||||
void it('returns an empty list when the student has no HOPS', () => {
|
||||
@@ -55,28 +55,28 @@ void describe('getHopsCourses', () => {
|
||||
})
|
||||
|
||||
void describe('calculateExcelData', () => {
|
||||
void it('builds completed-course rows and aggregates credits per course', () => {
|
||||
void it('builds completed-course rows and aggregates credits per course, displaying code rather than id', () => {
|
||||
const students = [
|
||||
createStudent({
|
||||
studentNumber: '1',
|
||||
courses: [createCourse('A', 5, true, new Date('2024-09-01'))],
|
||||
studyplans: [createHops(['A'])],
|
||||
courses: [createCourse('course-a', 5, true, new Date('2024-09-01'))],
|
||||
studyplans: [createHops(['course-a'])],
|
||||
}),
|
||||
createStudent({
|
||||
studentNumber: '2',
|
||||
courses: [
|
||||
createCourse('A', 5, true, new Date('2024-09-01')),
|
||||
createCourse('B', 3, true, new Date('2024-10-01')),
|
||||
createCourse('course-a', 5, true, new Date('2024-09-01')),
|
||||
createCourse('course-b', 3, true, new Date('2024-10-01')),
|
||||
],
|
||||
studyplans: [createHops(['A', 'B'])],
|
||||
studyplans: [createHops(['course-a', 'course-b'])],
|
||||
}),
|
||||
]
|
||||
const courseNameByCode = new Map([
|
||||
['A', 'Course A'],
|
||||
['B', 'Course B'],
|
||||
const courseInfoById = new Map([
|
||||
['course-a', { code: 'A', name: 'Course A' }],
|
||||
['course-b', { code: 'B', name: 'Course B' }],
|
||||
])
|
||||
|
||||
const data = calculateExcelData(students, courseNameByCode)
|
||||
const data = calculateExcelData(students, courseInfoById)
|
||||
|
||||
assert.deepEqual(data.completedCoursesRows, [
|
||||
['1', 'Testi Opiskelija', 'Course A (A)'],
|
||||
@@ -88,18 +88,18 @@ void describe('calculateExcelData', () => {
|
||||
])
|
||||
})
|
||||
|
||||
void it('uses an empty name for courses missing from the course map', () => {
|
||||
void it('falls back to id as the code for courses missing from the course map', () => {
|
||||
const students = [
|
||||
createStudent({
|
||||
studentNumber: '1',
|
||||
courses: [createCourse('RANDOMCODE', 5, true, new Date('2024-09-01'))],
|
||||
studyplans: [createHops(['RANDOMCODE'])],
|
||||
courses: [createCourse('unknown-course-id', 5, true, new Date('2024-09-01'))],
|
||||
studyplans: [createHops(['unknown-course-id'])],
|
||||
}),
|
||||
]
|
||||
|
||||
const data = calculateExcelData(students, new Map())
|
||||
|
||||
assert.deepEqual(data.completedCoursesRows, [['1', 'Testi Opiskelija', ' (RANDOMCODE)']])
|
||||
assert.deepEqual(data.courseCounterRows, [['RANDOMCODE', '', '1', '5']])
|
||||
assert.deepEqual(data.completedCoursesRows, [['1', 'Testi Opiskelija', ' (unknown-course-id)']])
|
||||
assert.deepEqual(data.courseCounterRows, [['unknown-course-id', '', '1', '5']])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user