feat: AI grading bug (#420)

* worked on the AI grading task

* worked on the AI grading task

* improved the ai grading function

* improved the ai grading function

* update types

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Nwosu Emmanuel
2025-09-28 20:08:56 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 7e0443fc37
commit 594eb97caa
10 changed files with 119 additions and 36 deletions
@@ -93,6 +93,29 @@
}
}
const getMultipleAnswersGrade = (options, answer, points) => {
const correctOptions = options
.filter((option) => option.is_correct)
.map((option) => option.value);
const correctSelections = answer.answers.filter((answer) =>
correctOptions.includes(answer)
).length;
const incorrectSelections = answer.answers.length - correctSelections;
const pointsEarned = (correctSelections / correctOptions.length) * points;
const deduction = (incorrectSelections * points) / correctOptions.length;
const finalPoints = Math.max(pointsEarned - deduction, 0);
return {
finalPoints,
correctOptions,
correctSelections,
incorrectSelections
};
};
async function handleDeleteSubmission() {
isDeleting = true;
await deleteSubmission(data.id, status.id);
@@ -118,18 +141,37 @@
}
data?.questions.forEach((question) => {
const { id, points, question_type_id } = question;
if (question_type_id !== QUESTION_TYPE.TEXTAREA) {
const { id, points, question_type_id, options } = question;
if (question_type_id == QUESTION_TYPE.RADIO) {
const answer = data.questionAnswers.find((q) => q.question_id === id);
const answerPoints = answer?.answers?.length ?? 0;
reasons = {
...reasons,
[id]: `${$t(
'course.navItem.submissions.grading_modal.questions_tried'
[id]: `${$t('course.navItem.submissions.grading_modal.allocated')} ${$t(
'course.navItem.submissions.grading_modal.total_try'
)} ${answerPoints} `
};
data.questionAnswerByPoint[id] = `${Math.ceil(points / answerPoints)}`;
} else if (question_type_id == QUESTION_TYPE.CHECKBOX) {
const answer = data.questionAnswers.find((q) => q.question_id === id);
const { finalPoints, correctOptions, correctSelections, incorrectSelections } =
getMultipleAnswersGrade(options, answer, points);
reasons = {
...reasons,
[id]: `${$t(
'course.navItem.submissions.grading_modal.allocated'
)} ${correctSelections} ${$t('course.navItem.submissions.grading_modal.out_of')} ${
correctOptions.length
} ${$t('course.navItem.submissions.grading_modal.options_correct')} ${$t(
'course.navItem.submissions.grading_modal.options_wrong'
)} ${incorrectSelections} `
};
data.questionAnswerByPoint[id] = `${finalPoints}`;
} else if (aiResponses.length) {
const graded = aiResponses.find((res) => res.id === id);
@@ -182,10 +224,10 @@
headerClass="py-2"
labelClass="text-base font-semibold"
>
<div class="w-full h-full mt-2">
<div class="mt-2 h-full w-full">
{#if openDeletePrompt}
<div class="w-96 mx-auto p-3 border border-gray-300 rounded-md">
<h1 class="dark:text-white text-lg">
<div class="mx-auto w-96 rounded-md border border-gray-300 p-3">
<h1 class="text-lg dark:text-white">
{$t('delete_modal.content')}
</h1>
@@ -223,19 +265,19 @@
/>
{/if}
</div>
<div class="ml-4 mt-2 w-2/5 sticky top-0">
<div class="border border-gray-300 rounded-md">
<div class="sticky top-0 ml-4 mt-2 w-2/5">
<div class="rounded-md border border-gray-300">
<div
class="flex gap-1 justify-between items-center border-b border-t-0 border-l-0 border-r-0 border-gray-300 p-3"
class="flex items-center justify-between gap-1 border-b border-l-0 border-r-0 border-t-0 border-gray-300 p-3"
>
<p class="dark:text-white font-bold text-base">
<p class="text-base font-bold dark:text-white">
{$t('course.navItem.submissions.grading_modal.details')}
{#if data.isEarly}
<span class="ml-2 text-sm badge rounded-sm px-2 bg-green-500 text-white">
<span class="badge ml-2 rounded-sm bg-green-500 px-2 text-sm text-white">
{$t('course.navItem.submissions.grading_modal.early')}</span
>
{:else}
<span class="ml-2 badge text-sm rounded-sm px-2 bg-red-500 text-white">
<span class="badge ml-2 rounded-sm bg-red-500 px-2 text-sm text-white">
{$t('course.navItem.submissions.grading_modal.late')}
</span>
{/if}
@@ -253,13 +295,13 @@
</OverflowMenu>
</div>
<div class="flex items-center space-x-4 text-sm px-3 py-2">
<p class="dark:text-white text-sm text-gray-500 font-semibold">
<div class="flex items-center space-x-4 px-3 py-2 text-sm">
<p class="text-sm font-semibold text-gray-500 dark:text-white">
{$t('course.navItem.submissions.grading_modal.total_grade')}:
</p>
<Tag
class="dark:text-white font-semibold text-black bg-gray-100 dark:bg-neutral-700 rounded-md w-fit"
class="w-fit rounded-md bg-gray-100 font-semibold text-black dark:bg-neutral-700 dark:text-white"
>
{total}/{maxPoints}
</Tag>
@@ -271,20 +313,20 @@
<p class="dark:text-white">Grading</p>
</div>
</div> -->
<div class="flex items-center space-x-4 text-sm px-3 py-2">
<p class="dark:text-white text-sm text-gray-500 font-semibold">
<div class="flex items-center space-x-4 px-3 py-2 text-sm">
<p class="text-sm font-semibold text-gray-500 dark:text-white">
{$t('course.navItem.submissions.grading_modal.student')}:
</p>
{#if data.student}
<div
class="flex flex-row justify-center items-center bg-gray-100 dark:bg-neutral-700 rounded-md p-[6px]"
class="flex flex-row items-center justify-center rounded-md bg-gray-100 p-[6px] dark:bg-neutral-700"
>
<img
alt="Student avatar"
class="flex rounded-full h-5 w-5"
class="flex h-5 w-5 rounded-full"
src={data.student.avatar_url}
/>
<p class="dark:text-white font-semibold ml-2 text-sm line-clamp-1">
<p class="ml-2 line-clamp-1 text-sm font-semibold dark:text-white">
{data.student.fullname}
</p>
</div>
@@ -298,8 +340,8 @@
>
</div> -->
<div class="flex flex-col items-start text-sm px-3 py-2">
<p class="dark:text-white text-gray-500 font-semibold">
<div class="flex flex-col items-start px-3 py-2 text-sm">
<p class="font-semibold text-gray-500 dark:text-white">
{$t('course.navItem.submissions.grading_modal.status')}:
</p>
<Dropdown
@@ -310,8 +352,8 @@
/>
</div>
<div class="flex flex-col items-start text-sm px-3 py-2">
<p class="dark:text-white text-gray-500 font-semibold">
<div class="flex flex-col items-start px-3 py-2 text-sm">
<p class="font-semibold text-gray-500 dark:text-white">
{$t('course.navItem.submissions.grading_modal.add_comment')}:
</p>
<TextArea
@@ -322,14 +364,14 @@
/>
</div>
<div class="flex flex-col w-full space-y-3 px-3 py-2">
<div class="flex w-full flex-col space-y-3 px-3 py-2">
<PrimaryButton
onClick={gradeWithAI}
variant={VARIANTS.OUTLINED}
className="space-x-3 py-3 px-8 w-full "
>
<img src="/ai.svg" alt="ai" />
<p class="font-semibold text-sm">
<p class="text-sm font-semibold">
{$t('course.navItem.submissions.grading_modal.grade_with_ai')}
</p>
</PrimaryButton>
@@ -515,6 +515,11 @@
"accept_grade": "Akzeptieren",
"reject_grade": "Ablehnen",
"add_comment_placeholder": "Hinterlasse einen Kommentar",
"allocated": "Diese Punktzahl wurde vergeben, weil",
"total_try": "der Gesamtversuche:",
"options_correct": "Es wurden die richtigen Optionen ausgewählt",
"out_of": "aus",
"options_wrong": "und die Anzahl der falschen Auswahlen war",
"questions_tried": "Diese Punktzahl wurde aufgrund der Gesamtzahl der Versuche vergeben:",
"delete_prompt": "Löschen",
"delete_error": "Die Übermittlung konnte nicht gelöscht werden",
@@ -1581,4 +1586,4 @@
"message": "Sie haben nicht gerettete Änderungen. Bist du sicher, dass du gehen willst?"
}
}
}
}
@@ -912,6 +912,11 @@
"submit_grades": "Submit Grades",
"accept_grade": "Accept",
"reject_grade": "Reject",
"allocated": "Allocated this score because",
"total_try": "of total tries:",
"options_correct": "correct options were selected",
"out_of": "out of",
"options_wrong": "and the number of wrong selections were",
"questions_tried": "Allocated this score because of total tries:",
"delete_prompt": "Delete",
"delete_error": "Failed to delete submission",
@@ -515,6 +515,11 @@
"accept_grade": "Aceptar",
"reject_grade": "Rechazar",
"add_comment_placeholder": "Deja un comentario",
"allocated": "Asignó esta puntuación porque",
"total_try": "de intentos totales:",
"options_correct": "se seleccionaron las opciones correctas",
"out_of": "fuera de",
"options_wrong": "y el número de selecciones incorrectas fue",
"questions_tried": "Se asignó esta puntuación debido al total de intentos:",
"delete_prompt": "Borrar",
"delete_error": "No se pudo eliminar el envío",
@@ -1581,4 +1586,4 @@
"message": "Tienes cambios no salvos. ¿Estás seguro de que quieres irte?"
}
}
}
}
@@ -515,6 +515,11 @@
"accept_grade": "Accepter",
"reject_grade": "Rejeter",
"add_comment_placeholder": "laissez un commentaire",
"allocated": "J'ai attribué ce score parce que",
"total_try": "du total d'essais :",
"options_correct": "les bonnes options ont été sélectionnées",
"out_of": "hors de",
"options_wrong": "et le nombre de mauvaises sélections était",
"questions_tried": "Attribué ce score en raison du nombre total d'essais :",
"delete_prompt": "Supprimer",
"delete_error": "Échec de la suppression de la soumission",
@@ -1581,4 +1586,4 @@
"message": "Vous avez des changements non sauvés. Êtes-vous sûr de vouloir partir?"
}
}
}
}
@@ -515,6 +515,11 @@
"accept_grade": "स्वीकार करना",
"reject_grade": "अस्वीकार करना",
"add_comment_placeholder": "एक टिप्पणी छोड़ें",
"allocated": "यह अंक इसलिए आवंटित किया गया क्योंकि",
"total_try": "कुल प्रयासों में से:",
"options_correct": "सही विकल्प का चयन किया गया",
"out_of": "से बाहर",
"options_wrong": "और गलत चयनों की संख्या थी",
"questions_tried": "कुल प्रयासों के कारण यह स्कोर आवंटित किया गया:",
"delete_prompt": "मिटाना",
"delete_error": "सबमिशन हटाने में विफल",
@@ -1581,4 +1586,4 @@
"message": "आपके पास अनसुना परिवर्तन हैं। क्या आपकी वाकई छोड़ने की इच्छा है?"
}
}
}
}
@@ -515,6 +515,11 @@
"accept_grade": "Aceitar",
"reject_grade": "Rejeitar",
"add_comment_placeholder": "Deixe um comentário",
"allocated": "Alocou esta pontuação porque",
"total_try": "do total de tentativas:",
"options_correct": "opções corretas foram selecionadas",
"out_of": "fora de",
"options_wrong": "e o número de seleções erradas foi",
"questions_tried": "Alocada esta pontuação por causa do total de tentativas:",
"delete_prompt": "Excluir",
"delete_error": "Falha ao excluir o envio",
@@ -1581,4 +1586,4 @@
"message": "Você tem mudanças não salvas. Tem certeza de que deseja sair?"
}
}
}
}
@@ -515,6 +515,11 @@
"accept_grade": "Принимать",
"reject_grade": "Отклонять",
"add_comment_placeholder": "Оставить комментарий",
"allocated": "Присвоен этот балл, потому что",
"total_try": "общего числа попыток:",
"options_correct": "выбраны правильные варианты",
"out_of": "снаружи",
"options_wrong": "и количество неправильных выборов было",
"questions_tried": "Присвоен этот балл из-за общего количества попыток:",
"delete_prompt": "Удалить",
"delete_error": "Не удалось удалить отправку",
@@ -1581,4 +1586,4 @@
"message": "У вас есть неспасенные изменения. Вы уверены, что хотите уйти?"
}
}
}
}
@@ -515,6 +515,11 @@
"accept_grade": "Chấp nhận",
"reject_grade": "Từ chối",
"add_comment_placeholder": "Để lại một bình luận",
"allocated": "Phân bổ số điểm này bởi vì",
"total_try": "trong tổng số lần thử:",
"options_correct": "các phương án đúng đã được chọn",
"out_of": "ra khỏi",
"options_wrong": "và số lựa chọn sai là",
"questions_tried": "Phân bổ số điểm này dựa trên tổng số lần thử:",
"delete_prompt": "Xóa bỏ",
"delete_error": "Không thể xóa bài nộp",
@@ -1581,4 +1586,4 @@
"message": "Bạn có những thay đổi chưa được lưu. Bạn có chắc bạn muốn rời đi?"
}
}
}
}
@@ -1,4 +1,4 @@
import { Profile } from '.';
import { Option, Profile } from '.';
export interface SubmissionItem {
id: string;
@@ -37,6 +37,7 @@ export interface SubmissionIdData {
order: number;
points: number;
question_type_id: number;
options: Option[];
}[];
questionAnswers: {
open_answer: string;