From 4b9614d89d754d3bce6c7422196d1757e24165db Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 3 May 2024 17:12:15 +0200 Subject: [PATCH] Show grades instead of points for certificates --- .../CompetenceCertificateComponent.vue | 15 +++--- .../CompetenceCertificateListPage.vue | 11 ++--- .../pages/competence/CompetenceIndexPage.vue | 22 ++++----- client/src/pages/competence/utils.ts | 48 ++++++++++++++++++- client/src/services/assignmentService.ts | 7 +++ client/src/types.ts | 1 + ...ssignment_competence_certificate_weight.py | 11 +++-- 7 files changed, 79 insertions(+), 36 deletions(-) diff --git a/client/src/pages/competence/CompetenceCertificateComponent.vue b/client/src/pages/competence/CompetenceCertificateComponent.vue index 30647b23..8cbb2ee5 100644 --- a/client/src/pages/competence/CompetenceCertificateComponent.vue +++ b/client/src/pages/competence/CompetenceCertificateComponent.vue @@ -5,8 +5,8 @@ import CompetenceAssignmentRow from "@/pages/competence/CompetenceAssignmentRow. import { computed } from "vue"; import ItProgress from "@/components/ui/ItProgress.vue"; import { - assignmentsMaxEvaluationPoints, assignmentsUserPoints, + calcCompetenceCertificateGrade, competenceCertificateProgressStatusCount, } from "@/pages/competence/utils"; @@ -18,14 +18,14 @@ const props = defineProps<{ frontendUrl?: string; }>(); -const totalPointsEvaluatedAssignments = computed(() => { - return assignmentsMaxEvaluationPoints(props.competenceCertificate.assignments); -}); - const userPointsEvaluatedAssignments = computed(() => { return assignmentsUserPoints(props.competenceCertificate.assignments); }); +const userGrade = computed(() => { + return calcCompetenceCertificateGrade(props.competenceCertificate.assignments); +}); + const numAssignmentsEvaluated = computed(() => { return props.competenceCertificate.assignments.filter((a) => { return a.completion?.completion_status === "EVALUATION_SUBMITTED"; @@ -75,10 +75,7 @@ const frontendUrl = computed(() => { class="py-4" :class="{ 'heading-1': props.detailView, 'heading-2': !props.detailView }" > - {{ userPointsEvaluatedAssignments }} - -
- {{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }} + {{ userGrade }}
diff --git a/client/src/pages/competence/CompetenceCertificateListPage.vue b/client/src/pages/competence/CompetenceCertificateListPage.vue index 31694b1d..cbee7030 100644 --- a/client/src/pages/competence/CompetenceCertificateListPage.vue +++ b/client/src/pages/competence/CompetenceCertificateListPage.vue @@ -5,8 +5,8 @@ import type { CompetenceCertificate } from "@/types"; import { useCertificateQuery } from "@/composables"; import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue"; import { - assignmentsMaxEvaluationPoints, assignmentsUserPoints, + calcCompetencesTotalGrade, } from "@/pages/competence/utils"; import { useRoute } from "vue-router"; import { getCertificates } from "@/services/competence"; @@ -44,8 +44,8 @@ const assignments = computed(() => { return competenceCertificates?.value?.flatMap((cc) => cc.assignments); }); -const totalPointsEvaluatedAssignments = computed(() => { - return assignmentsMaxEvaluationPoints(assignments.value ?? []); +const totalGrade = computed(() => { + return calcCompetencesTotalGrade(competenceCertificates.value ?? []); }); const userPointsEvaluatedAssignments = computed(() => { @@ -91,10 +91,7 @@ onMounted(async () => {
- {{ userPointsEvaluatedAssignments }} -
-
- {{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }} + {{ totalGrade }}
diff --git a/client/src/pages/competence/CompetenceIndexPage.vue b/client/src/pages/competence/CompetenceIndexPage.vue index 90e898c4..ca4e80bc 100644 --- a/client/src/pages/competence/CompetenceIndexPage.vue +++ b/client/src/pages/competence/CompetenceIndexPage.vue @@ -6,8 +6,9 @@ import { computed } from "vue"; import type { CompetenceCertificate } from "@/types"; import { useCurrentCourseSession } from "@/composables"; import { - assignmentsMaxEvaluationPoints, assignmentsUserPoints, + calcCompetenceCertificateGrade, + calcCompetencesTotalGrade, competenceCertificateProgressStatusCount, } from "@/pages/competence/utils"; import ItProgress from "@/components/ui/ItProgress.vue"; @@ -42,10 +43,6 @@ const allAssignments = computed(() => { return competenceCertificates.value.flatMap((cc) => cc.assignments); }); -const totalPointsEvaluatedAssignments = computed(() => { - return assignmentsMaxEvaluationPoints(allAssignments.value); -}); - const userPointsEvaluatedAssignments = computed(() => { return assignmentsUserPoints(allAssignments.value); }); @@ -71,9 +68,8 @@ const router = useRouter();
{{ $t("a.Zwischenstand") }} {{ $t("a.Gesamtpunktzahl") }}: - {{ userPointsEvaluatedAssignments }} + {{ calcCompetencesTotalGrade(competenceCertificates ?? []) }} - {{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }}
{{ $t("a.competenceCertificateNoUserPoints") }} @@ -92,14 +88,12 @@ const router = useRouter(); {{ certificate.title }}
- - {{ assignmentsUserPoints(certificate.assignments) }} + + {{ calcCompetenceCertificateGrade(certificate.assignments) }} - {{ - $t("assignment.von x Punkten", { - x: assignmentsMaxEvaluationPoints(certificate.assignments), - }) - }}
diff --git a/client/src/pages/competence/utils.ts b/client/src/pages/competence/utils.ts index 9f5539c0..0761187e 100644 --- a/client/src/pages/competence/utils.ts +++ b/client/src/pages/competence/utils.ts @@ -1,5 +1,6 @@ import type { StatusCount } from "@/components/ui/ItProgress.vue"; -import type { CompetenceCertificateAssignment } from "@/types"; +import { percentToGrade } from "@/services/assignmentService"; +import type { CompetenceCertificate, CompetenceCertificateAssignment } from "@/types"; import _ from "lodash"; export function assignmentsMaxEvaluationPoints( @@ -20,6 +21,51 @@ export function assignmentsUserPoints(assignments: CompetenceCertificateAssignme ).toFixed(1); } +export function calcCompetenceCertificateGrade( + assignments: CompetenceCertificateAssignment[] +) { + const evaluatedAssignments = assignments.filter( + (a) => a.completion?.completion_status === "EVALUATION_SUBMITTED" + ); + + const adjustedResults = evaluatedAssignments.map((a) => { + return ( + ((a.completion?.evaluation_points ?? 0) / a.max_points) * + a.competence_certificate_weight + ); + }); + + const adjustedAssignmentCount = _.sum( + evaluatedAssignments.map((a) => a.competence_certificate_weight) + ); + + if (adjustedAssignmentCount === 0) { + return undefined; + } + + return percentToGrade(_.sum(adjustedResults) / adjustedAssignmentCount); +} + +export function calcCompetencesTotalGrade( + competenceCertificates: CompetenceCertificate[] +) { + const competenceCertificateGrades = competenceCertificates + .map((cc) => { + return calcCompetenceCertificateGrade(cc.assignments); + }) + .filter((g) => { + // filter out "empty" grades + return !!g; + }); + + const percentGraded = + // @ts-ignore `g` cannot be undefined here + _.sum(competenceCertificateGrades.map((g) => g - 1)) / + (competenceCertificateGrades.length * 5); + + return percentToGrade(percentGraded); +} + export function competenceCertificateProgressStatusCount( assignments: CompetenceCertificateAssignment[] ) { diff --git a/client/src/services/assignmentService.ts b/client/src/services/assignmentService.ts index 796e1180..74356818 100644 --- a/client/src/services/assignmentService.ts +++ b/client/src/services/assignmentService.ts @@ -90,3 +90,10 @@ export function pointsToGrade(points: number, maxPoints: number) { const halfGrade = grade / 2; return Math.min(halfGrade, 5) + 1; } + +export function percentToGrade(percent: number) { + // round to half-grades + const grade = Math.round(percent * 10); + const halfGrade = grade / 2; + return Math.min(halfGrade, 5) + 1; +} diff --git a/client/src/types.ts b/client/src/types.ts index cf7a2268..d357616b 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -392,6 +392,7 @@ export type ActionCompetence = Omit< export interface CompetenceCertificateAssignment extends BaseCourseWagtailPage { assignment_type: "CASEWORK" | "EDONIQ_TEST"; max_points: number; + competence_certificate_weight: number; learning_content: | (BaseCourseWagtailPage & { circle: CircleLight; diff --git a/server/vbv_lernwelt/assignment/migrations/0013_assignment_competence_certificate_weight.py b/server/vbv_lernwelt/assignment/migrations/0013_assignment_competence_certificate_weight.py index c97a1842..cef59f69 100644 --- a/server/vbv_lernwelt/assignment/migrations/0013_assignment_competence_certificate_weight.py +++ b/server/vbv_lernwelt/assignment/migrations/0013_assignment_competence_certificate_weight.py @@ -4,15 +4,16 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('assignment', '0012_auto_20240124_1004'), + ("assignment", "0012_auto_20240124_1004"), ] operations = [ migrations.AddField( - model_name='assignment', - name='competence_certificate_weight', - field=models.FloatField(default=1.0, help_text='Gewichtung für den Kompetenznachweis'), + model_name="assignment", + name="competence_certificate_weight", + field=models.FloatField( + default=1.0, help_text="Gewichtung für den Kompetenznachweis" + ), ), ]