Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
83c54d4e
Commit
83c54d4e
authored
10 years ago
by
Jason Bau
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5048 from Stanford-Online/jbau/edx/custom-response-fractional-grades
capa custom response support for decimal grades
parents
1e6084b9
637f5541
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
153 additions
and
14 deletions
+153
-14
common/lib/capa/capa/responsetypes.py
+34
-5
common/lib/capa/capa/tests/test_correctmap.py
+2
-2
common/lib/capa/capa/tests/test_responsetypes.py
+88
-2
common/lib/xmodule/xmodule/tests/test_capa_module.py
+29
-5
No files found.
common/lib/capa/capa/responsetypes.py
View file @
83c54d4e
...
@@ -1598,11 +1598,17 @@ class CustomResponse(LoncapaResponse):
...
@@ -1598,11 +1598,17 @@ class CustomResponse(LoncapaResponse):
correct
=
self
.
context
[
'correct'
]
correct
=
self
.
context
[
'correct'
]
messages
=
self
.
context
[
'messages'
]
messages
=
self
.
context
[
'messages'
]
overall_message
=
self
.
clean_message_html
(
self
.
context
[
'overall_message'
])
overall_message
=
self
.
clean_message_html
(
self
.
context
[
'overall_message'
])
grade_decimals
=
self
.
context
.
get
(
'grade_decimals'
)
correct_map
=
CorrectMap
()
correct_map
=
CorrectMap
()
correct_map
.
set_overall_message
(
overall_message
)
correct_map
.
set_overall_message
(
overall_message
)
for
k
in
range
(
len
(
idset
)):
for
k
in
range
(
len
(
idset
)):
npoints
=
self
.
maxpoints
[
idset
[
k
]]
if
correct
[
k
]
==
'correct'
else
0
max_points
=
self
.
maxpoints
[
idset
[
k
]]
if
grade_decimals
:
npoints
=
max_points
*
grade_decimals
[
k
]
else
:
npoints
=
max_points
if
correct
[
k
]
==
'correct'
else
0
correct_map
.
set
(
idset
[
k
],
correct
[
k
],
msg
=
messages
[
k
],
correct_map
.
set
(
idset
[
k
],
correct
[
k
],
msg
=
messages
[
k
],
npoints
=
npoints
)
npoints
=
npoints
)
return
correct_map
return
correct_map
...
@@ -1643,7 +1649,9 @@ class CustomResponse(LoncapaResponse):
...
@@ -1643,7 +1649,9 @@ class CustomResponse(LoncapaResponse):
)
)
if
isinstance
(
ret
,
dict
):
if
isinstance
(
ret
,
dict
):
# One kind of dictionary the check function can return has the
# One kind of dictionary the check function can return has the
# form {'ok': BOOLEAN, 'msg': STRING}
# form {'ok': BOOLEAN, 'msg': STRING, 'grade_decimal' (optional): FLOAT (between 0.0 and 1.0)}
# 'ok' will control the checkmark, while grade_decimal, if present, will scale
# the score the student receives on the response.
# If there are multiple inputs, they all get marked
# If there are multiple inputs, they all get marked
# to the same correct/incorrect value
# to the same correct/incorrect value
if
'ok'
in
ret
:
if
'ok'
in
ret
:
...
@@ -1658,28 +1666,49 @@ class CustomResponse(LoncapaResponse):
...
@@ -1658,28 +1666,49 @@ class CustomResponse(LoncapaResponse):
else
:
else
:
self
.
context
[
'messages'
][
0
]
=
msg
self
.
context
[
'messages'
][
0
]
=
msg
if
'grade_decimal'
in
ret
:
decimal
=
ret
[
'grade_decimal'
]
else
:
decimal
=
1.0
if
ret
[
'ok'
]
else
0.0
grade_decimals
=
[
decimal
]
*
len
(
idset
)
self
.
context
[
'grade_decimals'
]
=
grade_decimals
# Another kind of dictionary the check function can return has
# Another kind of dictionary the check function can return has
# the form:
# the form:
# {'overall_message': STRING,
# { 'overall_message': STRING,
# 'input_list': [{ 'ok': BOOLEAN, 'msg': STRING }, ...] }
# 'input_list': [
# { 'ok': BOOLEAN, 'msg': STRING, 'grade_decimal' (optional): FLOAT (between 0.0 and 1.0)},
# ...
# ]
# }
# 'ok' will control the checkmark, while grade_decimal, if present, will scale
# the score the student receives on the response.
#
#
# This allows the function to return an 'overall message'
# This allows the function to return an 'overall message'
# that applies to the entire problem, as well as correct/incorrect
# that applies to the entire problem, as well as correct/incorrect
# status and messages for individual inputs
# status
, scaled grades,
and messages for individual inputs
elif
'input_list'
in
ret
:
elif
'input_list'
in
ret
:
overall_message
=
ret
.
get
(
'overall_message'
,
''
)
overall_message
=
ret
.
get
(
'overall_message'
,
''
)
input_list
=
ret
[
'input_list'
]
input_list
=
ret
[
'input_list'
]
correct
=
[]
correct
=
[]
messages
=
[]
messages
=
[]
grade_decimals
=
[]
for
input_dict
in
input_list
:
for
input_dict
in
input_list
:
correct
.
append
(
'correct'
correct
.
append
(
'correct'
if
input_dict
[
'ok'
]
else
'incorrect'
)
if
input_dict
[
'ok'
]
else
'incorrect'
)
msg
=
(
self
.
clean_message_html
(
input_dict
[
'msg'
])
msg
=
(
self
.
clean_message_html
(
input_dict
[
'msg'
])
if
'msg'
in
input_dict
else
None
)
if
'msg'
in
input_dict
else
None
)
messages
.
append
(
msg
)
messages
.
append
(
msg
)
if
'grade_decimal'
in
input_dict
:
decimal
=
input_dict
[
'grade_decimal'
]
else
:
decimal
=
1.0
if
input_dict
[
'ok'
]
else
0.0
grade_decimals
.
append
(
decimal
)
self
.
context
[
'messages'
]
=
messages
self
.
context
[
'messages'
]
=
messages
self
.
context
[
'overall_message'
]
=
overall_message
self
.
context
[
'overall_message'
]
=
overall_message
self
.
context
[
'grade_decimals'
]
=
grade_decimals
# Otherwise, we do not recognize the dictionary
# Otherwise, we do not recognize the dictionary
# Raise an exception
# Raise an exception
...
...
This diff is collapsed.
Click to expand it.
common/lib/capa/capa/tests/test_correctmap.py
View file @
83c54d4e
...
@@ -85,7 +85,7 @@ class CorrectMapTest(unittest.TestCase):
...
@@ -85,7 +85,7 @@ class CorrectMapTest(unittest.TestCase):
self
.
cmap
.
set
(
self
.
cmap
.
set
(
answer_id
=
'1_2_1'
,
answer_id
=
'1_2_1'
,
correctness
=
'correct'
,
correctness
=
'correct'
,
npoints
=
5
npoints
=
5
.3
)
)
self
.
cmap
.
set
(
self
.
cmap
.
set
(
...
@@ -116,7 +116,7 @@ class CorrectMapTest(unittest.TestCase):
...
@@ -116,7 +116,7 @@ class CorrectMapTest(unittest.TestCase):
# If points assigned --> npoints
# If points assigned --> npoints
# If no points assigned and correct --> 1 point
# If no points assigned and correct --> 1 point
# If no points assigned and incorrect --> 0 points
# If no points assigned and incorrect --> 0 points
self
.
assertEqual
(
self
.
cmap
.
get_npoints
(
'1_2_1'
),
5
)
self
.
assertEqual
(
self
.
cmap
.
get_npoints
(
'1_2_1'
),
5
.3
)
self
.
assertEqual
(
self
.
cmap
.
get_npoints
(
'2_2_1'
),
1
)
self
.
assertEqual
(
self
.
cmap
.
get_npoints
(
'2_2_1'
),
1
)
self
.
assertEqual
(
self
.
cmap
.
get_npoints
(
'3_2_1'
),
5
)
self
.
assertEqual
(
self
.
cmap
.
get_npoints
(
'3_2_1'
),
5
)
self
.
assertEqual
(
self
.
cmap
.
get_npoints
(
'4_2_1'
),
0
)
self
.
assertEqual
(
self
.
cmap
.
get_npoints
(
'4_2_1'
),
0
)
...
...
This diff is collapsed.
Click to expand it.
common/lib/capa/capa/tests/test_responsetypes.py
View file @
83c54d4e
...
@@ -1399,7 +1399,7 @@ class CustomResponseTest(ResponseTest):
...
@@ -1399,7 +1399,7 @@ class CustomResponseTest(ResponseTest):
# or an ordered list of answers (if there are multiple inputs)
# or an ordered list of answers (if there are multiple inputs)
#
#
# The function should return a dict of the form
# The function should return a dict of the form
# { 'ok': BOOL, 'msg': STRING }
# { 'ok': BOOL, 'msg': STRING }
(no 'grade_decimal' key to test that it's optional)
#
#
script
=
textwrap
.
dedent
(
"""
script
=
textwrap
.
dedent
(
"""
def check_func(expect, answer_given):
def check_func(expect, answer_given):
...
@@ -1414,9 +1414,11 @@ class CustomResponseTest(ResponseTest):
...
@@ -1414,9 +1414,11 @@ class CustomResponseTest(ResponseTest):
correctness
=
correct_map
.
get_correctness
(
'1_2_1'
)
correctness
=
correct_map
.
get_correctness
(
'1_2_1'
)
msg
=
correct_map
.
get_msg
(
'1_2_1'
)
msg
=
correct_map
.
get_msg
(
'1_2_1'
)
npoints
=
correct_map
.
get_npoints
(
'1_2_1'
)
self
.
assertEqual
(
correctness
,
'correct'
)
self
.
assertEqual
(
correctness
,
'correct'
)
self
.
assertEqual
(
msg
,
"Message text"
)
self
.
assertEqual
(
msg
,
"Message text"
)
self
.
assertEqual
(
npoints
,
1
)
# Incorrect answer
# Incorrect answer
input_dict
=
{
'1_2_1'
:
'0'
}
input_dict
=
{
'1_2_1'
:
'0'
}
...
@@ -1424,9 +1426,45 @@ class CustomResponseTest(ResponseTest):
...
@@ -1424,9 +1426,45 @@ class CustomResponseTest(ResponseTest):
correctness
=
correct_map
.
get_correctness
(
'1_2_1'
)
correctness
=
correct_map
.
get_correctness
(
'1_2_1'
)
msg
=
correct_map
.
get_msg
(
'1_2_1'
)
msg
=
correct_map
.
get_msg
(
'1_2_1'
)
npoints
=
correct_map
.
get_npoints
(
'1_2_1'
)
self
.
assertEqual
(
correctness
,
'incorrect'
)
self
.
assertEqual
(
correctness
,
'incorrect'
)
self
.
assertEqual
(
msg
,
"Message text"
)
self
.
assertEqual
(
msg
,
"Message text"
)
self
.
assertEqual
(
npoints
,
0
)
def
test_function_code_single_input_decimal_score
(
self
):
# For function code, we pass in these arguments:
#
# 'expect' is the expect attribute of the <customresponse>
#
# 'answer_given' is the answer the student gave (if there is just one input)
# or an ordered list of answers (if there are multiple inputs)
#
# The function should return a dict of the form
# { 'ok': BOOL, 'msg': STRING, 'grade_decimal': FLOAT }
#
script
=
textwrap
.
dedent
(
"""
def check_func(expect, answer_given):
return {
'ok': answer_given == expect,
'msg': 'Message text',
'grade_decimal': 0.9 if answer_given == expect else 0.1,
}
"""
)
problem
=
self
.
build_problem
(
script
=
script
,
cfn
=
"check_func"
,
expect
=
"42"
)
# Correct answer
input_dict
=
{
'1_2_1'
:
'42'
}
correct_map
=
problem
.
grade_answers
(
input_dict
)
self
.
assertEqual
(
correct_map
.
get_npoints
(
'1_2_1'
),
0.9
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_1'
),
'correct'
)
# Incorrect answer
input_dict
=
{
'1_2_1'
:
'43'
}
correct_map
=
problem
.
grade_answers
(
input_dict
)
self
.
assertEqual
(
correct_map
.
get_npoints
(
'1_2_1'
),
0.1
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_1'
),
'incorrect'
)
def
test_function_code_multiple_input_no_msg
(
self
):
def
test_function_code_multiple_input_no_msg
(
self
):
...
@@ -1469,7 +1507,7 @@ class CustomResponseTest(ResponseTest):
...
@@ -1469,7 +1507,7 @@ class CustomResponseTest(ResponseTest):
# the check function can return a dict of the form:
# the check function can return a dict of the form:
#
#
# {'overall_message': STRING,
# {'overall_message': STRING,
# 'input_list': [{'ok': BOOL, 'msg': STRING}, ...] }
# 'input_list': [{'ok': BOOL, 'msg': STRING}, ...] }
(no grade_decimal to test it's optional)
#
#
# 'overall_message' is displayed at the end of the response
# 'overall_message' is displayed at the end of the response
#
#
...
@@ -1502,11 +1540,59 @@ class CustomResponseTest(ResponseTest):
...
@@ -1502,11 +1540,59 @@ class CustomResponseTest(ResponseTest):
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_2'
),
'correct'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_2'
),
'correct'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_3'
),
'correct'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_3'
),
'correct'
)
# Expect that the inputs were given correct npoints
self
.
assertEqual
(
correct_map
.
get_npoints
(
'1_2_1'
),
0
)
self
.
assertEqual
(
correct_map
.
get_npoints
(
'1_2_2'
),
1
)
self
.
assertEqual
(
correct_map
.
get_npoints
(
'1_2_3'
),
1
)
# Expect that we received messages for each individual input
# Expect that we received messages for each individual input
self
.
assertEqual
(
correct_map
.
get_msg
(
'1_2_1'
),
'Feedback 1'
)
self
.
assertEqual
(
correct_map
.
get_msg
(
'1_2_1'
),
'Feedback 1'
)
self
.
assertEqual
(
correct_map
.
get_msg
(
'1_2_2'
),
'Feedback 2'
)
self
.
assertEqual
(
correct_map
.
get_msg
(
'1_2_2'
),
'Feedback 2'
)
self
.
assertEqual
(
correct_map
.
get_msg
(
'1_2_3'
),
'Feedback 3'
)
self
.
assertEqual
(
correct_map
.
get_msg
(
'1_2_3'
),
'Feedback 3'
)
def
test_function_code_multiple_inputs_decimal_score
(
self
):
# If the <customresponse> has multiple inputs associated with it,
# the check function can return a dict of the form:
#
# {'overall_message': STRING,
# 'input_list': [{'ok': BOOL, 'msg': STRING, 'grade_decimal': FLOAT}, ...] }
# #
# 'input_list' contains dictionaries representing the correctness
# and message for each input.
script
=
textwrap
.
dedent
(
"""
def check_func(expect, answer_given):
check1 = (int(answer_given[0]) == 1)
check2 = (int(answer_given[1]) == 2)
check3 = (int(answer_given[2]) == 3)
score1 = 0.9 if check1 else 0.1
score2 = 0.9 if check2 else 0.1
score3 = 0.9 if check3 else 0.1
return {
'input_list': [
{'ok': check1, 'grade_decimal': score1, 'msg': 'Feedback 1'},
{'ok': check2, 'grade_decimal': score2, 'msg': 'Feedback 2'},
{'ok': check3, 'grade_decimal': score3, 'msg': 'Feedback 3'},
]
}
"""
)
problem
=
self
.
build_problem
(
script
=
script
,
cfn
=
"check_func"
,
num_inputs
=
3
)
# Grade the inputs (one input incorrect)
input_dict
=
{
'1_2_1'
:
'-999'
,
'1_2_2'
:
'2'
,
'1_2_3'
:
'3'
}
correct_map
=
problem
.
grade_answers
(
input_dict
)
# Expect that the inputs were graded individually
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_1'
),
'incorrect'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_2'
),
'correct'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_3'
),
'correct'
)
# Expect that the inputs were given correct npoints
self
.
assertEqual
(
correct_map
.
get_npoints
(
'1_2_1'
),
0.1
)
self
.
assertEqual
(
correct_map
.
get_npoints
(
'1_2_2'
),
0.9
)
self
.
assertEqual
(
correct_map
.
get_npoints
(
'1_2_3'
),
0.9
)
def
test_function_code_with_extra_args
(
self
):
def
test_function_code_with_extra_args
(
self
):
script
=
textwrap
.
dedent
(
"""
\
script
=
textwrap
.
dedent
(
"""
\
def check_func(expect, answer_given, options, dynamath):
def check_func(expect, answer_given, options, dynamath):
...
...
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/tests/test_capa_module.py
View file @
83c54d4e
...
@@ -83,6 +83,7 @@ class CapaFactory(object):
...
@@ -83,6 +83,7 @@ class CapaFactory(object):
problem_state
=
None
,
problem_state
=
None
,
correct
=
False
,
correct
=
False
,
xml
=
None
,
xml
=
None
,
override_get_score
=
True
,
**
kwargs
**
kwargs
):
):
"""
"""
...
@@ -130,11 +131,12 @@ class CapaFactory(object):
...
@@ -130,11 +131,12 @@ class CapaFactory(object):
ScopeIds
(
None
,
None
,
location
,
location
),
ScopeIds
(
None
,
None
,
location
,
location
),
)
)
if
correct
:
if
override_get_score
:
# TODO: probably better to actually set the internal state properly, but...
if
correct
:
module
.
get_score
=
lambda
:
{
'score'
:
1
,
'total'
:
1
}
# TODO: probably better to actually set the internal state properly, but...
else
:
module
.
get_score
=
lambda
:
{
'score'
:
1
,
'total'
:
1
}
module
.
get_score
=
lambda
:
{
'score'
:
0
,
'total'
:
1
}
else
:
module
.
get_score
=
lambda
:
{
'score'
:
0
,
'total'
:
1
}
return
module
return
module
...
@@ -211,6 +213,28 @@ class CapaModuleTest(unittest.TestCase):
...
@@ -211,6 +213,28 @@ class CapaModuleTest(unittest.TestCase):
other_module
=
CapaFactory
.
create
(
correct
=
True
)
other_module
=
CapaFactory
.
create
(
correct
=
True
)
self
.
assertEqual
(
other_module
.
get_score
()[
'score'
],
1
)
self
.
assertEqual
(
other_module
.
get_score
()[
'score'
],
1
)
def
test_get_score
(
self
):
"""
Do 1 test where the internals of get_score are properly set
@jbau Note: this obviously depends on a particular implementation of get_score, but I think this is actually
useful as unit-code coverage for this current implementation. I don't see a layer where LoncapaProblem
is tested directly
"""
from
capa.correctmap
import
CorrectMap
student_answers
=
{
'1_2_1'
:
'abcd'
}
correct_map
=
CorrectMap
(
answer_id
=
'1_2_1'
,
correctness
=
"correct"
,
npoints
=
0.9
)
module
=
CapaFactory
.
create
(
correct
=
True
,
override_get_score
=
False
)
module
.
lcp
.
correct_map
=
correct_map
module
.
lcp
.
student_answers
=
student_answers
self
.
assertEqual
(
module
.
get_score
()[
'score'
],
0.9
)
other_correct_map
=
CorrectMap
(
answer_id
=
'1_2_1'
,
correctness
=
"incorrect"
,
npoints
=
0.1
)
other_module
=
CapaFactory
.
create
(
correct
=
False
,
override_get_score
=
False
)
other_module
.
lcp
.
correct_map
=
other_correct_map
other_module
.
lcp
.
student_answers
=
student_answers
self
.
assertEqual
(
other_module
.
get_score
()[
'score'
],
0.1
)
def
test_showanswer_default
(
self
):
def
test_showanswer_default
(
self
):
"""
"""
Make sure the show answer logic does the right thing.
Make sure the show answer logic does the right thing.
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment