Skip to content

Commit

Permalink
Make multiple problems unanswered whenever any part is unanswered.
Browse files Browse the repository at this point in the history
When a score is marked as empty the framework displays a "more parts
to answer" message. Multiple-type exercises run validators for each of
their parts and then decide whether to call the combined result correct,
unanswered, or incorrect.

This turns answers that are partially correct, partially empty into
unanswered (rather than wrong). If any of the parts are wrong, the
overall guess is graded wrong, no matter if any parts are empty.

Some finer points are unsimplified fractions and coefficients.

The proper-fraction type has three modes of simplification:
- "required" (unsimplified result is not accepted, but the user is not
  graded wrong; the score is marked empty, with a message);
- "enforced" (unsimplified result is marked as incorrect);
- "optional" (unsimplified result is silently accepted).
A proper-field defaults to "required" and an untyped multiple part
defaults to "number" (which includes proper). When a proper field is a
part of a multiple problem its simplification option should be
respected. Moreover, if the user submits an unsimplified fraction, but
also forgets to fill another field, the simplification reminder should
be forwarded.

The coefficient-field can be left unfilled and defaults to 1.
A problem composed of only coefficient fields will now never be
considered unanswered (as all its parts have defaults).

This is a semantic change, a couple of tests were modified. If any
exercise would happen to rely on the any-part-empty-combined-wrong
behavior it will need to be changed.
  • Loading branch information
wrwrwr committed Nov 22, 2015
1 parent f8eb84f commit 9a25e47
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 52 deletions.
30 changes: 16 additions & 14 deletions test/answer-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@
start();
});

