Show grades instead of points for certificates

This commit is contained in:
Daniel Egger 2024-05-03 17:12:15 +02:00
parent 8cab40f1d5
commit 4b9614d89d
7 changed files with 79 additions and 36 deletions

View File

@ -5,8 +5,8 @@ import CompetenceAssignmentRow from "@/pages/competence/CompetenceAssignmentRow.
import { computed } from "vue"; import { computed } from "vue";
import ItProgress from "@/components/ui/ItProgress.vue"; import ItProgress from "@/components/ui/ItProgress.vue";
import { import {
assignmentsMaxEvaluationPoints,
assignmentsUserPoints, assignmentsUserPoints,
calcCompetenceCertificateGrade,
competenceCertificateProgressStatusCount, competenceCertificateProgressStatusCount,
} from "@/pages/competence/utils"; } from "@/pages/competence/utils";
@ -18,14 +18,14 @@ const props = defineProps<{
frontendUrl?: string; frontendUrl?: string;
}>(); }>();
const totalPointsEvaluatedAssignments = computed(() => {
return assignmentsMaxEvaluationPoints(props.competenceCertificate.assignments);
});
const userPointsEvaluatedAssignments = computed(() => { const userPointsEvaluatedAssignments = computed(() => {
return assignmentsUserPoints(props.competenceCertificate.assignments); return assignmentsUserPoints(props.competenceCertificate.assignments);
}); });
const userGrade = computed(() => {
return calcCompetenceCertificateGrade(props.competenceCertificate.assignments);
});
const numAssignmentsEvaluated = computed(() => { const numAssignmentsEvaluated = computed(() => {
return props.competenceCertificate.assignments.filter((a) => { return props.competenceCertificate.assignments.filter((a) => {
return a.completion?.completion_status === "EVALUATION_SUBMITTED"; return a.completion?.completion_status === "EVALUATION_SUBMITTED";
@ -75,10 +75,7 @@ const frontendUrl = computed(() => {
class="py-4" class="py-4"
:class="{ 'heading-1': props.detailView, 'heading-2': !props.detailView }" :class="{ 'heading-1': props.detailView, 'heading-2': !props.detailView }"
> >
{{ userPointsEvaluatedAssignments }} {{ userGrade }}
</div>
<div class="ml-1">
{{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }}
</div> </div>
</section> </section>
<section v-else class="py-2"> <section v-else class="py-2">

View File

@ -5,8 +5,8 @@ import type { CompetenceCertificate } from "@/types";
import { useCertificateQuery } from "@/composables"; import { useCertificateQuery } from "@/composables";
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue"; import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
import { import {
assignmentsMaxEvaluationPoints,
assignmentsUserPoints, assignmentsUserPoints,
calcCompetencesTotalGrade,
} from "@/pages/competence/utils"; } from "@/pages/competence/utils";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { getCertificates } from "@/services/competence"; import { getCertificates } from "@/services/competence";
@ -44,8 +44,8 @@ const assignments = computed(() => {
return competenceCertificates?.value?.flatMap((cc) => cc.assignments); return competenceCertificates?.value?.flatMap((cc) => cc.assignments);
}); });
const totalPointsEvaluatedAssignments = computed(() => { const totalGrade = computed(() => {
return assignmentsMaxEvaluationPoints(assignments.value ?? []); return calcCompetencesTotalGrade(competenceCertificates.value ?? []);
}); });
const userPointsEvaluatedAssignments = computed(() => { const userPointsEvaluatedAssignments = computed(() => {
@ -91,10 +91,7 @@ onMounted(async () => {
<section v-if="userPointsEvaluatedAssignments > 0" class="flex items-center"> <section v-if="userPointsEvaluatedAssignments > 0" class="flex items-center">
<div class="heading-1 py-4"> <div class="heading-1 py-4">
{{ userPointsEvaluatedAssignments }} {{ totalGrade }}
</div>
<div class="pl-2">
{{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }}
</div> </div>
</section> </section>
<section v-else class="my-4"> <section v-else class="my-4">

View File

@ -6,8 +6,9 @@ import { computed } from "vue";
import type { CompetenceCertificate } from "@/types"; import type { CompetenceCertificate } from "@/types";
import { useCurrentCourseSession } from "@/composables"; import { useCurrentCourseSession } from "@/composables";
import { import {
assignmentsMaxEvaluationPoints,
assignmentsUserPoints, assignmentsUserPoints,
calcCompetenceCertificateGrade,
calcCompetencesTotalGrade,
competenceCertificateProgressStatusCount, competenceCertificateProgressStatusCount,
} from "@/pages/competence/utils"; } from "@/pages/competence/utils";
import ItProgress from "@/components/ui/ItProgress.vue"; import ItProgress from "@/components/ui/ItProgress.vue";
@ -42,10 +43,6 @@ const allAssignments = computed(() => {
return competenceCertificates.value.flatMap((cc) => cc.assignments); return competenceCertificates.value.flatMap((cc) => cc.assignments);
}); });
const totalPointsEvaluatedAssignments = computed(() => {
return assignmentsMaxEvaluationPoints(allAssignments.value);
});
const userPointsEvaluatedAssignments = computed(() => { const userPointsEvaluatedAssignments = computed(() => {
return assignmentsUserPoints(allAssignments.value); return assignmentsUserPoints(allAssignments.value);
}); });
@ -71,9 +68,8 @@ const router = useRouter();
<div v-if="userPointsEvaluatedAssignments > 0"> <div v-if="userPointsEvaluatedAssignments > 0">
{{ $t("a.Zwischenstand") }} {{ $t("a.Gesamtpunktzahl") }}: {{ $t("a.Zwischenstand") }} {{ $t("a.Gesamtpunktzahl") }}:
<span class="font-bold"> <span class="font-bold">
{{ userPointsEvaluatedAssignments }} {{ calcCompetencesTotalGrade(competenceCertificates ?? []) }}
</span> </span>
{{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }}
</div> </div>
<div v-else> <div v-else>
{{ $t("a.competenceCertificateNoUserPoints") }} {{ $t("a.competenceCertificateNoUserPoints") }}
@ -92,14 +88,12 @@ const router = useRouter();
{{ certificate.title }} {{ certificate.title }}
</div> </div>
<div class="mt-4 lg:mt-0"> <div class="mt-4 lg:mt-0">
<span class="text-bold"> <span
{{ assignmentsUserPoints(certificate.assignments) }} v-if="calcCompetenceCertificateGrade(certificate.assignments)"
class="text-bold"
>
{{ calcCompetenceCertificateGrade(certificate.assignments) }}
</span> </span>
{{
$t("assignment.von x Punkten", {
x: assignmentsMaxEvaluationPoints(certificate.assignments),
})
}}
</div> </div>
<div class="flex"> <div class="flex">
<div> <div>

View File

@ -1,5 +1,6 @@
import type { StatusCount } from "@/components/ui/ItProgress.vue"; 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"; import _ from "lodash";
export function assignmentsMaxEvaluationPoints( export function assignmentsMaxEvaluationPoints(
@ -20,6 +21,51 @@ export function assignmentsUserPoints(assignments: CompetenceCertificateAssignme
).toFixed(1); ).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( export function competenceCertificateProgressStatusCount(
assignments: CompetenceCertificateAssignment[] assignments: CompetenceCertificateAssignment[]
) { ) {

View File

@ -90,3 +90,10 @@ export function pointsToGrade(points: number, maxPoints: number) {
const halfGrade = grade / 2; const halfGrade = grade / 2;
return Math.min(halfGrade, 5) + 1; 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;
}

View File

@ -392,6 +392,7 @@ export type ActionCompetence = Omit<
export interface CompetenceCertificateAssignment extends BaseCourseWagtailPage { export interface CompetenceCertificateAssignment extends BaseCourseWagtailPage {
assignment_type: "CASEWORK" | "EDONIQ_TEST"; assignment_type: "CASEWORK" | "EDONIQ_TEST";
max_points: number; max_points: number;
competence_certificate_weight: number;
learning_content: learning_content:
| (BaseCourseWagtailPage & { | (BaseCourseWagtailPage & {
circle: CircleLight; circle: CircleLight;

View File

@ -4,15 +4,16 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('assignment', '0012_auto_20240124_1004'), ("assignment", "0012_auto_20240124_1004"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='assignment', model_name="assignment",
name='competence_certificate_weight', name="competence_certificate_weight",
field=models.FloatField(default=1.0, help_text='Gewichtung für den Kompetenznachweis'), field=models.FloatField(
default=1.0, help_text="Gewichtung für den Kompetenznachweis"
),
), ),
] ]