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 <[email protected]>
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() { async function handleDeleteSubmission() {
isDeleting = true; isDeleting = true;
await deleteSubmission(data.id, status.id); await deleteSubmission(data.id, status.id);
@@ -118,18 +141,37 @@
} }
data?.questions.forEach((question) => { data?.questions.forEach((question) => {
const { id, points, question_type_id } = question; const { id, points, question_type_id, options } = question;
if (question_type_id == QUESTION_TYPE.RADIO) {
if (question_type_id !== QUESTION_TYPE.TEXTAREA) {
const answer = data.questionAnswers.find((q) => q.question_id === id); const answer = data.questionAnswers.find((q) => q.question_id === id);
const answerPoints = answer?.answers?.length ?? 0; const answerPoints = answer?.answers?.length ?? 0;
reasons = { reasons = {
...reasons, ...reasons,
[id]: `${$t( [id]: `${$t('course.navItem.submissions.grading_modal.allocated')} ${$t(
'course.navItem.submissions.grading_modal.questions_tried' 'course.navItem.submissions.grading_modal.total_try'
)} ${answerPoints} ` )} ${answerPoints} `
}; };
data.questionAnswerByPoint[id] = `${Math.ceil(points / 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) { } else if (aiResponses.length) {
const graded = aiResponses.find((res) => res.id === id); const graded = aiResponses.find((res) => res.id === id);
@@ -182,10 +224,10 @@
headerClass="py-2" headerClass="py-2"
labelClass="text-base font-semibold" labelClass="text-base font-semibold"
> >
<div class="w-full h-full mt-2"> <div class="mt-2 h-full w-full">
{#if openDeletePrompt} {#if openDeletePrompt}
<div class="w-96 mx-auto p-3 border border-gray-300 rounded-md"> <div class="mx-auto w-96 rounded-md border border-gray-300 p-3">
<h1 class="dark:text-white text-lg"> <h1 class="text-lg dark:text-white">
{$t('delete_modal.content')} {$t('delete_modal.content')}
</h1> </h1>
@@ -223,19 +265,19 @@
/> />
{/if} {/if}
</div> </div>
<div class="ml-4 mt-2 w-2/5 sticky top-0"> <div class="sticky top-0 ml-4 mt-2 w-2/5">
<div class="border border-gray-300 rounded-md"> <div class="rounded-md border border-gray-300">
<div <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')} {$t('course.navItem.submissions.grading_modal.details')}
{#if data.isEarly} {#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 {$t('course.navItem.submissions.grading_modal.early')}</span
> >
{:else} {: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')} {$t('course.navItem.submissions.grading_modal.late')}
</span> </span>
{/if} {/if}
@@ -253,13 +295,13 @@
</OverflowMenu> </OverflowMenu>
</div> </div>
<div class="flex items-center space-x-4 text-sm px-3 py-2"> <div class="flex items-center space-x-4 px-3 py-2 text-sm">
<p class="dark:text-white text-sm text-gray-500 font-semibold"> <p class="text-sm font-semibold text-gray-500 dark:text-white">
{$t('course.navItem.submissions.grading_modal.total_grade')}: {$t('course.navItem.submissions.grading_modal.total_grade')}:
</p> </p>
<Tag <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} {total}/{maxPoints}
</Tag> </Tag>
@@ -271,20 +313,20 @@
<p class="dark:text-white">Grading</p> <p class="dark:text-white">Grading</p>
</div> </div>
</div> --> </div> -->
<div class="flex items-center space-x-4 text-sm px-3 py-2"> <div class="flex items-center space-x-4 px-3 py-2 text-sm">
<p class="dark:text-white text-sm text-gray-500 font-semibold"> <p class="text-sm font-semibold text-gray-500 dark:text-white">
{$t('course.navItem.submissions.grading_modal.student')}: {$t('course.navItem.submissions.grading_modal.student')}:
</p> </p>
{#if data.student} {#if data.student}
<div <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 <img
alt="Student avatar" alt="Student avatar"
class="flex rounded-full h-5 w-5" class="flex h-5 w-5 rounded-full"
src={data.student.avatar_url} 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} {data.student.fullname}
</p> </p>
</div> </div>
@@ -298,8 +340,8 @@
> >
</div> --> </div> -->
<div class="flex flex-col items-start text-sm px-3 py-2"> <div class="flex flex-col items-start px-3 py-2 text-sm">
<p class="dark:text-white text-gray-500 font-semibold"> <p class="font-semibold text-gray-500 dark:text-white">
{$t('course.navItem.submissions.grading_modal.status')}: {$t('course.navItem.submissions.grading_modal.status')}:
</p> </p>
<Dropdown <Dropdown
@@ -310,8 +352,8 @@
/> />
</div> </div>
<div class="flex flex-col items-start text-sm px-3 py-2"> <div class="flex flex-col items-start px-3 py-2 text-sm">
<p class="dark:text-white text-gray-500 font-semibold"> <p class="font-semibold text-gray-500 dark:text-white">
{$t('course.navItem.submissions.grading_modal.add_comment')}: {$t('course.navItem.submissions.grading_modal.add_comment')}:
</p> </p>
<TextArea <TextArea
@@ -322,14 +364,14 @@
/> />
</div> </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 <PrimaryButton
onClick={gradeWithAI} onClick={gradeWithAI}
variant={VARIANTS.OUTLINED} variant={VARIANTS.OUTLINED}
className="space-x-3 py-3 px-8 w-full " className="space-x-3 py-3 px-8 w-full "
> >
<img src="/ai.svg" alt="ai" /> <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')} {$t('course.navItem.submissions.grading_modal.grade_with_ai')}
</p> </p>
</PrimaryButton> </PrimaryButton>
@@ -515,6 +515,11 @@
"accept_grade": "Akzeptieren", "accept_grade": "Akzeptieren",
"reject_grade": "Ablehnen", "reject_grade": "Ablehnen",
"add_comment_placeholder": "Hinterlasse einen Kommentar", "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:", "questions_tried": "Diese Punktzahl wurde aufgrund der Gesamtzahl der Versuche vergeben:",
"delete_prompt": "Löschen", "delete_prompt": "Löschen",
"delete_error": "Die Übermittlung konnte nicht gelöscht werden", "delete_error": "Die Übermittlung konnte nicht gelöscht werden",
@@ -912,6 +912,11 @@
"submit_grades": "Submit Grades", "submit_grades": "Submit Grades",
"accept_grade": "Accept", "accept_grade": "Accept",
"reject_grade": "Reject", "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:", "questions_tried": "Allocated this score because of total tries:",
"delete_prompt": "Delete", "delete_prompt": "Delete",
"delete_error": "Failed to delete submission", "delete_error": "Failed to delete submission",
@@ -515,6 +515,11 @@
"accept_grade": "Aceptar", "accept_grade": "Aceptar",
"reject_grade": "Rechazar", "reject_grade": "Rechazar",
"add_comment_placeholder": "Deja un comentario", "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:", "questions_tried": "Se asignó esta puntuación debido al total de intentos:",
"delete_prompt": "Borrar", "delete_prompt": "Borrar",
"delete_error": "No se pudo eliminar el envío", "delete_error": "No se pudo eliminar el envío",
@@ -515,6 +515,11 @@
"accept_grade": "Accepter", "accept_grade": "Accepter",
"reject_grade": "Rejeter", "reject_grade": "Rejeter",
"add_comment_placeholder": "laissez un commentaire", "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 :", "questions_tried": "Attribué ce score en raison du nombre total d'essais :",
"delete_prompt": "Supprimer", "delete_prompt": "Supprimer",
"delete_error": "Échec de la suppression de la soumission", "delete_error": "Échec de la suppression de la soumission",
@@ -515,6 +515,11 @@
"accept_grade": "स्वीकार करना", "accept_grade": "स्वीकार करना",
"reject_grade": "अस्वीकार करना", "reject_grade": "अस्वीकार करना",
"add_comment_placeholder": "एक टिप्पणी छोड़ें", "add_comment_placeholder": "एक टिप्पणी छोड़ें",
"allocated": "यह अंक इसलिए आवंटित किया गया क्योंकि",
"total_try": "कुल प्रयासों में से:",
"options_correct": "सही विकल्प का चयन किया गया",
"out_of": "से बाहर",
"options_wrong": "और गलत चयनों की संख्या थी",
"questions_tried": "कुल प्रयासों के कारण यह स्कोर आवंटित किया गया:", "questions_tried": "कुल प्रयासों के कारण यह स्कोर आवंटित किया गया:",
"delete_prompt": "मिटाना", "delete_prompt": "मिटाना",
"delete_error": "सबमिशन हटाने में विफल", "delete_error": "सबमिशन हटाने में विफल",
@@ -515,6 +515,11 @@
"accept_grade": "Aceitar", "accept_grade": "Aceitar",
"reject_grade": "Rejeitar", "reject_grade": "Rejeitar",
"add_comment_placeholder": "Deixe um comentário", "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:", "questions_tried": "Alocada esta pontuação por causa do total de tentativas:",
"delete_prompt": "Excluir", "delete_prompt": "Excluir",
"delete_error": "Falha ao excluir o envio", "delete_error": "Falha ao excluir o envio",
@@ -515,6 +515,11 @@
"accept_grade": "Принимать", "accept_grade": "Принимать",
"reject_grade": "Отклонять", "reject_grade": "Отклонять",
"add_comment_placeholder": "Оставить комментарий", "add_comment_placeholder": "Оставить комментарий",
"allocated": "Присвоен этот балл, потому что",
"total_try": "общего числа попыток:",
"options_correct": "выбраны правильные варианты",
"out_of": "снаружи",
"options_wrong": "и количество неправильных выборов было",
"questions_tried": "Присвоен этот балл из-за общего количества попыток:", "questions_tried": "Присвоен этот балл из-за общего количества попыток:",
"delete_prompt": "Удалить", "delete_prompt": "Удалить",
"delete_error": "Не удалось удалить отправку", "delete_error": "Не удалось удалить отправку",
@@ -515,6 +515,11 @@
"accept_grade": "Chấp nhận", "accept_grade": "Chấp nhận",
"reject_grade": "Từ chối", "reject_grade": "Từ chối",
"add_comment_placeholder": "Để lại một bình luận", "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ử:", "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_prompt": "Xóa bỏ",
"delete_error": "Không thể xóa bài nộp", "delete_error": "Không thể xóa bài nộp",
@@ -1,4 +1,4 @@
import { Profile } from '.'; import { Option, Profile } from '.';
export interface SubmissionItem { export interface SubmissionItem {
id: string; id: string;
@@ -37,6 +37,7 @@ export interface SubmissionIdData {
order: number; order: number;
points: number; points: number;
question_type_id: number; question_type_id: number;
options: Option[];
}[]; }[];
questionAnswers: { questionAnswers: {
open_answer: string; open_answer: string;