fix: bound confirmed points and the attempt percentage
WR-05: Confirm clamps the grader's score to the question's current points, so lowering a question's points no longer lets a confirm store more than full marks, and Totals caps the percentage at 100 when a question's points were raised after the attempt was scored.
This commit is contained in:
@@ -419,7 +419,8 @@ func notFound(err error) error {
|
||||
}
|
||||
|
||||
// Confirm makes the grader's score of a graded answer final: status
|
||||
// reviewed, points = grader_points (D-12). A reviewed answer is returned
|
||||
// reviewed, points = grader_points (D-12), clamped again to the question's
|
||||
// current points. A reviewed answer is returned
|
||||
// unchanged, without a write or an event; a pending or failed answer has
|
||||
// no score to confirm (ErrNoGraderScore). A completed attempt is
|
||||
// recalculated, and AnswerReviewedEvent, plus AttemptReviewedEvent when
|
||||
@@ -464,7 +465,13 @@ func (s *Service) confirmTarget(ctx context.Context, tx *gorm.DB, t *reviewTarge
|
||||
case status != models.GradingGraded || t.Answer.GraderPoints == nil:
|
||||
return false, ErrNoGraderScore
|
||||
}
|
||||
return true, s.storeReview(ctx, tx, t, *t.Answer.GraderPoints, t.Answer.ReviewComment, false, by)
|
||||
// The grader's score was clamped to the question's points at grading
|
||||
// time; the owner may have lowered them since.
|
||||
points, ok := ClampOpenPoints(*t.Answer.GraderPoints, t.Question.Points)
|
||||
if !ok {
|
||||
return false, ErrNoGraderScore
|
||||
}
|
||||
return true, s.storeReview(ctx, tx, t, points, t.Answer.ReviewComment, false, by)
|
||||
}
|
||||
|
||||
// storeReview writes the owner's decision on one locked open answer and
|
||||
|
||||
@@ -140,7 +140,8 @@ func Score(in ScoreInput, cfg ScoringConfig) ScoreResult {
|
||||
}
|
||||
|
||||
// Totals sums the earned points of an attempt, floored at 0, and the
|
||||
// percentage of maxPoints it is. With no points to earn the percentage is 0.
|
||||
// percentage of maxPoints it is, capped at 100. With no points to earn the
|
||||
// percentage is 0.
|
||||
func Totals(points []float64, maxPoints int) (score, percent float64) {
|
||||
for _, p := range points {
|
||||
score += p
|
||||
@@ -149,7 +150,9 @@ func Totals(points []float64, maxPoints int) (score, percent float64) {
|
||||
if maxPoints <= 0 {
|
||||
return score, 0
|
||||
}
|
||||
return score, round(score / float64(maxPoints) * 100)
|
||||
// A question's points raised after the attempt was scored can push the
|
||||
// score past the attempt's stored maximum; the percentage stays <= 100.
|
||||
return score, round(math.Min(score/float64(maxPoints)*100, 100))
|
||||
}
|
||||
|
||||
// ClampOpenPoints keeps a grader's score of an open question in
|
||||
|
||||
@@ -84,6 +84,7 @@ func TestTotals(t *testing.T) {
|
||||
{[]float64{2, -0.5}, 4, 1.5, 37.5},
|
||||
{nil, 0, 0, 0},
|
||||
{[]float64{1}, 1, 1, 100},
|
||||
{[]float64{3}, 2, 3, 100},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
score, pct := classes.Totals(tc.points, tc.max)
|
||||
|
||||
Reference in New Issue
Block a user