asyncTest("multiple", 42, function() {
asyncTest("multiple", 48, function() {
setupSolutionArea();
var $problem = jQuery("#qunit-fixture .problem").append(
"<p class='solution' data-type='multiple'>" +
Expand All @@ -623,8 +623,10 @@
testMultipleAnswer(answerData, ["7", "3/2"], "right", "right answer is right");
testMultipleAnswer(answerData, ["7", "1.5"], "right", "right answer is right");
testMultipleAnswer(answerData, ["3/2", "7"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", ""], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["", "3/2"], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["7", ""], "empty", "incomplete answer is empty");
testMultipleAnswer(answerData, ["6", ""], "wrong", "partially wrong answer is wrong");
testMultipleAnswer(answerData, ["", "3/2"], "empty", "incomplete answer is empty");
testMultipleAnswer(answerData, ["", "5/2"], "wrong", "partially wrong answer is wrong");
testMultipleAnswer(answerData, ["", ""], "empty", "empty answer is empty");
testMultipleAnswer(answerData, ["7", "6/4"], "empty-message", "unsimplified right answer provides a message");
testMultipleAnswer(answerData, ["14/2", "6/4"], "empty-message", "unsimplified right gives message");
Expand All @@ -633,7 +635,7 @@
testMultipleAnswer(answerData, ["14/2", "7"], "wrong", "unsimplified right and wrong is wrong");
testMultipleAnswer(answerData, ["3", "6/4"], "wrong", "unsimplified right and wrong is wrong");
testMultipleAnswer(answerData, ["4/2", "4/2"], "wrong", "unsimplified wrong is wrong");
testMultipleAnswer(answerData, ["14/2", ""], "wrong", "unsimplified imcomplete answer is wrong");
testMultipleAnswer(answerData, ["14/2", ""], "empty-message", "unsimplified imcomplete answer is empty");

start();
});
Expand All @@ -653,8 +655,8 @@
testMultipleAnswer(answerData, ["7", "3/2"], "right", "right answer is right");
testMultipleAnswer(answerData, ["7", "1.5"], "right", "right answer is right");
testMultipleAnswer(answerData, ["3/2", "7"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", ""], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["", "3/2"], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["7", ""], "empty", "incomplete answer is empty");
testMultipleAnswer(answerData, ["", "3/2"], "empty", "incomplete answer is empty");
testMultipleAnswer(answerData, ["", ""], "empty", "empty answer is empty");
testMultipleAnswer(answerData, ["7", "6/4"], "wrong-message", "unsimplified right answer provides a message");
testMultipleAnswer(answerData, ["14/2", "6/4"], "wrong-message", "unsimplified right gives message");
Expand Down Expand Up @@ -695,8 +697,8 @@
testMultipleAnswer(answerData, ["1", "7"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", "2"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", ""], "right", "right answer is right");
testMultipleAnswer(answerData, ["", "1"], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["", "2"], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["", "1"], "empty", "incomplete answer is empty");
testMultipleAnswer(answerData, ["", "2"], "wrong", "partially wrong answer is wrong");
testMultipleAnswer(answerData, ["", ""], "empty", "empty answer is empty");

start();
Expand All @@ -718,8 +720,8 @@
testMultipleAnswer(answerData, ["0", "7"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", "2"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", ""], "right", "right answer is right");
testMultipleAnswer(answerData, ["", "0"], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["", "2"], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["", "0"], "empty", "incomplete answer is empty");
testMultipleAnswer(answerData, ["", "2"], "wrong", "partially wrong answer is wrong");
testMultipleAnswer(answerData, ["", ""], "empty", "empty answer is empty");

start();
Expand All @@ -743,7 +745,7 @@
testMultipleAnswer(answerData, ["1", "7"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", "2"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", ""], "right", "right answer is right");
testMultipleAnswer(answerData, ["", "2"], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["", "2"], "wrong", "partially wrong answer is wrong");
testMultipleAnswer(answerData, ["", ""], "empty", "empty answer is empty");

start();
Expand All @@ -767,8 +769,8 @@
testMultipleAnswer(answerData, ["-1", "7"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", "2"], "wrong", "wrong answer is wrong");
testMultipleAnswer(answerData, ["7", "-"], "right", "right answer is right");
testMultipleAnswer(answerData, ["7", ""], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["", "2"], "wrong", "incomplete answer is wrong");
testMultipleAnswer(answerData, ["7", ""], "empty", "incomplete answer is empty");
testMultipleAnswer(answerData, ["", "2"], "wrong", "partially wrong answer is wrong");
testMultipleAnswer(answerData, ["", ""], "empty", "empty answer is empty");

start();
Expand All @@ -792,7 +794,7 @@
testMultipleAnswer(answerData, ["1", ""], "right", "right answer is right");
testMultipleAnswer(answerData, ["1", " "], "right", "right answer is right");
testMultipleAnswer(answerData, ["", "1"], "right", "right answer is right");
testMultipleAnswer(answerData, ["", ""], "empty", "empty answer is empty");
testMultipleAnswer(answerData, ["", ""], "right", "right answer is right");

start();
});
Expand Down
77 changes: 39 additions & 38 deletions utils/answer-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -1184,57 +1184,58 @@ Khan.answerTypes = $.extend(Khan.answerTypes, {
});

return function(guess) {
var score = {
empty: true,
correct: true,
message: null,
guess: guess
};
var blockGradingMessage = null;

// If the answer is completely empty, don't grade it
if (checkIfAnswerEmpty(guess)) {
score.empty = true;
score.correct = false;
return score;
}
var answered = true; // Are all parts non-empty?
var correct = true; // Are all non-empty parts correct?
// TODO(eater): This just forwards one message
var rightMessage = null; // First from a correct part.
var wrongMessage = null; // First from an incorrect part.
var emptyMessage = null; // First from an empty part.

// Iterate over each of the elements in the guess
$.each(guess, function(i, g) {
// Check whether that answer is right by validating it
// with the corresponding validator
var pass = validators[i](g);

if (pass.message && pass.empty) {
// Special case where a validator returns a message
// for an "empty" response. This probably means it's
// not really empty, but a correct-but-not-simplified
// answer. Rather that treating this as actually empty,
// possibly leading to the entire multiple being marked
// wrong for being incomplete, note the situation but
// continue determining whether the entire answer is
// otherwise correct or not before forwarding on the
// message.
blockGradingMessage = pass.message;
if (pass.empty) {
answered = false;
emptyMessage = emptyMessage || pass.message;
} else {
score.empty = score.empty && pass.empty;
score.correct = score.correct && pass.correct;
// TODO(eater): This just forwards one message
score.message = score.message || pass.message;
if (pass.correct) {
rightMessage = rightMessage || pass.message;
} else {
correct = false;
wrongMessage = wrongMessage || pass.message;
}
}
});

if (score.correct && blockGradingMessage != null) {
return {
empty: true,
correct: false,
message: blockGradingMessage,
guess: guess
};
var message;
if (answered && correct) {
// Technically, correct scores can carry a message.
message = rightMessage;
} else if (correct) {
// There are just empty and correct parts. Grade-blocking
// lack of simplification (number without a data-simplfy)
// is marked as empty, but carries a message.
message = emptyMessage;
} else {
score.empty = false;
return score;
// If any part is wrong, we only want to consider messages
// about the reason for incorrectness.
message = wrongMessage;
}

// If all parts are correct, the answer is correct.
// If some are correct, but some empty, consider it unanswered.
// If there is at least one incorrect part, grade incorrect.
return {
empty: !answered && correct,
// TODO(wrwrwr): Take care of the set validator too and let
// unanswered scores be (partially) correct.
correct: answered && correct,
message: message,
guess: guess
};
};
}
},
Expand Down

0 comments on commit 9a25e47

Please sign in to comment.