Merged in feature/grades-deduction (pull request #333)
Feature/grades deduction Approved-by: Christian Cueni Approved-by: Stéphanie Rotzetter
This commit is contained in:
commit
5a461ca86f
|
|
@ -106,7 +106,12 @@ function evaluationForTask(task: AssignmentEvaluationTask) {
|
|||
return expertData;
|
||||
}
|
||||
|
||||
const maxPoints = computed(() => maxAssignmentPoints(props.assignment));
|
||||
const maxPoints = computed(() => {
|
||||
if (props.assignmentCompletion.completion_status === "EVALUATION_SUBMITTED") {
|
||||
return props.assignmentCompletion.evaluation_max_points;
|
||||
}
|
||||
return maxAssignmentPoints(props.assignment);
|
||||
});
|
||||
const userPoints = computed(() =>
|
||||
userAssignmentPoints(props.assignment, props.assignmentCompletion)
|
||||
);
|
||||
|
|
@ -128,22 +133,49 @@ const userPoints = computed(() =>
|
|||
</h3>
|
||||
<h3 v-else class="mb-6" data-cy="sub-title">{{ $t(text.evaluationSubmission) }}</h3>
|
||||
<section class="mb-6 border p-6" data-cy="result-section">
|
||||
<section
|
||||
v-if="props.assignment.assignment_type === 'CASEWORK'"
|
||||
class="flex items-center"
|
||||
>
|
||||
<div class="heading-1 py-4" data-cy="user-points">
|
||||
{{ userPoints }}
|
||||
<section v-if="props.assignment.assignment_type === 'CASEWORK'">
|
||||
<div class="flex items-center">
|
||||
<div class="heading-1 py-4" data-cy="user-points">
|
||||
<template
|
||||
v-if="
|
||||
props.assignmentCompletion.completion_status == 'EVALUATION_SUBMITTED'
|
||||
"
|
||||
>
|
||||
{{ props.assignmentCompletion.evaluation_points_final }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ userPoints }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="pl-2" data-cy="total-points">
|
||||
{{ $t("assignment.von x Punkten", { x: maxPoints }) }}
|
||||
({{
|
||||
(
|
||||
((props.assignmentCompletion?.evaluation_points_final ?? 0) /
|
||||
(props.assignmentCompletion?.evaluation_max_points ?? 1)) *
|
||||
100
|
||||
).toFixed(0)
|
||||
}}%)
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-2" data-cy="total-points">
|
||||
{{ $t("assignment.von x Punkten", { x: maxPoints }) }}
|
||||
({{
|
||||
(
|
||||
((props.assignmentCompletion?.evaluation_points ?? 0) /
|
||||
(props.assignmentCompletion?.evaluation_max_points ?? 1)) *
|
||||
100
|
||||
).toFixed(0)
|
||||
}}%)
|
||||
|
||||
<div
|
||||
v-if="assignmentCompletion.evaluation_points_deducted > 0"
|
||||
data-cy="points-deducted"
|
||||
class="mb-4 text-gray-900"
|
||||
>
|
||||
<div>
|
||||
{{ $t("a.Punkte aus Bewertung") }}:
|
||||
{{ assignmentCompletion.evaluation_points }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t("a.Abgezogene Punkte") }}:
|
||||
{{ assignmentCompletion.evaluation_points_deducted }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t("a.Grund") }}:
|
||||
{{ assignmentCompletion.evaluation_points_deducted_reason }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ const getIconName = (lc: LearningContent) => {
|
|||
v-for="submittable in submittables"
|
||||
:key="submittable.id"
|
||||
class="flex flex-col justify-between gap-2 py-4 lg:flex-row lg:gap-4"
|
||||
:data-cy="`submittable-${submittable.content.slug}`"
|
||||
>
|
||||
<div class="flex flex-row items-center gap-2 lg:w-1/3">
|
||||
<div class="min-h-9 min-w-9">
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ const documents = {
|
|||
"\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n $evaluationUserId: ID\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n evaluation_user_id: $evaluationUserId\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n": types.UpsertAssignmentCompletionDocument,
|
||||
"\n fragment CoursePageFields on CoursePageInterface {\n title\n id\n slug\n content_type\n frontend_url\n }\n": types.CoursePageFieldsFragmentDoc,
|
||||
"\n query attendanceCheckQuery($courseSessionId: ID!) {\n course_session_attendance_course(id: $courseSessionId) {\n id\n attendance_user_list {\n user_id\n status\n }\n }\n }\n": types.AttendanceCheckQueryDocument,
|
||||
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
||||
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
|
||||
"\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateForUserQueryDocument,
|
||||
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
||||
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_deducted\n evaluation_points_final\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
|
||||
"\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_final\n evaluation_points_deducted\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateForUserQueryDocument,
|
||||
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
|
||||
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
|
||||
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n }\n }\n": types.DashboardConfigDocument,
|
||||
|
|
@ -63,15 +63,15 @@ export function graphql(source: "\n query attendanceCheckQuery($courseSessionId
|
|||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"): (typeof documents)["\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"];
|
||||
export function graphql(source: "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"): (typeof documents)["\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_deducted\n evaluation_points_final\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_deducted\n evaluation_points_final\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_final\n evaluation_points_deducted\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_final\n evaluation_points_deducted\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -512,6 +512,7 @@ type AssignmentObjectType implements CoursePageInterface {
|
|||
evaluation_tasks: JSONStreamField
|
||||
performance_objectives: JSONStreamField
|
||||
max_points: Int
|
||||
competence_certificate_weight: Float
|
||||
learning_content: LearningContentInterface
|
||||
completion(course_session_id: ID!, learning_content_page_id: ID, assignment_user_id: UUID): AssignmentCompletionObjectType
|
||||
solution_sample: ContentDocumentObjectType
|
||||
|
|
@ -559,6 +560,9 @@ type AssignmentCompletionObjectType {
|
|||
submitted_at: DateTime
|
||||
evaluation_submitted_at: DateTime
|
||||
evaluation_user: UserObjectType
|
||||
evaluation_points_deducted: Float!
|
||||
evaluation_points_deducted_reason: String!
|
||||
evaluation_points_deducted_user: UserObjectType
|
||||
evaluation_passed: Boolean
|
||||
edoniq_extended_time_flag: Boolean!
|
||||
assignment_user: UserObjectType!
|
||||
|
|
@ -570,6 +574,7 @@ type AssignmentCompletionObjectType {
|
|||
task_completion_data: GenericScalar
|
||||
learning_content_page_id: ID
|
||||
evaluation_points: Float
|
||||
evaluation_points_final: Float
|
||||
evaluation_max_points: Float
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,10 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
|
|||
}
|
||||
evaluation_points
|
||||
evaluation_max_points
|
||||
evaluation_points_deducted
|
||||
evaluation_points_deducted_reason
|
||||
evaluation_points_final
|
||||
|
||||
evaluation_passed
|
||||
edoniq_extended_time_flag
|
||||
completion_data
|
||||
|
|
@ -95,11 +99,14 @@ export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
|||
...CoursePageFields
|
||||
assignment_type
|
||||
max_points
|
||||
competence_certificate_weight
|
||||
completion(course_session_id: $courseSessionId) {
|
||||
id
|
||||
completion_status
|
||||
submitted_at
|
||||
evaluation_points
|
||||
evaluation_points_deducted
|
||||
evaluation_points_final
|
||||
evaluation_max_points
|
||||
evaluation_passed
|
||||
}
|
||||
|
|
@ -131,11 +138,14 @@ export const COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY = graphql(`
|
|||
...CoursePageFields
|
||||
assignment_type
|
||||
max_points
|
||||
competence_certificate_weight
|
||||
completion(course_session_id: $courseSessionId) {
|
||||
id
|
||||
completion_status
|
||||
submitted_at
|
||||
evaluation_points
|
||||
evaluation_points_final
|
||||
evaluation_points_deducted
|
||||
evaluation_max_points
|
||||
evaluation_passed
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,18 +81,24 @@ const getIconName = () => {
|
|||
>
|
||||
<div class="flex flex-col lg:items-center">
|
||||
<div class="heading-2">
|
||||
{{ assignment.completion?.evaluation_points }}
|
||||
{{ assignment.completion?.evaluation_points_final }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t("assignment.von x Punkten", { x: assignment.max_points }) }}
|
||||
({{
|
||||
(
|
||||
((assignment.completion?.evaluation_points ?? 0) /
|
||||
((assignment.completion?.evaluation_points_final ?? 0) /
|
||||
(assignment.completion?.evaluation_max_points ?? 1)) *
|
||||
100
|
||||
).toFixed(0)
|
||||
}}%)
|
||||
</div>
|
||||
<div
|
||||
v-if="(assignment.completion?.evaluation_points_deducted ?? 0) > 0"
|
||||
class="text-gray-900"
|
||||
>
|
||||
{{ $t("a.mit Abzug") }}
|
||||
</div>
|
||||
<div
|
||||
v-if="assignment.completion && !assignment.completion.evaluation_passed"
|
||||
class="my-2 rounded-md bg-error-red-200 px-2.5 py-0.5"
|
||||
|
|
|
|||
|
|
@ -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,18 @@ 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, true);
|
||||
});
|
||||
|
||||
const userGradeRounded2Places = computed(() => {
|
||||
return calcCompetenceCertificateGrade(props.competenceCertificate.assignments, false);
|
||||
});
|
||||
|
||||
const numAssignmentsEvaluated = computed(() => {
|
||||
return props.competenceCertificate.assignments.filter((a) => {
|
||||
return a.completion?.completion_status === "EVALUATION_SUBMITTED";
|
||||
|
|
@ -70,15 +74,29 @@ const frontendUrl = computed(() => {
|
|||
</h3>
|
||||
</div>
|
||||
|
||||
<section v-if="userPointsEvaluatedAssignments > 0" class="flex items-center">
|
||||
<div
|
||||
class="py-4"
|
||||
:class="{ 'heading-1': props.detailView, 'heading-2': !props.detailView }"
|
||||
>
|
||||
{{ userPointsEvaluatedAssignments }}
|
||||
<section v-if="userPointsEvaluatedAssignments > 0">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="py-4"
|
||||
:class="{ 'heading-1': props.detailView, 'heading-2': !props.detailView }"
|
||||
:data-cy="`certificate-${competenceCertificate.slug}-grade`"
|
||||
>
|
||||
{{ $t("a.Note") }}: {{ userGrade }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-1">
|
||||
{{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }}
|
||||
<div
|
||||
class="text-gray-900"
|
||||
:data-cy="`certificate-${competenceCertificate.slug}-grade-percent`"
|
||||
>
|
||||
{{ $t("a.Ungerundete Note") }}: {{ userGradeRounded2Places }}.
|
||||
<a
|
||||
:href="$t('a.wegleitungUkUrl')"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="underline"
|
||||
>
|
||||
{{ $t("a.Wegleitung üK") }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<section v-else class="py-2">
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
@ -86,16 +86,15 @@ onMounted(async () => {
|
|||
>
|
||||
{{ $t("a.Zwischenstand") }}
|
||||
</div>
|
||||
<h3 class="mt-2 lg:order-first lg:mt-0">{{ $t("a.Gesamtpunktzahl") }}</h3>
|
||||
<h3 class="mt-2 lg:order-first lg:mt-0">{{ $t("a.Erfahrungsnote üK") }}</h3>
|
||||
</div>
|
||||
|
||||
<section v-if="userPointsEvaluatedAssignments > 0" class="flex items-center">
|
||||
<div class="heading-1 py-4">
|
||||
{{ userPointsEvaluatedAssignments }}
|
||||
</div>
|
||||
<div class="pl-2">
|
||||
{{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }}
|
||||
</div>
|
||||
<section
|
||||
v-if="userPointsEvaluatedAssignments > 0"
|
||||
class="flex items-center"
|
||||
data-cy="certificate-total-grade"
|
||||
>
|
||||
<div class="heading-1 py-4">{{ $t("a.Note") }}: {{ totalGrade }}</div>
|
||||
</section>
|
||||
<section v-else class="my-4">
|
||||
{{ $t("a.competenceCertificateNoUserPoints") }}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -69,11 +66,14 @@ const router = useRouter();
|
|||
</div>
|
||||
<div class="mt-4" data-cy="certificate-total-points-text">
|
||||
<div v-if="userPointsEvaluatedAssignments > 0">
|
||||
{{ $t("a.Zwischenstand") }} {{ $t("a.Gesamtpunktzahl") }}:
|
||||
{{ $t("a.Erfahrungsnote üK") }}:
|
||||
<span class="font-bold">
|
||||
{{ userPointsEvaluatedAssignments }}
|
||||
{{ calcCompetencesTotalGrade(competenceCertificates ?? []) }}
|
||||
</span>
|
||||
|
||||
<span class="rounded-full bg-gray-200 px-2.5 py-0.5 text-sm lg:ml-2">
|
||||
{{ $t("a.Zwischenstand") }}
|
||||
</span>
|
||||
{{ $t("assignment.von x Punkten", { x: totalPointsEvaluatedAssignments }) }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ $t("a.competenceCertificateNoUserPoints") }}
|
||||
|
|
@ -92,14 +92,13 @@ const router = useRouter();
|
|||
{{ certificate.title }}
|
||||
</div>
|
||||
<div class="mt-4 lg:mt-0">
|
||||
<span class="text-bold">
|
||||
{{ assignmentsUserPoints(certificate.assignments) }}
|
||||
<span
|
||||
v-if="calcCompetenceCertificateGrade(certificate.assignments)"
|
||||
class="text-bold"
|
||||
>
|
||||
{{ $t("a.Note") }}:
|
||||
{{ calcCompetenceCertificateGrade(certificate.assignments) }}
|
||||
</span>
|
||||
{{
|
||||
$t("assignment.von x Punkten", {
|
||||
x: assignmentsMaxEvaluationPoints(certificate.assignments),
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { StatusCount } from "@/components/ui/ItProgress.vue";
|
||||
import type { CompetenceCertificateAssignment } from "@/types";
|
||||
import { percentToRoundedGrade } from "@/services/assignmentService";
|
||||
import type { CompetenceCertificate, CompetenceCertificateAssignment } from "@/types";
|
||||
import _ from "lodash";
|
||||
|
||||
export function assignmentsMaxEvaluationPoints(
|
||||
|
|
@ -16,10 +17,61 @@ export function assignmentsUserPoints(assignments: CompetenceCertificateAssignme
|
|||
return +_.sum(
|
||||
assignments
|
||||
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
|
||||
.map((a) => a.completion?.evaluation_points ?? 0)
|
||||
.map((a) => a.completion?.evaluation_points_final ?? 0)
|
||||
).toFixed(1);
|
||||
}
|
||||
|
||||
export function calcCompetenceCertificateGrade(
|
||||
assignments: CompetenceCertificateAssignment[],
|
||||
roundedToHalfGrade = true
|
||||
) {
|
||||
const evaluatedAssignments = assignments.filter(
|
||||
(a) => a.completion?.completion_status === "EVALUATION_SUBMITTED"
|
||||
);
|
||||
|
||||
const adjustedResults = evaluatedAssignments.map((a) => {
|
||||
return (
|
||||
((a.completion?.evaluation_points_final ?? 0) / a.max_points) *
|
||||
a.competence_certificate_weight
|
||||
);
|
||||
});
|
||||
|
||||
const adjustedAssignmentCount = _.sum(
|
||||
evaluatedAssignments.map((a) => a.competence_certificate_weight)
|
||||
);
|
||||
|
||||
if (adjustedAssignmentCount === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return percentToRoundedGrade(
|
||||
_.sum(adjustedResults) / adjustedAssignmentCount,
|
||||
roundedToHalfGrade
|
||||
);
|
||||
}
|
||||
|
||||
export function calcCompetencesTotalGrade(
|
||||
competenceCertificates: CompetenceCertificate[]
|
||||
) {
|
||||
// für das Total der Kompetenznote werden jeweils die gerundenten Noten der
|
||||
// einzelnen Kompetenznachweise verwendet und dann noch einmal gerundet.
|
||||
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 percentToRoundedGrade(percentGraded);
|
||||
}
|
||||
|
||||
export function competenceCertificateProgressStatusCount(
|
||||
assignments: CompetenceCertificateAssignment[]
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@ const total = (metrics: AssignmentCompletionMetricsType) => {
|
|||
:items="courseStatistics.assignments.records"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="flex justify-between">
|
||||
<div
|
||||
class="flex justify-between"
|
||||
:data-cy=" (item as AssignmentStatisticsRecordType).assignment_title"
|
||||
>
|
||||
<div>
|
||||
<h4 class="font-bold">
|
||||
{{ (item as AssignmentStatisticsRecordType).assignment_title }}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ async function startTest() {
|
|||
<div class="my-4">
|
||||
{{ $t("a.Resultat") }}:
|
||||
<span class="font-bold">
|
||||
{{ assignmentCompletion.evaluation_points }}
|
||||
{{ assignmentCompletion.evaluation_points_final }}
|
||||
</span>
|
||||
{{
|
||||
$t("assignment.von x Punkten", {
|
||||
|
|
@ -178,7 +178,7 @@ async function startTest() {
|
|||
}}
|
||||
({{
|
||||
(
|
||||
((assignmentCompletion.evaluation_points ?? 0) /
|
||||
((assignmentCompletion.evaluation_points_final ?? 0) /
|
||||
(assignmentCompletion.evaluation_max_points ?? 1)) *
|
||||
100
|
||||
).toFixed(0)
|
||||
|
|
@ -191,6 +191,24 @@ async function startTest() {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="assignmentCompletion.evaluation_points_deducted > 0"
|
||||
class="my-4 text-gray-900"
|
||||
>
|
||||
<div>
|
||||
{{ $t("a.Punkte aus Bewertung") }}:
|
||||
{{ assignmentCompletion.evaluation_points }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t("a.Abgezogene Punkte") }}:
|
||||
{{ assignmentCompletion.evaluation_points_deducted }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t("a.Grund") }}:
|
||||
{{ assignmentCompletion.evaluation_points_deducted_reason }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn-primary inline-flex items-center" @click="startTest()">
|
||||
{{ $t("edoniqTest.viewResults") }}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it } from "vitest";
|
||||
import { pointsToGrade } from "../assignmentService";
|
||||
import { percentToRoundedGrade, pointsToGrade } from "../assignmentService";
|
||||
|
||||
describe("assignmentService", () => {
|
||||
it("pointsToGrade", () => {
|
||||
|
|
@ -29,4 +29,60 @@ describe("assignmentService", () => {
|
|||
expect(pointsToGrade(1, 24)).toBe(1);
|
||||
expect(pointsToGrade(0, 24)).toBe(1);
|
||||
});
|
||||
|
||||
it("percentToRoundedGrade with half grades", () => {
|
||||
expect(percentToRoundedGrade(24 / 24)).toBe(6);
|
||||
expect(percentToRoundedGrade(23 / 24)).toBe(6);
|
||||
expect(percentToRoundedGrade(22 / 24)).toBe(5.5);
|
||||
expect(percentToRoundedGrade(21 / 24)).toBe(5.5);
|
||||
expect(percentToRoundedGrade(20 / 24)).toBe(5);
|
||||
expect(percentToRoundedGrade(19 / 24)).toBe(5);
|
||||
expect(percentToRoundedGrade(18 / 24)).toBe(5);
|
||||
expect(percentToRoundedGrade(17 / 24)).toBe(4.5);
|
||||
expect(percentToRoundedGrade(16 / 24)).toBe(4.5);
|
||||
expect(percentToRoundedGrade(15 / 24)).toBe(4);
|
||||
expect(percentToRoundedGrade(14 / 24)).toBe(4);
|
||||
expect(percentToRoundedGrade(13 / 24)).toBe(3.5);
|
||||
expect(percentToRoundedGrade(12 / 24)).toBe(3.5);
|
||||
expect(percentToRoundedGrade(11 / 24)).toBe(3.5);
|
||||
expect(percentToRoundedGrade(10 / 24)).toBe(3);
|
||||
expect(percentToRoundedGrade(9 / 24)).toBe(3);
|
||||
expect(percentToRoundedGrade(8 / 24)).toBe(2.5);
|
||||
expect(percentToRoundedGrade(7 / 24)).toBe(2.5);
|
||||
expect(percentToRoundedGrade(6 / 24)).toBe(2.5);
|
||||
expect(percentToRoundedGrade(5 / 24)).toBe(2);
|
||||
expect(percentToRoundedGrade(4 / 24)).toBe(2);
|
||||
expect(percentToRoundedGrade(3 / 24)).toBe(1.5);
|
||||
expect(percentToRoundedGrade(2 / 24)).toBe(1.5);
|
||||
expect(percentToRoundedGrade(1 / 24)).toBe(1);
|
||||
expect(percentToRoundedGrade(0 / 24)).toBe(1);
|
||||
});
|
||||
|
||||
it("percentToRoundedGrade with 2 decimal places", () => {
|
||||
expect(percentToRoundedGrade(24 / 24, false)).toBeCloseTo(6);
|
||||
expect(percentToRoundedGrade(23 / 24, false)).toBeCloseTo(5.79);
|
||||
expect(percentToRoundedGrade(22 / 24, false)).toBeCloseTo(5.58);
|
||||
expect(percentToRoundedGrade(21 / 24, false)).toBeCloseTo(5.38);
|
||||
expect(percentToRoundedGrade(20 / 24, false)).toBeCloseTo(5.17);
|
||||
expect(percentToRoundedGrade(19 / 24, false)).toBeCloseTo(4.96);
|
||||
expect(percentToRoundedGrade(18 / 24, false)).toBeCloseTo(4.75);
|
||||
expect(percentToRoundedGrade(17 / 24, false)).toBeCloseTo(4.54);
|
||||
expect(percentToRoundedGrade(16 / 24, false)).toBeCloseTo(4.33);
|
||||
expect(percentToRoundedGrade(15 / 24, false)).toBeCloseTo(4.13);
|
||||
expect(percentToRoundedGrade(14 / 24, false)).toBeCloseTo(3.92);
|
||||
expect(percentToRoundedGrade(13 / 24, false)).toBeCloseTo(3.71);
|
||||
expect(percentToRoundedGrade(12 / 24, false)).toBeCloseTo(3.5);
|
||||
expect(percentToRoundedGrade(11 / 24, false)).toBeCloseTo(3.29);
|
||||
expect(percentToRoundedGrade(10 / 24, false)).toBeCloseTo(3.08);
|
||||
expect(percentToRoundedGrade(9 / 24, false)).toBeCloseTo(2.88);
|
||||
expect(percentToRoundedGrade(8 / 24, false)).toBeCloseTo(2.67);
|
||||
expect(percentToRoundedGrade(7 / 24, false)).toBeCloseTo(2.46);
|
||||
expect(percentToRoundedGrade(6 / 24, false)).toBeCloseTo(2.25);
|
||||
expect(percentToRoundedGrade(5 / 24, false)).toBeCloseTo(2.04);
|
||||
expect(percentToRoundedGrade(4 / 24, false)).toBeCloseTo(1.83);
|
||||
expect(percentToRoundedGrade(3 / 24, false)).toBeCloseTo(1.63);
|
||||
expect(percentToRoundedGrade(2 / 24, false)).toBeCloseTo(1.42);
|
||||
expect(percentToRoundedGrade(1 / 24, false)).toBeCloseTo(1.21);
|
||||
expect(percentToRoundedGrade(0 / 24, false)).toBeCloseTo(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export async function loadAssignmentCompletionStatusData(
|
|||
if (userAssignmentStatus?.completion_status === "EVALUATION_SUBMITTED") {
|
||||
gradedUsers.push({
|
||||
user: csu,
|
||||
points: userAssignmentStatus.evaluation_points ?? 0,
|
||||
points: userAssignmentStatus.evaluation_points_final ?? 0,
|
||||
maxPoints: userAssignmentStatus.evaluation_max_points ?? 0,
|
||||
passed: userAssignmentStatus.evaluation_passed ?? false,
|
||||
});
|
||||
|
|
@ -90,3 +90,15 @@ export function pointsToGrade(points: number, maxPoints: number) {
|
|||
const halfGrade = grade / 2;
|
||||
return Math.min(halfGrade, 5) + 1;
|
||||
}
|
||||
|
||||
export function percentToRoundedGrade(percent: number, roundedToHalfGrade = true) {
|
||||
if (roundedToHalfGrade) {
|
||||
// Round to half-grades
|
||||
const grade = Math.round(percent * 10);
|
||||
const halfGrade = grade / 2;
|
||||
return Math.min(halfGrade, 5) + 1;
|
||||
} else {
|
||||
// Round to 2 decimal places
|
||||
return Math.round((percent * 5 + 1) * 100) / 100;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -402,6 +403,9 @@ export interface CompetenceCertificateAssignment extends BaseCourseWagtailPage {
|
|||
completion_status: AssignmentCompletionStatus;
|
||||
evaluation_submitted_at: string | null;
|
||||
evaluation_points: number | null;
|
||||
evaluation_points_final: number | null;
|
||||
evaluation_points_deducted: number | null;
|
||||
evaluation_points_reason: string;
|
||||
evaluation_max_points: number | null;
|
||||
evaluation_passed: boolean | null;
|
||||
} | null;
|
||||
|
|
@ -566,6 +570,8 @@ export interface UserAssignmentCompletionStatus {
|
|||
assignment_user_id: string;
|
||||
completion_status: AssignmentCompletionStatus;
|
||||
evaluation_points: number | null;
|
||||
evaluation_points_final: number | null;
|
||||
evaluation_points_deducted: number | null;
|
||||
evaluation_max_points: number | null;
|
||||
evaluation_passed: boolean;
|
||||
learning_content_page_id: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
import { login } from "../helpers";
|
||||
|
||||
describe("cockpitPointsDeducted.cy.js", () => {
|
||||
it("will show results with points", () => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 6,4,6,3,3 --create-edoniq-test-results 19 24 0"
|
||||
);
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
|
||||
// check edoniq test with deducted points
|
||||
cy.get(
|
||||
'[data-cy="submittable-test-lehrgang-lp-circle-fahrzeug-lc-wissens-und-verständnisfragen"]'
|
||||
).should("contain", "1 von 3 Bewertungen freigegeben");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-wissens-und-verständnisfragen"]'
|
||||
).click();
|
||||
cy.get('[data-cy="Student1"]')
|
||||
.should("contain", "19 von 24 Punkten")
|
||||
.and("contain", "79%")
|
||||
.and("not.contain", "Nicht bestanden");
|
||||
|
||||
// check casework with deducted points
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="submittable-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).should("contain", "1 von 3 Bewertungen freigegeben");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
cy.get('[data-cy="Student1"]')
|
||||
.should("contain", "22 von 24 Punkten")
|
||||
.and("contain", "92%")
|
||||
.and("not.contain", "Nicht bestanden");
|
||||
});
|
||||
|
||||
it("will show results with deducted points", () => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 6,4,6,3,3 --assignment-points-deducted 14 --create-edoniq-test-results 19 24 8"
|
||||
);
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
|
||||
// check edoniq test with deducted points
|
||||
cy.get(
|
||||
'[data-cy="submittable-test-lehrgang-lp-circle-fahrzeug-lc-wissens-und-verständnisfragen"]'
|
||||
).should("contain", "1 von 3 Bewertungen freigegeben");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-wissens-und-verständnisfragen"]'
|
||||
).click();
|
||||
cy.get('[data-cy="Student1"]')
|
||||
.should("contain", "11 von 24 Punkten")
|
||||
.and("contain", "46%")
|
||||
.and("contain", "Nicht bestanden");
|
||||
|
||||
// check casework with deducted points
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="submittable-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).should("contain", "1 von 3 Bewertungen freigegeben");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
cy.get('[data-cy="Student1"]')
|
||||
.should("contain", "8 von 24 Punkten")
|
||||
.and("contain", "33%")
|
||||
.and("contain", "Nicht bestanden");
|
||||
});
|
||||
});
|
||||
|
|
@ -14,9 +14,7 @@ describe("competenceCertificate.cy.js", () => {
|
|||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||
)
|
||||
.should("contain", "0 von 0 Punkten")
|
||||
.and("contain", "0 von 2 Kompetenznachweis-Elementen");
|
||||
).and("contain", "0 von 2 Kompetenznachweis-Elementen");
|
||||
|
||||
// check on certificates page
|
||||
cy.get('[data-cy="certificates-show-all-button"]').click();
|
||||
|
|
@ -48,31 +46,37 @@ describe("competenceCertificate.cy.js", () => {
|
|||
|
||||
it("check with finished passed edoniq test", () => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24"
|
||||
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 0"
|
||||
);
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/competence");
|
||||
|
||||
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||
"Zwischenstand Gesamtpunktzahl: 19 von 24 Punkten"
|
||||
"Erfahrungsnote üK: 5"
|
||||
);
|
||||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||
)
|
||||
.should("contain", "19 von 24 Punkten")
|
||||
.should("contain", "Note: 5")
|
||||
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
|
||||
|
||||
// check on certificates page
|
||||
cy.get('[data-cy="certificates-show-all-button"]').click();
|
||||
cy.get('[data-cy="certificate-total-points-text"]')
|
||||
.should("contain", "19")
|
||||
.should("contain", "Erfahrungsnote üK")
|
||||
.and("contain", "Zwischenstand");
|
||||
cy.get('[data-cy="certificate-total-grade"]').should("contain", "Note: 5");
|
||||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||
).should("contain", "Note: 5");
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||
).should("contain", "Ungerundete Note: 4.96");
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||
)
|
||||
.should("contain", "19")
|
||||
.and("contain", "Zwischenstand")
|
||||
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
|
||||
|
||||
|
|
@ -104,7 +108,7 @@ describe("competenceCertificate.cy.js", () => {
|
|||
|
||||
it("check with finished failed edoniq test", () => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-completion --create-edoniq-test-results 10 24"
|
||||
"cypress_reset --create-assignment-completion --create-edoniq-test-results 10 24 0"
|
||||
);
|
||||
login("test-student1@example.com", "test");
|
||||
|
||||
|
|
@ -113,6 +117,13 @@ describe("competenceCertificate.cy.js", () => {
|
|||
"/course/test-lehrgang/competence/certificates/kompetenznachweis-1"
|
||||
);
|
||||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||
).should("contain", "Note: 3");
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||
).should("contain", "Ungerundete Note: 3.08");
|
||||
|
||||
cy.get(
|
||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
||||
)
|
||||
|
|
@ -133,33 +144,39 @@ describe("competenceCertificate.cy.js", () => {
|
|||
|
||||
it("check with finished edoniq test and finished casework", () => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --create-edoniq-test-results 19 24"
|
||||
"cypress_reset --create-assignment-evaluation --create-edoniq-test-results 19 24 0"
|
||||
);
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/competence");
|
||||
|
||||
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||
"Zwischenstand Gesamtpunktzahl: 43 von 48 Punkten"
|
||||
"Erfahrungsnote üK: 5.5"
|
||||
);
|
||||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||
)
|
||||
.should("contain", "43 von 48 Punkten")
|
||||
.should("contain", "Note: 5.5")
|
||||
.and("contain", "2 von 2 Kompetenznachweis-Elementen");
|
||||
|
||||
// check on certificates page
|
||||
cy.get('[data-cy="certificates-show-all-button"]').click();
|
||||
cy.get('[data-cy="certificate-total-points-text"]')
|
||||
.should("contain", "43")
|
||||
.should("contain", "Erfahrungsnote üK")
|
||||
.and("contain", "Note: 5.5")
|
||||
.and("not.contain", "Zwischenstand");
|
||||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||
)
|
||||
.should("contain", "43")
|
||||
.and("not.contain", "Zwischenstand")
|
||||
.and("contain", "2 von 2 Kompetenznachweis-Elementen");
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||
).should("contain", "Note: 5.5");
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||
).should("contain", "Ungerundete Note: 5.48");
|
||||
|
||||
// check certificate detail page
|
||||
cy.get(
|
||||
|
|
@ -180,6 +197,110 @@ describe("competenceCertificate.cy.js", () => {
|
|||
.and("contain", "Bewertung freigegeben");
|
||||
});
|
||||
|
||||
it("check with finished edoniq test with deducted points", () => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 8"
|
||||
);
|
||||
login("test-student1@example.com", "test");
|
||||
|
||||
// go to certificate detail page
|
||||
cy.visit(
|
||||
"/course/test-lehrgang/competence/certificates/kompetenznachweis-1"
|
||||
);
|
||||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||
).should("contain", "Note: 3.5");
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||
).should("contain", "Ungerundete Note: 3.29");
|
||||
|
||||
cy.get(
|
||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
||||
)
|
||||
.should("contain", "11")
|
||||
.and("contain", "Bewertung freigegeben")
|
||||
.and("contain", "46%")
|
||||
.and("contain", "mit Abzug")
|
||||
.and("contain", "Nicht bestanden");
|
||||
|
||||
// it can open learning content page directly
|
||||
cy.get(
|
||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
|
||||
).click();
|
||||
cy.get('[data-cy="test-result"]')
|
||||
.should("contain", "11 von 24 Punkten")
|
||||
.and("contain", "46%")
|
||||
.and("contain", "Punkte aus Bewertung: 19")
|
||||
.and("contain", "Abgezogene Punkte: 8")
|
||||
.and("contain", "Grund: Edoniq Punkteabzug Test")
|
||||
.and("contain", "Nicht bestanden");
|
||||
});
|
||||
|
||||
it("check with finished casework and points deducted", () => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 4,6,4,3,2 --assignment-points-deducted 5"
|
||||
);
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/competence");
|
||||
|
||||
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||
"Erfahrungsnote üK: 4"
|
||||
);
|
||||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||
)
|
||||
.should("contain", "Note: 4")
|
||||
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
|
||||
|
||||
// check on certificates page
|
||||
cy.get('[data-cy="certificates-show-all-button"]').click();
|
||||
cy.get('[data-cy="certificate-total-points-text"]')
|
||||
.should("contain", "Erfahrungsnote üK")
|
||||
.and("contain", "Note: 4")
|
||||
.and("contain", "Zwischenstand");
|
||||
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||
)
|
||||
.and("contain", "Zwischenstand")
|
||||
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||
).should("contain", "Note: 4");
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||
).should("contain", "Ungerundete Note: 3.92");
|
||||
|
||||
// check certificate detail page
|
||||
cy.get(
|
||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
||||
).click();
|
||||
|
||||
cy.get(
|
||||
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
||||
)
|
||||
.should("contain", "14")
|
||||
.and("contain", "von 24 Punkten")
|
||||
.and("contain", "58%")
|
||||
.and("contain", "mit Abzug")
|
||||
.and("contain", "Bewertung freigegeben");
|
||||
|
||||
cy.get(
|
||||
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"] [data-cy="open-learning-content"]'
|
||||
).click();
|
||||
cy.get('[data-cy="user-points"]').should("contain", "14");
|
||||
cy.get('[data-cy="total-points"]').should(
|
||||
"contain",
|
||||
"von 24 Punkten (58%)"
|
||||
);
|
||||
cy.get('[data-cy="points-deducted"]')
|
||||
.should("contain", "Punkte aus Bewertung: 19")
|
||||
.and("contain", "Abgezogene Punkte: 5")
|
||||
.and("contain", "Grund: Assignment Punkteabzug Test");
|
||||
});
|
||||
|
||||
it("should display link to details", () => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
login("test-student1@example.com", "test");
|
||||
|
|
|
|||
|
|
@ -13,88 +13,122 @@ const clickOnDetailsLink = (within) => {
|
|||
};
|
||||
|
||||
describe("dashboardSupervisor.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days"
|
||||
);
|
||||
login("test-supervisor1@example.com", "test");
|
||||
cy.visit("/");
|
||||
describe("with data", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days"
|
||||
);
|
||||
login("test-supervisor1@example.com", "test");
|
||||
cy.visit("/");
|
||||
});
|
||||
|
||||
describe("assignment summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
// we have no completed assignments, but some are in progress
|
||||
// -> makes sure that the numbers are correct
|
||||
getDashboardStatistics("assignments.completed").should(
|
||||
"have.text",
|
||||
"1"
|
||||
);
|
||||
getDashboardStatistics("assignments.passed").should("have.text", "34%");
|
||||
});
|
||||
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("assignments");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
cy.contains("Noch nicht bestätigt");
|
||||
cy.contains("Überprüfen einer Motorfahrzeugs-Versicherungspolice");
|
||||
cy.contains("Test Bern 2022 a");
|
||||
});
|
||||
});
|
||||
|
||||
describe("attendance day summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("attendance.dayCompleted").should(
|
||||
"have.text",
|
||||
"1"
|
||||
);
|
||||
getDashboardStatistics("attendance.participantsPresent").should(
|
||||
"have.text",
|
||||
"34%"
|
||||
);
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("attendance");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/attendance");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
cy.contains("Durchführung «Test Bern 2022 a»");
|
||||
cy.contains("Circle «Fahrzeug»");
|
||||
cy.contains("Termin: 31. Oktober 2000");
|
||||
});
|
||||
});
|
||||
|
||||
describe("feedback summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("feedback.average").should("have.text", "3.3");
|
||||
getDashboardStatistics("feedback.count").should("have.text", "6");
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("feedback");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/feedback");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
cy.contains("3.3 von 4");
|
||||
cy.contains("Test Trainer1");
|
||||
cy.contains("Durchführung «Test Bern 2022 a»");
|
||||
cy.contains("Circle «Fahrzeug»");
|
||||
});
|
||||
});
|
||||
|
||||
describe("competence summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("competence.success").should("have.text", "1");
|
||||
getDashboardStatistics("competence.fail").should("have.text", "0");
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("competence");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/competence");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
cy.contains("Selbsteinschätzung: Vorbereitung");
|
||||
cy.contains("Durchführung «Test Bern 2022 a»");
|
||||
cy.contains("Circle «Fahrzeug»");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("assignment summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
describe("with deducted points", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 6,6,6,3,3 --assignment-points-deducted 14 --create-edoniq-test-results 19 24 8"
|
||||
);
|
||||
login("test-supervisor1@example.com", "test");
|
||||
cy.visit("/");
|
||||
});
|
||||
|
||||
it("contains numbers with deduction", () => {
|
||||
// we have no completed assignments, but some are in progress
|
||||
// -> makes sure that the numbers are correct
|
||||
getDashboardStatistics("assignments.completed").should("have.text", "1");
|
||||
getDashboardStatistics("assignments.passed").should("have.text", "34%");
|
||||
});
|
||||
getDashboardStatistics("assignments.completed").should("have.text", "2");
|
||||
getDashboardStatistics("assignments.passed").should("have.text", "0%");
|
||||
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("assignments");
|
||||
// check data on the details page
|
||||
cy.get(
|
||||
'[data-cy="dashboard.stats.assignments"] [data-cy="basebox.detailsLink"]'
|
||||
).click();
|
||||
cy.get(
|
||||
'[data-cy="Edoniq Wissens- und Verständisfragen - Circle Fahrzeug (Demo)"]'
|
||||
).should("contain", "0 von 3 bestanden");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
cy.contains("Noch nicht bestätigt");
|
||||
cy.contains("Überprüfen einer Motorfahrzeugs-Versicherungspolice");
|
||||
cy.contains("Test Bern 2022 a");
|
||||
});
|
||||
});
|
||||
|
||||
describe("attendance day summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("attendance.dayCompleted").should(
|
||||
"have.text",
|
||||
"1"
|
||||
);
|
||||
getDashboardStatistics("attendance.participantsPresent").should(
|
||||
"have.text",
|
||||
"34%"
|
||||
);
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("attendance");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/attendance");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
cy.contains("Durchführung «Test Bern 2022 a»");
|
||||
cy.contains("Circle «Fahrzeug»");
|
||||
cy.contains("Termin: 31. Oktober 2000");
|
||||
});
|
||||
});
|
||||
|
||||
describe("feedback summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("feedback.average").should("have.text", "3.3");
|
||||
getDashboardStatistics("feedback.count").should("have.text", "6");
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("feedback");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/feedback");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
cy.contains("3.3 von 4");
|
||||
cy.contains("Test Trainer1");
|
||||
cy.contains("Durchführung «Test Bern 2022 a»");
|
||||
cy.contains("Circle «Fahrzeug»");
|
||||
});
|
||||
});
|
||||
|
||||
describe("competence summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("competence.success").should("have.text", "1");
|
||||
getDashboardStatistics("competence.fail").should("have.text", "0");
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("competence");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/competence");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
cy.contains("Selbsteinschätzung: Vorbereitung");
|
||||
cy.contains("Durchführung «Test Bern 2022 a»");
|
||||
cy.contains("Circle «Fahrzeug»");
|
||||
cy.get(
|
||||
'[data-cy="Überprüfen einer Motorfahrzeugs-Versicherungspolice"]'
|
||||
).should("contain", "0 von 3 bestanden");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,8 +13,43 @@ class AssignmentCompletionAdmin(admin.ModelAdmin):
|
|||
date_hierarchy = "created_at"
|
||||
list_display = [
|
||||
"id",
|
||||
"completion_status",
|
||||
"assignment",
|
||||
"get_circle",
|
||||
"assignment_user",
|
||||
"course_session",
|
||||
"completion_status",
|
||||
"evaluation_points",
|
||||
"evaluation_points_deducted",
|
||||
]
|
||||
list_filter = [
|
||||
"completion_status",
|
||||
"assignment__assignment_type",
|
||||
"course_session__course",
|
||||
"course_session",
|
||||
]
|
||||
search_fields = ["assignment_user__email"]
|
||||
readonly_fields = [
|
||||
"assignment_user",
|
||||
"assignment",
|
||||
"completion_data",
|
||||
"course_session",
|
||||
"learning_content_page",
|
||||
"evaluation_points",
|
||||
"submitted_at",
|
||||
"evaluation_user",
|
||||
"evaluation_submitted_at",
|
||||
"evaluation_points_deducted_user",
|
||||
]
|
||||
|
||||
def get_circle(self, obj):
|
||||
try:
|
||||
return obj.learning_content_page.specific.get_circle().title
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
get_circle.short_description = "Circle"
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if change and "evaluation_points_deducted" in form.changed_data:
|
||||
obj.evaluation_points_deducted_user = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class AssignmentCompletionObjectType(DjangoObjectType):
|
|||
|
||||
# rounded to sensible representation
|
||||
evaluation_points = graphene.Float()
|
||||
evaluation_points_final = graphene.Float()
|
||||
evaluation_max_points = graphene.Float()
|
||||
|
||||
class Meta:
|
||||
|
|
@ -38,6 +39,9 @@ class AssignmentCompletionObjectType(DjangoObjectType):
|
|||
"evaluation_user",
|
||||
"additional_json_data",
|
||||
"edoniq_extended_time_flag",
|
||||
"evaluation_points_deducted",
|
||||
"evaluation_points_deducted_reason",
|
||||
"evaluation_points_deducted_user",
|
||||
"evaluation_passed",
|
||||
"task_completion_data",
|
||||
)
|
||||
|
|
@ -47,6 +51,11 @@ class AssignmentCompletionObjectType(DjangoObjectType):
|
|||
return round(self.evaluation_points, 1) # noqa
|
||||
return None
|
||||
|
||||
def resolve_evaluation_points_final(self, info):
|
||||
if self.evaluation_points:
|
||||
return round(self.evaluation_points_final, 1) # noqa
|
||||
return None
|
||||
|
||||
def resolve_evaluation_max_points(self, info):
|
||||
if self.evaluation_max_points:
|
||||
return round(self.evaluation_max_points, 1) # noqa
|
||||
|
|
@ -58,6 +67,7 @@ class AssignmentObjectType(DjangoObjectType):
|
|||
evaluation_tasks = JSONStreamField()
|
||||
performance_objectives = JSONStreamField()
|
||||
max_points = graphene.Int()
|
||||
competence_certificate_weight = graphene.Float()
|
||||
learning_content = graphene.Field(LearningContentInterface)
|
||||
completion = graphene.Field(
|
||||
AssignmentCompletionObjectType,
|
||||
|
|
@ -87,6 +97,9 @@ class AssignmentObjectType(DjangoObjectType):
|
|||
def resolve_max_points(self, info):
|
||||
return self.get_max_points()
|
||||
|
||||
def resolve_competence_certificate_weight(self, info):
|
||||
return self.competence_certificate_weight
|
||||
|
||||
def resolve_learning_content(self, info):
|
||||
return self.find_attached_learning_content()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.2.20 on 2024-05-03 14:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("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"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# Generated by Django 3.2.20 on 2024-05-21 14:52
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("assignment", "0013_assignment_competence_certificate_weight"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="assignmentcompletion",
|
||||
name="evaluation_points_deducted",
|
||||
field=models.FloatField(default=0.0, verbose_name="Punkteabzug"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="assignmentcompletion",
|
||||
name="evaluation_points_deducted_reason",
|
||||
field=models.TextField(
|
||||
blank=True, default="", verbose_name="Punkteabzug Begründung"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="assignmentcompletion",
|
||||
name="evaluation_points_deducted_user",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="assignmentcompletion",
|
||||
name="completion_data",
|
||||
field=models.JSONField(blank=True, default=dict),
|
||||
),
|
||||
]
|
||||
|
|
@ -153,6 +153,10 @@ class Assignment(CourseBasePage):
|
|||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
competence_certificate_weight = models.FloatField(
|
||||
default=1.0,
|
||||
help_text="Gewichtung für den Kompetenznachweis",
|
||||
)
|
||||
|
||||
intro_text = RichTextField(
|
||||
help_text="Erläuterung der Ausgangslage",
|
||||
|
|
@ -336,10 +340,34 @@ class AssignmentCompletion(models.Model):
|
|||
related_name="+",
|
||||
)
|
||||
evaluation_points = models.FloatField(null=True, blank=True)
|
||||
evaluation_points_deducted = models.FloatField(
|
||||
default=0.0, verbose_name="Punkteabzug"
|
||||
)
|
||||
evaluation_points_deducted_reason = models.TextField(
|
||||
default="", blank=True, verbose_name="Punkteabzug Begründung"
|
||||
)
|
||||
evaluation_points_deducted_user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
)
|
||||
|
||||
evaluation_max_points = models.FloatField(null=True, blank=True)
|
||||
evaluation_passed = models.BooleanField(null=True, blank=True)
|
||||
edoniq_extended_time_flag = models.BooleanField(default=False)
|
||||
|
||||
@property
|
||||
def evaluation_points_final(self):
|
||||
"""
|
||||
Das ist das relevante Feld für die Punkteberechnung, es berücksichtigt
|
||||
den Punkteabzug aus `evaluation_points_deducted`
|
||||
"""
|
||||
if self.evaluation_points is None:
|
||||
return None
|
||||
return self.evaluation_points - self.evaluation_points_deducted
|
||||
|
||||
assignment_user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE)
|
||||
course_session = models.ForeignKey("course.CourseSession", on_delete=models.CASCADE)
|
||||
|
|
@ -360,7 +388,7 @@ class AssignmentCompletion(models.Model):
|
|||
default=AssignmentCompletionStatus.IN_PROGRESS.value,
|
||||
)
|
||||
|
||||
completion_data = models.JSONField(default=dict)
|
||||
completion_data = models.JSONField(default=dict, blank=True)
|
||||
additional_json_data = models.JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -387,6 +415,14 @@ class AssignmentCompletion(models.Model):
|
|||
data[task.id] = get_task_data(task, self.completion_data)
|
||||
return data
|
||||
|
||||
def save(
|
||||
self,
|
||||
**kwargs,
|
||||
):
|
||||
if self.evaluation_points_deducted > 0:
|
||||
recalculate_assignment_passed(self)
|
||||
super().save(**kwargs)
|
||||
|
||||
|
||||
def get_file_info(file_id):
|
||||
file_info = UploadFile.objects.filter(id=file_id).first()
|
||||
|
|
@ -444,3 +480,13 @@ class AssignmentCompletionAuditLog(models.Model):
|
|||
evaluation_points = models.FloatField(null=True, blank=True)
|
||||
evaluation_max_points = models.FloatField(null=True, blank=True)
|
||||
evaluation_passed = models.BooleanField(null=True, blank=True)
|
||||
|
||||
|
||||
def recalculate_assignment_passed(ac: AssignmentCompletion):
|
||||
if ac.evaluation_points_final is not None and ac.evaluation_max_points is not None:
|
||||
# if more or equal than 55% of the points are reached, the assignment is passed
|
||||
ac.evaluation_passed = (
|
||||
ac.evaluation_points_final / ac.evaluation_max_points
|
||||
) >= 0.55
|
||||
|
||||
return ac
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ class AssignmentCompletionSerializer(serializers.ModelSerializer):
|
|||
"evaluation_user",
|
||||
"additional_json_data",
|
||||
"evaluation_points",
|
||||
"evaluation_points_deducted",
|
||||
"evaluation_points_deducted_reason",
|
||||
"evaluation_max_points",
|
||||
"evaluation_passed",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from vbv_lernwelt.assignment.models import (
|
|||
AssignmentCompletionStatus,
|
||||
AssignmentType,
|
||||
is_valid_assignment_completion_status,
|
||||
recalculate_assignment_passed,
|
||||
)
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.core.utils import find_first
|
||||
|
|
@ -146,11 +147,7 @@ def update_assignment_completion(
|
|||
|
||||
# if no evaluation_passed is provided, we calculate it from the points
|
||||
if evaluation_passed is None and ac.evaluation_max_points > 0:
|
||||
if evaluation_points is not None and ac.evaluation_max_points is not None:
|
||||
# if more or equal than 60% of the points are reached, the assignment is passed
|
||||
ac.evaluation_passed = (
|
||||
evaluation_points / ac.evaluation_max_points
|
||||
) >= 0.55
|
||||
recalculate_assignment_passed(ac)
|
||||
else:
|
||||
ac.evaluation_passed = evaluation_passed
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ def request_assignment_completion_status(request, assignment_id, course_session_
|
|||
"assignment_user_id",
|
||||
"completion_status",
|
||||
"evaluation_points",
|
||||
"evaluation_points_deducted",
|
||||
"evaluation_max_points",
|
||||
"evaluation_passed",
|
||||
"learning_content_page_id",
|
||||
|
|
@ -29,6 +30,13 @@ def request_assignment_completion_status(request, assignment_id, course_session_
|
|||
# Convert the learning_content_page_id to a string
|
||||
data = list(qs) # Evaluate the queryset
|
||||
for item in data:
|
||||
if item["evaluation_points"] is not None:
|
||||
# only `evaluation_points_final` is relevant for the frontend
|
||||
item["evaluation_points_final"] = (
|
||||
item["evaluation_points"] - item["evaluation_points_deducted"]
|
||||
)
|
||||
else:
|
||||
item["evaluation_points_final"] = None
|
||||
item["learning_content_page_id"] = str(item["learning_content_page_id"])
|
||||
|
||||
return Response(status=200, data=data)
|
||||
|
|
|
|||
|
|
@ -66,11 +66,16 @@ from vbv_lernwelt.self_evaluation_feedback.models import (
|
|||
default=None,
|
||||
help="Provide assignment evaluation scores in the format: 6,6,6,3,3",
|
||||
)
|
||||
@click.option(
|
||||
"--assignment-points-deducted",
|
||||
default=0,
|
||||
help="Provide assignment points deducted",
|
||||
)
|
||||
@click.option(
|
||||
"--create-edoniq-test-results",
|
||||
type=(int, int),
|
||||
default=(None, None),
|
||||
metavar="USER_POINTS MAX_POINTS",
|
||||
type=(int, int, float),
|
||||
default=(None, None, 0.0),
|
||||
metavar="USER_POINTS MAX_POINTS POINTS_DEDUCTED",
|
||||
help="Create edoniq result data for test-student1@example.com with user points and max points",
|
||||
)
|
||||
@click.option(
|
||||
|
|
@ -112,6 +117,7 @@ def command(
|
|||
create_assignment_completion,
|
||||
create_assignment_evaluation,
|
||||
assignment_evaluation_scores,
|
||||
assignment_points_deducted,
|
||||
create_edoniq_test_results,
|
||||
create_feedback_responses,
|
||||
create_course_completion_performance_criteria,
|
||||
|
|
@ -171,9 +177,10 @@ def command(
|
|||
assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
||||
evaluation_user=User.objects.get(id=TEST_TRAINER1_USER_ID),
|
||||
input_scores=assignment_evaluation_scores,
|
||||
points_deducted=assignment_points_deducted,
|
||||
)
|
||||
|
||||
user_points, max_points = create_edoniq_test_results
|
||||
user_points, max_points, points_deducted = create_edoniq_test_results
|
||||
if user_points is not None and max_points is not None:
|
||||
print(
|
||||
f"Create edoniq test results: User Points: {user_points}, Max Points: {max_points}"
|
||||
|
|
@ -186,6 +193,7 @@ def command(
|
|||
assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
||||
user_points=user_points,
|
||||
max_points=max_points,
|
||||
evaluation_points_deducted=points_deducted,
|
||||
)
|
||||
|
||||
if create_feedback_responses:
|
||||
|
|
|
|||
|
|
@ -58,3 +58,10 @@ def pretty_print_json(json_string):
|
|||
# pylint: disable=broad-except
|
||||
except Exception:
|
||||
return json_string
|
||||
|
||||
|
||||
def safe_deque_popleft(deq, default=None):
|
||||
try:
|
||||
return deq.popleft()
|
||||
except IndexError:
|
||||
return default
|
||||
|
|
|
|||
|
|
@ -151,6 +151,9 @@ def cypress_reset_view(request):
|
|||
assignment_evaluation_scores = request.data.get("assignment_evaluation_scores")
|
||||
if assignment_evaluation_scores:
|
||||
options["assignment_evaluation_scores"] = assignment_evaluation_scores
|
||||
options["assignment_points_deducted"] = float(
|
||||
request.data.get("assignment_points_deducted") or 0
|
||||
)
|
||||
|
||||
options["create_feedback_responses"] = (
|
||||
request.data.get("create_feedback_responses") == "true"
|
||||
|
|
@ -159,10 +162,12 @@ def cypress_reset_view(request):
|
|||
# edoniq test results
|
||||
edoniq_test_user_points = request.data.get("edoniq_test_user_points")
|
||||
edoniq_test_max_points = request.data.get("edoniq_test_max_points")
|
||||
edoniq_points_deducted = request.data.get("edoniq_test_points_deducted") or 0
|
||||
if bool(edoniq_test_user_points and edoniq_test_max_points):
|
||||
options["create_edoniq_test_results"] = (
|
||||
int(edoniq_test_user_points),
|
||||
int(edoniq_test_max_points),
|
||||
float(edoniq_points_deducted),
|
||||
)
|
||||
|
||||
options["create_course_completion_performance_criteria"] = (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from collections import deque
|
||||
from datetime import datetime
|
||||
|
||||
from dateutil.relativedelta import MO, relativedelta, TH, TU, WE
|
||||
|
|
@ -42,6 +43,7 @@ from vbv_lernwelt.core.constants import (
|
|||
TEST_TRAINER1_USER_ID,
|
||||
)
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.core.utils import safe_deque_popleft
|
||||
from vbv_lernwelt.course.consts import COURSE_TEST_ID
|
||||
from vbv_lernwelt.course.factories import CoursePageFactory
|
||||
from vbv_lernwelt.course.models import (
|
||||
|
|
@ -350,20 +352,27 @@ def create_test_assignment_submitted_data(assignment, course_session, user):
|
|||
|
||||
|
||||
def create_test_assignment_evaluation_data(
|
||||
assignment, course_session, assignment_user, evaluation_user, input_scores=None
|
||||
assignment,
|
||||
course_session,
|
||||
assignment_user,
|
||||
evaluation_user,
|
||||
input_scores=None,
|
||||
points_deducted=0,
|
||||
):
|
||||
if assignment and course_session and assignment_user and evaluation_user:
|
||||
subtasks = assignment.get_evaluation_tasks()
|
||||
evaluation_points = 0
|
||||
|
||||
input_scores_deque = deque(input_scores) if input_scores else deque()
|
||||
|
||||
for index, evaluation_task in enumerate(subtasks):
|
||||
task_score = evaluation_task["value"]["max_points"]
|
||||
if input_scores[index] < len(input_scores):
|
||||
input_score = safe_deque_popleft(input_scores_deque)
|
||||
if input_score is not None:
|
||||
task_score = input_scores[index]
|
||||
|
||||
evaluation_points += task_score
|
||||
|
||||
update_assignment_completion(
|
||||
ac, _ = update_assignment_completion(
|
||||
assignment_user=assignment_user,
|
||||
assignment=assignment,
|
||||
course_session=course_session,
|
||||
|
|
@ -380,7 +389,7 @@ def create_test_assignment_evaluation_data(
|
|||
evaluation_user=evaluation_user,
|
||||
)
|
||||
|
||||
update_assignment_completion(
|
||||
ac, _ = update_assignment_completion(
|
||||
assignment_user=assignment_user,
|
||||
assignment=assignment,
|
||||
course_session=course_session,
|
||||
|
|
@ -391,12 +400,27 @@ def create_test_assignment_evaluation_data(
|
|||
evaluation_points=evaluation_points,
|
||||
)
|
||||
|
||||
# take the last input score as deduction if there is one left...
|
||||
if points_deducted > 0:
|
||||
ac.evaluation_points_deducted = points_deducted
|
||||
ac.evaluation_points_deducted_reason = "Assignment Punkteabzug Test"
|
||||
ac.save()
|
||||
|
||||
|
||||
def create_edoniq_test_result_data(
|
||||
assignment, course_session, assignment_user, user_points=19, max_points=24
|
||||
assignment,
|
||||
course_session,
|
||||
assignment_user,
|
||||
user_points=19,
|
||||
max_points=24,
|
||||
evaluation_points_deducted=0,
|
||||
):
|
||||
assignment.assignment.evaluation_tasks.raw_data[0]["value"][
|
||||
"max_points"
|
||||
] = max_points
|
||||
assignment.assignment.save()
|
||||
if assignment and course_session and assignment_user:
|
||||
update_assignment_completion(
|
||||
ac, _ = update_assignment_completion(
|
||||
assignment_user=assignment_user,
|
||||
assignment=assignment,
|
||||
course_session=course_session,
|
||||
|
|
@ -408,6 +432,11 @@ def create_edoniq_test_result_data(
|
|||
evaluation_max_points=max_points,
|
||||
)
|
||||
|
||||
if evaluation_points_deducted > 0:
|
||||
ac.evaluation_points_deducted = evaluation_points_deducted
|
||||
ac.evaluation_points_deducted_reason = "Edoniq Punkteabzug Test"
|
||||
ac.save()
|
||||
|
||||
|
||||
def create_feedback_response_data(
|
||||
course_session,
|
||||
|
|
|
|||
|
|
@ -188,14 +188,22 @@ class DashboardQuery(graphene.ObjectType):
|
|||
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
|
||||
assignment_user=user,
|
||||
course_session__course=course,
|
||||
).values("evaluation_max_points", "evaluation_points")
|
||||
).values(
|
||||
"evaluation_max_points", "evaluation_points", "evaluation_points_deducted"
|
||||
)
|
||||
|
||||
evaluation_results = list(evaluation_results)
|
||||
points_max_count = sum(
|
||||
[result.get("evaluation_max_points", 0) for result in evaluation_results]
|
||||
)
|
||||
points_achieved_count = sum(
|
||||
[result.get("evaluation_points", 0) for result in evaluation_results]
|
||||
[
|
||||
(
|
||||
result.get("evaluation_points", 0)
|
||||
- result.get("evaluation_points_deducted", 0)
|
||||
)
|
||||
for result in evaluation_results
|
||||
]
|
||||
)
|
||||
|
||||
return CourseProgressType(
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@
|
|||
<label>
|
||||
evaluation score:
|
||||
<input type="text" name="assignment_evaluation_scores" placeholder="6,6,6,3,3">
|
||||
</label><br>
|
||||
<label>
|
||||
points deducted:
|
||||
<input type="number" name="assignment_points_deducted" min="0">
|
||||
</label>
|
||||
<div style="margin-bottom: 8px; padding: 4px; border-bottom: 1px lightblue solid"></div>
|
||||
|
||||
|
|
@ -84,6 +88,10 @@
|
|||
<label>
|
||||
max points:
|
||||
<input type="number" name="edoniq_test_max_points" min="0">
|
||||
</label><br>
|
||||
<label>
|
||||
points deducted:
|
||||
<input type="number" name="edoniq_test_points_deducted" min="0">
|
||||
</label>
|
||||
<div style="margin-bottom: 8px; padding: 4px; border-bottom: 1px lightblue solid"></div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue