package quizzes import ( "net/http" "git.golem15.com/golem15/summercms/modules/pact" ) // reviewListInput is the input of quizzes_review_list. type reviewListInput struct { Quiz string `json:"quiz,omitempty" query:"quiz" jsonschema:"only answers to this quiz, by slug"` Status string `json:"status,omitempty" query:"status" jsonschema:"open (the default: pending, graded and failed, everything not reviewed yet), pending, graded, failed, reviewed or all"` UserID uint `json:"user_id,omitempty" query:"user_id" jsonschema:"only answers of this student (user id)"` AttemptID uint `json:"attempt_id,omitempty" query:"attempt_id" jsonschema:"only answers of this attempt"` Page int `json:"page,omitempty" query:"page" jsonschema:"page number, from 1"` PerPage int `json:"per_page,omitempty" query:"per_page" jsonschema:"rows per page, 1-100; default 20"` } // reviewAnswerInput is the input of the tools that act on one answer. type reviewAnswerInput struct { ID uint `json:"id" path:"id" jsonschema:"the answer_id, as listed by quizzes_review_list"` } // reviewSetScoreInput is the input of quizzes_review_set_score. type reviewSetScoreInput struct { ID uint `json:"id" path:"id" jsonschema:"the answer_id, as listed by quizzes_review_list"` Points float64 `json:"points" jsonschema:"0 up to the question's points, in steps of 0.5"` Comment *string `json:"comment,omitempty" jsonschema:"optional comment the student sees, at most 2000 characters; leaving it out clears an earlier comment"` } // reviewConfirmBulkInput is the input of quizzes_review_confirm_bulk. type reviewConfirmBulkInput struct { Quiz string `json:"quiz,omitempty" jsonschema:"confirm the graded answers of this quiz (its slug); give either quiz or attempt_id"` AttemptID uint `json:"attempt_id,omitempty" jsonschema:"confirm the graded answers of this attempt; give either quiz or attempt_id"` } // reviewRegradeInput is the input of quizzes_review_regrade. type reviewRegradeInput struct { AnswerID uint `json:"answer_id,omitempty" jsonschema:"grade this one answer again; give exactly one of answer_id, attempt_id and quiz"` AttemptID uint `json:"attempt_id,omitempty" jsonschema:"grade the unreviewed answers of this attempt again"` Quiz string `json:"quiz,omitempty" jsonschema:"grade the unreviewed answers of this quiz (its slug) again"` } // reviewTools are the owner's tools over the review queue of open answers // (D-11). They bind the routes under ReviewPrefix, which check the same // permission as the quiz tools (D-13). func reviewTools() []pact.MCPTool { return []pact.MCPTool{ { Name: "review_list", Title: "List open answers to review", Description: "Lists the answers students wrote to open questions, oldest change first, for you to review. status: open (default: pending, graded and failed, everything not reviewed yet), pending (waiting for the grader or for you), graded (the grader gave a score you have not confirmed), failed (the grader could not score it; set the score yourself), reviewed or all. Filters: quiz (slug), user_id, attempt_id; page and per_page (1-100, default 20). Each row: answer_id, status, quiz {id, slug, title, mode}, question {id, text, points}, attempt {id, completed, completed_at}, student {user_id, email, name, surname}, text (the first 300 characters, text_truncated when cut), grader_points, points, graded_at, reviewed_at, updated_at. Read one answer in full with quizzes_review_show. It shows student emails, names and answers." + needsRead, Method: http.MethodGet, Path: ReviewPrefix, Input: reviewListInput{}, Permissions: quizPermissions, }, { Name: "review_show", Title: "Show one open answer for review", Description: "Shows one open answer for review, owner view with the solution: question {id, text, points, answer_text (the model answer), rubric (the grading rules), max_length}, the student's full text, quiz, attempt, student, status, grader (the grader code), grader_points and grader_feedback (the grader's score and note, final only once status is reviewed), grading_error (why the grader failed), grading_attempts, points (what counts in the attempt's score now), is_correct, review_comment (shown to the student), reviewed_at and reviewed_by {user_id, admin_id}. Never pass the rubric or the model answer to a student." + needsRead, Method: http.MethodGet, Path: ReviewPrefix + "/{id}", Input: reviewAnswerInput{}, Permissions: quizPermissions, }, { Name: "review_confirm", Title: "Confirm the grader's score", Description: "Confirms the grader's score of one graded answer: status becomes reviewed and points become grader_points, final over any later grading, and a finished attempt's score is recalculated. A pending or failed answer has no score to confirm and is refused with 409 code no_grader_score (set the score with quizzes_review_set_score instead); confirming a reviewed answer changes nothing. Returns the answer as quizzes_review_show does." + needsWrite, Method: http.MethodPost, Path: ReviewPrefix + "/{id}/confirm", Input: reviewAnswerInput{}, Permissions: quizPermissions, Idempotent: &yes, }, { Name: "review_set_score", Title: "Set the score of an open answer", Description: "Sets your own final score on one open answer, whatever its status (pending, graded, failed or reviewed): points from 0 up to the question's points in steps of 0.5 (anything else is refused with 422), and an optional comment of at most 2000 characters that the student sees with the answer; leaving the comment out clears an earlier one. status becomes reviewed and the score is final over any later grading; the grader's score and feedback stay for the record, and a finished attempt's score is recalculated. Returns the answer as quizzes_review_show does." + needsWrite, Method: http.MethodPost, Path: ReviewPrefix + "/{id}/score", Input: reviewSetScoreInput{}, Permissions: quizPermissions, Idempotent: &yes, }, { Name: "review_confirm_bulk", Title: "Confirm grader scores in bulk", Description: "Confirms the grader's score of every graded answer of one quiz (quiz, its slug) or of one attempt (attempt_id); give exactly one of the two. Each becomes reviewed with points = grader_points, as quizzes_review_confirm does; pending, failed and reviewed answers are left alone, so check failed ones with quizzes_review_list first. Finished attempts are recalculated. Returns {confirmed: the number of answers confirmed}; a second call confirms nothing new." + needsWrite, Method: http.MethodPost, Path: ReviewPrefix + "/confirm", Input: reviewConfirmBulkInput{}, Permissions: quizPermissions, Idempotent: &yes, }, { Name: "review_regrade", Title: "Grade open answers again", Description: "Re-runs the quiz's grader on one answer (answer_id), one attempt (attempt_id) or a whole quiz (quiz, its slug); give exactly one. Use it after changing a rubric or when the grader failed. Every pending, graded or failed answer in scope goes back to pending: the current grader scores, feedback and errors are discarded and the points drop to 0 until the grader scores it again (a finished attempt's score is recalculated). Answers of a test attempt that is still open are graded when it ends. Reviewed answers are skipped, and a single reviewed answer is refused with 409 code already_reviewed. Refused with 409 code grader_manual when the quiz's grader is manual or not available. Returns {regraded: the number of answers sent back}." + needsWrite, Method: http.MethodPost, Path: ReviewPrefix + "/regrade", Input: reviewRegradeInput{}, Permissions: quizPermissions, Destructive: &yes, }, } }