Squashed commit of the following: commit 0f7c2af5f7b8caed575dd253a45299293b2729d7 Author: Colin-Fredericks <colin.fredericks@gmail.com> Date: Tue Jun 30 12:04:43 2015 -0400 Forgot icon commit b48970392741130f774709c54eb6e5ab0089812c Author: Colin-Fredericks <colin.fredericks@gmail.com> Date: Tue Jun 30 11:49:57 2015 -0400 OSPR-535 Partial Credit Squashed commit of the following: commit 6dd34f58f994e32d0d54bf1d67bffd04e0f8ef08 Author: Colin-Fredericks <cof945@dhcp-140-247-184-176.fas.harvard.edu> Date: Tue Jun 30 11:44:01 2015 -0400 Fixing accidental overwrite. commit 1ff8fc4b0e83b90356e8e8dce1022f49bfd162cf Author: Colin-Fredericks <cof945@dhcp-140-247-184-176.fas.harvard.edu> Date: Tue Jun 30 11:18:36 2015 -0400 OSPR-535 Partial Credit Revised after first pull discussion. Fixing scss typos Fixing check/x display problem Empty set is not [] Shuffling empty answer code to grade properly. I don't think I ever wrote this in the first place... Adding tests for MC and Checkbox including proper partial-credit marking and scoring Numerical and OptionResponse tests Also a few improvements to NumericalResponse problem type and exception-raising. CustomResponse tests and more numerical tests Increasing coverage and fixing typos Exception added for pylint false positive Hopefully fixing coverage issue Retabulating line continuation Bok Choy test for partial credit Copypasta fix Adding tooltip for partial credit Improving and expanding comments Minor fixes
192 lines
6.9 KiB
Python
192 lines
6.9 KiB
Python
#-----------------------------------------------------------------------------
|
|
# class used to store graded responses to CAPA questions
|
|
#
|
|
# Used by responsetypes and capa_problem
|
|
|
|
|
|
class CorrectMap(object):
|
|
"""
|
|
Stores map between answer_id and response evaluation result for each question
|
|
in a capa problem. The response evaluation result for each answer_id includes
|
|
(correctness, npoints, msg, hint, hintmode).
|
|
|
|
- correctness : 'correct', 'incorrect', or 'partially-correct'
|
|
- npoints : None, or integer specifying number of points awarded for this answer_id
|
|
- msg : string (may have HTML) giving extra message response
|
|
(displayed below textline or textbox)
|
|
- hint : string (may have HTML) giving optional hint
|
|
(displayed below textline or textbox, above msg)
|
|
- hintmode : one of (None,'on_request','always') criteria for displaying hint
|
|
- queuestate : Dict {key:'', time:''} where key is a secret string, and time is a string dump
|
|
of a DateTime object in the format '%Y%m%d%H%M%S'. Is None when not queued
|
|
|
|
Behaves as a dict.
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
# start with empty dict
|
|
self.cmap = dict()
|
|
self.items = self.cmap.items
|
|
self.keys = self.cmap.keys
|
|
self.overall_message = ""
|
|
self.set(*args, **kwargs)
|
|
|
|
def __getitem__(self, *args, **kwargs):
|
|
return self.cmap.__getitem__(*args, **kwargs)
|
|
|
|
def __iter__(self):
|
|
return self.cmap.__iter__()
|
|
|
|
# See the documentation for 'set_dict' for the use of kwargs
|
|
def set(
|
|
self,
|
|
answer_id=None,
|
|
correctness=None,
|
|
npoints=None,
|
|
msg='',
|
|
hint='',
|
|
hintmode=None,
|
|
queuestate=None,
|
|
answervariable=None, # pylint: disable=C0330
|
|
**kwargs
|
|
):
|
|
|
|
if answer_id is not None:
|
|
self.cmap[answer_id] = {
|
|
'correctness': correctness,
|
|
'npoints': npoints,
|
|
'msg': msg,
|
|
'hint': hint,
|
|
'hintmode': hintmode,
|
|
'queuestate': queuestate,
|
|
'answervariable': answervariable,
|
|
}
|
|
|
|
def __repr__(self):
|
|
return repr(self.cmap)
|
|
|
|
def get_dict(self):
|
|
"""
|
|
return dict version of self
|
|
"""
|
|
return self.cmap
|
|
|
|
def set_dict(self, correct_map):
|
|
"""
|
|
Set internal dict of CorrectMap to provided correct_map dict
|
|
|
|
correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This
|
|
means that when the definition of CorrectMap (e.g. its properties) are altered,
|
|
an existing correct_map dict will not coincide with the newest CorrectMap format as
|
|
defined by self.set.
|
|
|
|
For graceful migration, feed the contents of each correct map to self.set, rather than
|
|
making a direct copy of the given correct_map dict. This way, the common keys between
|
|
the incoming correct_map dict and the new CorrectMap instance will be written, while
|
|
mismatched keys will be gracefully ignored.
|
|
|
|
Special migration case:
|
|
If correct_map is a one-level dict, then convert it to the new dict of dicts format.
|
|
|
|
"""
|
|
# empty current dict
|
|
self.__init__()
|
|
|
|
# create new dict entries
|
|
if correct_map and not isinstance(correct_map.values()[0], dict):
|
|
# special migration
|
|
for k in correct_map:
|
|
self.set(k, correctness=correct_map[k])
|
|
else:
|
|
for k in correct_map:
|
|
self.set(k, **correct_map[k])
|
|
|
|
def is_correct(self, answer_id):
|
|
"""
|
|
Takes an answer_id
|
|
Returns true if the problem is correct OR partially correct.
|
|
"""
|
|
if answer_id in self.cmap:
|
|
return self.cmap[answer_id]['correctness'] in ['correct', 'partially-correct']
|
|
return None
|
|
|
|
def is_partially_correct(self, answer_id):
|
|
"""
|
|
Takes an answer_id
|
|
Returns true if the problem is partially correct.
|
|
"""
|
|
if answer_id in self.cmap:
|
|
return self.cmap[answer_id]['correctness'] == 'partially-correct'
|
|
return None
|
|
|
|
def is_queued(self, answer_id):
|
|
return answer_id in self.cmap and self.cmap[answer_id]['queuestate'] is not None
|
|
|
|
def is_right_queuekey(self, answer_id, test_key):
|
|
return self.is_queued(answer_id) and self.cmap[answer_id]['queuestate']['key'] == test_key
|
|
|
|
def get_queuetime_str(self, answer_id):
|
|
if self.cmap[answer_id]['queuestate']:
|
|
return self.cmap[answer_id]['queuestate']['time']
|
|
else:
|
|
return None
|
|
|
|
def get_npoints(self, answer_id):
|
|
"""Return the number of points for an answer, used for partial credit."""
|
|
npoints = self.get_property(answer_id, 'npoints')
|
|
if npoints is not None:
|
|
return npoints
|
|
elif self.is_correct(answer_id):
|
|
return 1
|
|
# if not correct and no points have been assigned, return 0
|
|
return 0
|
|
|
|
def set_property(self, answer_id, property, value):
|
|
if answer_id in self.cmap:
|
|
self.cmap[answer_id][property] = value
|
|
else:
|
|
self.cmap[answer_id] = {property: value}
|
|
|
|
def get_property(self, answer_id, property, default=None):
|
|
if answer_id in self.cmap:
|
|
return self.cmap[answer_id].get(property, default)
|
|
return default
|
|
|
|
def get_correctness(self, answer_id):
|
|
return self.get_property(answer_id, 'correctness')
|
|
|
|
def get_msg(self, answer_id):
|
|
return self.get_property(answer_id, 'msg', '')
|
|
|
|
def get_hint(self, answer_id):
|
|
return self.get_property(answer_id, 'hint', '')
|
|
|
|
def get_hintmode(self, answer_id):
|
|
return self.get_property(answer_id, 'hintmode', None)
|
|
|
|
def set_hint_and_mode(self, answer_id, hint, hintmode):
|
|
"""
|
|
- hint : (string) HTML text for hint
|
|
- hintmode : (string) mode for hint display ('always' or 'on_request')
|
|
"""
|
|
self.set_property(answer_id, 'hint', hint)
|
|
self.set_property(answer_id, 'hintmode', hintmode)
|
|
|
|
def update(self, other_cmap):
|
|
"""
|
|
Update this CorrectMap with the contents of another CorrectMap
|
|
"""
|
|
if not isinstance(other_cmap, CorrectMap):
|
|
raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap)
|
|
self.cmap.update(other_cmap.get_dict())
|
|
self.set_overall_message(other_cmap.get_overall_message())
|
|
|
|
def set_overall_message(self, message_str):
|
|
""" Set a message that applies to the question as a whole,
|
|
rather than to individual inputs. """
|
|
self.overall_message = str(message_str) if message_str else ""
|
|
|
|
def get_overall_message(self):
|
|
""" Retrieve a message that applies to the question as a whole.
|
|
If no message is available, returns the empty string """
|
|
return self.overall_message
|