WR-02: Confirm and SetScore answer ErrAttemptOpen (409 attempt_open, an admin refusal) for an answer of a running test, ConfirmBulk skips such attempts, and the default open queue leaves them out, so AnswerReviewed never fires before the test ends.
109 lines
8.2 KiB
Go
109 lines
8.2 KiB
Go
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, except answers of a test attempt still in progress), 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, or that its feedback was withheld for repeating the rubric), 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. An answer of a test attempt still in progress is refused with 409 code attempt_open. 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. An answer of a test attempt still in progress is refused with 409 code attempt_open. 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. Answers of test attempts still in progress are skipped (an attempt_id of one is refused with 409 code attempt_open). 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,
|
|
},
|
|
}
|
|
}
|