Commit 6d3d69a2 by Brian Wilson

Fix seeds and correctness in non-submission problem check events.

Fix AN-663 and AN-664.

Change-Id: I704e64a7679c8fec35f2e70355b55f6cffb06580
parent b9a9cad9
...@@ -168,13 +168,16 @@ class LastProblemCheckEventMixin(object): ...@@ -168,13 +168,16 @@ class LastProblemCheckEventMixin(object):
# of any actual values selected by the student. # of any actual values selected by the student.
# For now, let's just dump an error and skip it, # For now, let's just dump an error and skip it,
# so that it becomes the equivalent of a hidden # so that it becomes the equivalent of a hidden
# answer. Eventually we would probably want to treat # answer.
# it explicitly as a hidden answer.
# TODO: Eventually treat it explicitly as a hidden
# answer.
if answer_id not in correct_map: if answer_id not in correct_map:
log.error("Unexpected answer_id %s not in correct_map: %s", answer_id, event) log.error("Unexpected answer_id %s not in correct_map: %s", answer_id, event)
continue continue
correctness = correct_map[answer_id].get('correctness') in ['correct']
correct_entry = correct_map[answer_id] variant = event.get('state', {}).get('seed')
# We do not know the values for 'input_type', # We do not know the values for 'input_type',
# 'response_type', or 'question'. We also don't know if # 'response_type', or 'question'. We also don't know if
...@@ -185,8 +188,8 @@ class LastProblemCheckEventMixin(object): ...@@ -185,8 +188,8 @@ class LastProblemCheckEventMixin(object):
# an 'answer' and only sometimes have an 'answer_value_id'. # an 'answer' and only sometimes have an 'answer_value_id'.
submission = { submission = {
'answer_value_id': answer_value, 'answer_value_id': answer_value,
'correct': correct_entry.get('correctness'), 'correct': correctness,
'variant': event.get('seed'), 'variant': variant,
} }
append_submission(answer_id, submission) append_submission(answer_id, submission)
...@@ -457,7 +460,14 @@ class AnswerDistributionPerCourseMixin(object): ...@@ -457,7 +460,14 @@ class AnswerDistributionPerCourseMixin(object):
def get_answer_grouping_key(self, answer): def get_answer_grouping_key(self, answer):
"""Return value to use for uniquely identify an answer value in the distribution.""" """Return value to use for uniquely identify an answer value in the distribution."""
variant = answer.get('variant', 'NO_VARIANT') # For variants, we want to treat missing variants with the
# same value as used for events that lack 'submission'
# information, so that they will be grouped together. That
# value is a seed value of '1'. We want to map both missing
# values and zero-length values to this default value.
variant = answer.get('variant', '')
if variant == '':
variant = '1'
# Events that lack 'submission' information will have a value # Events that lack 'submission' information will have a value
# for 'answer_value_id' and none for 'answer'. Events with # for 'answer_value_id' and none for 'answer'. Events with
# 'submission' information will have the reverse situation # 'submission' information will have the reverse situation
......
...@@ -39,7 +39,6 @@ class LastProblemCheckEventBaseTest(unittest.TestCase): ...@@ -39,7 +39,6 @@ class LastProblemCheckEventBaseTest(unittest.TestCase):
"""Returns event data dict with test values.""" """Returns event data dict with test values."""
event_data = { event_data = {
"problem_id": self.problem_id, "problem_id": self.problem_id,
"seed": 1,
"attempts": 2, "attempts": 2,
"answers": {self.answer_id: "3"}, "answers": {self.answer_id: "3"},
"correct_map": { "correct_map": {
...@@ -223,93 +222,107 @@ class LastProblemCheckEventReduceTest(LastProblemCheckEventBaseTest): ...@@ -223,93 +222,107 @@ class LastProblemCheckEventReduceTest(LastProblemCheckEventBaseTest):
self.assertTrue(answer_id in expected) self.assertTrue(answer_id in expected)
self.assertEquals(json.loads(answer_data), expected.get(answer_id)) self.assertEquals(json.loads(answer_data), expected.get(answer_id))
def test_no_events(self): def _add_second_answer(self, problem_data, answer_id=None):
self._check_output([], tuple()) """Adds a second answer to an existing problem check event."""
if answer_id is None:
answer_id = self.second_answer_id
problem_data['answers'][answer_id] = "4"
problem_data['correct_map'][answer_id] = {
"correctness": "incorrect",
}
def _get_answer_data(self, **kwargs): def _get_answer_data(self, **kwargs):
"""Returns expected answer data.""" """Returns expected answer data returned by the reducer."""
answer_data = { answer_data = {
"answer_value_id": "3", "answer_value_id": "3",
"problem_display_name": None, "problem_display_name": None,
"variant": 1, "variant": 1,
"correct": "incorrect", "correct": False,
"problem_id": self.problem_id, "problem_id": self.problem_id,
} }
answer_data.update(**kwargs) answer_data.update(**kwargs)
return answer_data return answer_data
def test_one_answer_event(self): def _create_submission_problem_data_dict(self, **kwargs):
problem_data = self._create_problem_data_dict() """Returns problem event data with test values for 'submission'."""
input_data = (self.timestamp, json.dumps(problem_data)) problem_data = self._create_problem_data_dict(**kwargs)
answer_data = self._get_answer_data() problem_data_submission = {
self._check_output([input_data], {self.answer_id: answer_data})
def test_one_submission_event(self):
problem_data = self._create_problem_data_dict()
problem_data['submission'] = {
self.answer_id: { self.answer_id: {
"input_type": "formulaequationinput", "input_type": "formulaequationinput",
"question": "Enter the number of fingers on a human hand", "question": "Enter the number of fingers on a human hand",
"response_type": "numericalresponse", "response_type": "numericalresponse",
"answer": "3", "answer": "3",
"variant": 629, "variant": "",
"correct": False "correct": False
}, },
} }
self._update_with_kwargs(problem_data_submission, **kwargs)
if 'answer_value_id' in kwargs:
problem_data_submission[self.answer_id]['answer_value_id'] = kwargs['answer_value_id']
problem_data['submission'] = problem_data_submission
return problem_data
def _get_answer_data_from_submission(self, problem_data, **kwargs):
"""Returns expected answer data returned by the reducer, given the event's data."""
print problem_data
answer_data = {}
problem_data_submission = problem_data['submission']
print problem_data_submission
for answer_id in problem_data_submission:
problem_data_sub = problem_data_submission[answer_id]
print problem_data_sub
answer_id_data = {
"answer": problem_data_sub['answer'],
"problem_display_name": None,
"variant": problem_data_sub['variant'],
"correct": problem_data_sub['correct'],
"input_type": problem_data_sub['input_type'],
"response_type": problem_data_sub['response_type'],
"question": problem_data_sub['question'],
"problem_id": self.problem_id,
}
if 'answer_value_id' in problem_data_sub:
answer_id_data['answer_value_id'] = problem_data_sub['answer_value_id']
self._update_with_kwargs(answer_id_data, **kwargs)
answer_data[answer_id] = answer_id_data
return answer_data
def test_no_events(self):
self._check_output([], tuple())
def test_one_answer_event(self):
problem_data = self._create_problem_data_dict()
input_data = (self.timestamp, json.dumps(problem_data)) input_data = (self.timestamp, json.dumps(problem_data))
answer_data = self._get_answer_data( answer_data = self._get_answer_data()
answer="3", self._check_output([input_data], {self.answer_id: answer_data})
variant=629,
correct=False, def test_one_correct_answer_event(self):
input_type="formulaequationinput", problem_data = self._create_problem_data_dict(
question="Enter the number of fingers on a human hand", correct_map={self.answer_id: {"correctness": "correct"}}
response_type="numericalresponse",
) )
del answer_data['answer_value_id'] input_data = (self.timestamp, json.dumps(problem_data))
answer_data = self._get_answer_data(correct=True)
self._check_output([input_data], {self.answer_id: answer_data})
def test_one_submission_event(self):
problem_data = self._create_submission_problem_data_dict()
input_data = (self.timestamp, json.dumps(problem_data))
answer_data = self._get_answer_data_from_submission(problem_data)[self.answer_id]
self._check_output([input_data], {self.answer_id: answer_data}) self._check_output([input_data], {self.answer_id: answer_data})
def test_one_submission_with_value_id(self): def test_one_submission_with_value_id(self):
problem_data = self._create_problem_data_dict() problem_data = self._create_submission_problem_data_dict(answer=3, answer_value_id='choice_3')
problem_data['submission'] = {
self.answer_id: {
"input_type": "formulaequationinput",
"question": "Enter the number of fingers on a human hand",
"response_type": "choiceresponse",
"answer": "3",
"answer_value_id": "choice_3",
"variant": 629,
"correct": False
},
}
input_data = (self.timestamp, json.dumps(problem_data)) input_data = (self.timestamp, json.dumps(problem_data))
answer_data = self._get_answer_data( answer_data = self._get_answer_data_from_submission(problem_data)[self.answer_id]
answer="3",
answer_value_id="choice_3",
variant=629,
correct=False,
input_type="formulaequationinput",
question="Enter the number of fingers on a human hand",
response_type="choiceresponse",
)
self._check_output([input_data], {self.answer_id: answer_data}) self._check_output([input_data], {self.answer_id: answer_data})
def _add_second_answer(self, problem_data, answer_id=None): def test_one_submission_with_variant(self):
"""Adds a second answer to an existing problem.""" problem_data = self._create_submission_problem_data_dict(variant=629)
if answer_id is None: input_data = (self.timestamp, json.dumps(problem_data))
answer_id = self.second_answer_id answer_data = self._get_answer_data_from_submission(problem_data)[self.answer_id]
problem_data['answers'][answer_id] = "4" self._check_output([input_data], {self.answer_id: answer_data})
problem_data['correct_map'][answer_id] = {
"correctness": "incorrect",
}
if 'submission' in problem_data:
problem_data['submission'][answer_id] = {
"input_type": "formulaequationinput",
"question": "Enter the number of fingers on the other hand",
"response_type": "numericalresponse",
"answer": "4",
"variant": 629,
"correct": False,
}
def test_two_answer_event(self): def test_two_answer_event(self):
problem_data = self._create_problem_data_dict() problem_data = self._create_problem_data_dict()
...@@ -323,42 +336,18 @@ class LastProblemCheckEventReduceTest(LastProblemCheckEventBaseTest): ...@@ -323,42 +336,18 @@ class LastProblemCheckEventReduceTest(LastProblemCheckEventBaseTest):
}) })
def test_two_answer_submission_event(self): def test_two_answer_submission_event(self):
problem_data = self._create_problem_data_dict() problem_data = self._create_submission_problem_data_dict()
problem_data['submission'] = { problem_data_2 = self._create_submission_problem_data_dict(
self.answer_id: { answer='4',
"input_type": "formulaequationinput",
"question": "Enter the number of fingers on a human hand",
"response_type": "numericalresponse",
"answer": "3",
"variant": 1,
"correct": False
},
}
self._add_second_answer(problem_data)
input_data = (self.timestamp, json.dumps(problem_data))
answer_data = self._get_answer_data(
answer="3",
variant=1,
correct=False,
input_type="formulaequationinput",
question="Enter the number of fingers on a human hand",
response_type="numericalresponse",
)
del answer_data['answer_value_id']
answer_data_2 = self._get_answer_data(
answer="4",
variant=629, variant=629,
correct=False, question="Enter the number of fingers on the other hand"
input_type="formulaequationinput",
question="Enter the number of fingers on the other hand",
response_type="numericalresponse",
) )
del answer_data_2['answer_value_id'] for key in ['answers', 'correct_map', 'submission']:
problem_data[key][self.second_answer_id] = problem_data_2[key][self.answer_id]
self._check_output([input_data], { input_data = (self.timestamp, json.dumps(problem_data))
self.answer_id: answer_data, self.second_answer_id: answer_data_2 answer_data = self._get_answer_data_from_submission(problem_data)
}) self._check_output([input_data], answer_data)
def test_hidden_answer_event(self): def test_hidden_answer_event(self):
for hidden_suffix in ['_dynamath', '_comment']: for hidden_suffix in ['_dynamath', '_comment']:
...@@ -419,17 +408,17 @@ class AnswerDistributionPerCourseReduceTest(unittest.TestCase): ...@@ -419,17 +408,17 @@ class AnswerDistributionPerCourseReduceTest(unittest.TestCase):
for course_id, _output in reducer_output: for course_id, _output in reducer_output:
self.assertEquals(course_id, self.course_id) self.assertEquals(course_id, self.course_id)
# We don't know what order the outputs will be dumped for a given # We don't know what order the outputs will be dumped for a given
# set of inputs, so we have to compare sets. # set of input dicts, so we have to compare sets of items.
reducer_outputs = set([frozenset(json.loads(output).items()) for _, output in reducer_output]) reducer_outputs = set([frozenset(json.loads(output).items()) for _, output in reducer_output])
expected_outputs = set([frozenset(output.items()) for output in expected]) expected_outputs = set([frozenset(output.items()) for output in expected])
self.assertEquals(reducer_outputs, expected_outputs) self.assertEquals(reducer_outputs, expected_outputs)
def _get_answer_data(self, **kwargs): def _get_answer_data(self, **kwargs):
"""Returns answer data for input with submission information.""" """Returns answer data with submission information for input to reducer."""
answer_data = { answer_data = {
"answer": u"\u00b2", "answer": u"\u00b2",
"problem_display_name": None, "problem_display_name": None,
"variant": None, "variant": "",
"correct": False, "correct": False,
"problem_id": self.problem_id, "problem_id": self.problem_id,
"input_type": "formulaequationinput", "input_type": "formulaequationinput",
...@@ -440,11 +429,11 @@ class AnswerDistributionPerCourseReduceTest(unittest.TestCase): ...@@ -440,11 +429,11 @@ class AnswerDistributionPerCourseReduceTest(unittest.TestCase):
return answer_data return answer_data
def _get_non_submission_answer_data(self, **kwargs): def _get_non_submission_answer_data(self, **kwargs):
"""Returns answer data for input without submission information.""" """Returns answer data without submission information for input to reducer ."""
answer_data = { answer_data = {
"answer_value_id": u'\u00b2', "answer_value_id": u'\u00b2',
"problem_display_name": None, "problem_display_name": None,
"variant": None, "variant": "1",
"correct": False, "correct": False,
"problem_id": self.problem_id, "problem_id": self.problem_id,
} }
...@@ -452,7 +441,7 @@ class AnswerDistributionPerCourseReduceTest(unittest.TestCase): ...@@ -452,7 +441,7 @@ class AnswerDistributionPerCourseReduceTest(unittest.TestCase):
return answer_data return answer_data
def _get_expected_output(self, answer_data, **kwargs): def _get_expected_output(self, answer_data, **kwargs):
"""Get an output based on the input.""" """Get an expected reducer output based on the input."""
expected_output = { expected_output = {
"Problem Display Name": answer_data.get('problem_display_name'), "Problem Display Name": answer_data.get('problem_display_name'),
"Count": 1, "Count": 1,
...@@ -545,6 +534,14 @@ class AnswerDistributionPerCourseReduceTest(unittest.TestCase): ...@@ -545,6 +534,14 @@ class AnswerDistributionPerCourseReduceTest(unittest.TestCase):
expected_output = self._get_expected_output(answer_data_2, Count=2) expected_output = self._get_expected_output(answer_data_2, Count=2)
self._check_output([input_data_1, input_data_2], (expected_output,)) self._check_output([input_data_1, input_data_2], (expected_output,))
def test_same_old_and_new_with_variant(self):
answer_data_1 = self._get_non_submission_answer_data(variant=123)
answer_data_2 = self._get_answer_data(variant=123)
input_data_1 = (self.earlier_timestamp, json.dumps(answer_data_1))
input_data_2 = (self.timestamp, json.dumps(answer_data_2))
expected_output = self._get_expected_output(answer_data_2, Count=2)
self._check_output([input_data_1, input_data_2], (expected_output,))
def test_two_answer_event_different_answer(self): def test_two_answer_event_different_answer(self):
answer_data_1 = self._get_answer_data(answer="first") answer_data_1 = self._get_answer_data(answer="first")
answer_data_2 = self._get_answer_data(answer="second") answer_data_2 = self._get_answer_data(answer="second")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment