Commit 8e364e6d by Victor Shnayder

ensure float when computing module score

parent c9fd7b22
...@@ -13,33 +13,33 @@ log = logging.getLogger("mitx.courseware") ...@@ -13,33 +13,33 @@ log = logging.getLogger("mitx.courseware")
def yield_module_descendents(module): def yield_module_descendents(module):
stack = module.get_display_items() stack = module.get_display_items()
while len(stack) > 0: while len(stack) > 0:
next_module = stack.pop() next_module = stack.pop()
stack.extend( next_module.get_display_items() ) stack.extend( next_module.get_display_items() )
yield next_module yield next_module
def grade(student, request, course, student_module_cache=None): def grade(student, request, course, student_module_cache=None):
""" """
This grades a student as quickly as possible. It retuns the This grades a student as quickly as possible. It retuns the
output from the course grader, augmented with the final letter output from the course grader, augmented with the final letter
grade. The keys in the output are: grade. The keys in the output are:
- grade : A final letter grade. - grade : A final letter grade.
- percent : The final percent for the class (rounded up). - percent : The final percent for the class (rounded up).
- section_breakdown : A breakdown of each section that makes - section_breakdown : A breakdown of each section that makes
up the grade. (For display) up the grade. (For display)
- grade_breakdown : A breakdown of the major components that - grade_breakdown : A breakdown of the major components that
make up the final grade. (For display) make up the final grade. (For display)
More information on the format is in the docstring for CourseGrader. More information on the format is in the docstring for CourseGrader.
""" """
grading_context = course.grading_context grading_context = course.grading_context
if student_module_cache == None: if student_module_cache == None:
student_module_cache = StudentModuleCache(student, grading_context['all_descriptors']) student_module_cache = StudentModuleCache(student, grading_context['all_descriptors'])
totaled_scores = {} totaled_scores = {}
# This next complicated loop is just to collect the totaled_scores, which is # This next complicated loop is just to collect the totaled_scores, which is
# passed to the grader # passed to the grader
...@@ -48,91 +48,91 @@ def grade(student, request, course, student_module_cache=None): ...@@ -48,91 +48,91 @@ def grade(student, request, course, student_module_cache=None):
for section in sections: for section in sections:
section_descriptor = section['section_descriptor'] section_descriptor = section['section_descriptor']
section_name = section_descriptor.metadata.get('display_name') section_name = section_descriptor.metadata.get('display_name')
should_grade_section = False should_grade_section = False
# If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0% # If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0%
for moduledescriptor in section['xmoduledescriptors']: for moduledescriptor in section['xmoduledescriptors']:
if student_module_cache.lookup(moduledescriptor.category, moduledescriptor.location.url() ): if student_module_cache.lookup(moduledescriptor.category, moduledescriptor.location.url() ):
should_grade_section = True should_grade_section = True
break break
if should_grade_section: if should_grade_section:
scores = [] scores = []
# TODO: We need the request to pass into here. If we could forgo that, our arguments # TODO: We need the request to pass into here. If we could forgo that, our arguments
# would be simpler # would be simpler
section_module = get_module(student, request, section_descriptor.location, student_module_cache) section_module = get_module(student, request, section_descriptor.location, student_module_cache)
# TODO: We may be able to speed this up by only getting a list of children IDs from section_module # TODO: We may be able to speed this up by only getting a list of children IDs from section_module
# Then, we may not need to instatiate any problems if they are already in the database # Then, we may not need to instatiate any problems if they are already in the database
for module in yield_module_descendents(section_module): for module in yield_module_descendents(section_module):
(correct, total) = get_score(student, module, student_module_cache) (correct, total) = get_score(student, module, student_module_cache)
if correct is None and total is None: if correct is None and total is None:
continue continue
if settings.GENERATE_PROFILE_SCORES: if settings.GENERATE_PROFILE_SCORES:
if total > 1: if total > 1:
correct = random.randrange(max(total - 2, 1), total + 1) correct = random.randrange(max(total - 2, 1), total + 1)
else: else:
correct = total correct = total
graded = module.metadata.get("graded", False) graded = module.metadata.get("graded", False)
if not total > 0: if not total > 0:
#We simply cannot grade a problem that is 12/0, because we might need it as a percentage #We simply cannot grade a problem that is 12/0, because we might need it as a percentage
graded = False graded = False
scores.append(Score(correct, total, graded, module.metadata.get('display_name'))) scores.append(Score(correct, total, graded, module.metadata.get('display_name')))
section_total, graded_total = graders.aggregate_scores(scores, section_name) section_total, graded_total = graders.aggregate_scores(scores, section_name)
else: else:
section_total = Score(0.0, 1.0, False, section_name) section_total = Score(0.0, 1.0, False, section_name)
graded_total = Score(0.0, 1.0, True, section_name) graded_total = Score(0.0, 1.0, True, section_name)
#Add the graded total to totaled_scores #Add the graded total to totaled_scores
if graded_total.possible > 0: if graded_total.possible > 0:
format_scores.append(graded_total) format_scores.append(graded_total)
else: else:
log.exception("Unable to grade a section with a total possible score of zero. " + str(section_descriptor.location)) log.exception("Unable to grade a section with a total possible score of zero. " + str(section_descriptor.location))
totaled_scores[section_format] = format_scores totaled_scores[section_format] = format_scores
grade_summary = course.grader.grade(totaled_scores) grade_summary = course.grader.grade(totaled_scores)
# We round the grade here, to make sure that the grade is an whole percentage and # We round the grade here, to make sure that the grade is an whole percentage and
# doesn't get displayed differently than it gets grades # doesn't get displayed differently than it gets grades
grade_summary['percent'] = round(grade_summary['percent'] * 100 + 0.05) / 100 grade_summary['percent'] = round(grade_summary['percent'] * 100 + 0.05) / 100
letter_grade = grade_for_percentage(course.grade_cutoffs, grade_summary['percent']) letter_grade = grade_for_percentage(course.grade_cutoffs, grade_summary['percent'])
grade_summary['grade'] = letter_grade grade_summary['grade'] = letter_grade
return grade_summary return grade_summary
def grade_for_percentage(grade_cutoffs, percentage): def grade_for_percentage(grade_cutoffs, percentage):
""" """
Returns a letter grade 'A' 'B' 'C' or None. Returns a letter grade 'A' 'B' 'C' or None.
Arguments Arguments
- grade_cutoffs is a dictionary mapping a grade to the lowest - grade_cutoffs is a dictionary mapping a grade to the lowest
possible percentage to earn that grade. possible percentage to earn that grade.
- percentage is the final percent across all problems in a course - percentage is the final percent across all problems in a course
""" """
letter_grade = None letter_grade = None
for possible_grade in ['A', 'B', 'C']: for possible_grade in ['A', 'B', 'C']:
if percentage >= grade_cutoffs[possible_grade]: if percentage >= grade_cutoffs[possible_grade]:
letter_grade = possible_grade letter_grade = possible_grade
break break
return letter_grade return letter_grade
def progress_summary(student, course, grader, student_module_cache): def progress_summary(student, course, grader, student_module_cache):
""" """
This pulls a summary of all problems in the course. This pulls a summary of all problems in the course.
Returns Returns
- courseware_summary is a summary of all sections with problems in the course. - courseware_summary is a summary of all sections with problems in the course.
It is organized as an array of chapters, each containing an array of sections, It is organized as an array of chapters, each containing an array of sections,
each containing an array of scores. This contains information for graded and each containing an array of scores. This contains information for graded and
ungraded problems, and is good for displaying a course summary with due dates, ungraded problems, and is good for displaying a course summary with due dates,
etc. etc.
Arguments: Arguments:
...@@ -152,7 +152,7 @@ def progress_summary(student, course, grader, student_module_cache): ...@@ -152,7 +152,7 @@ def progress_summary(student, course, grader, student_module_cache):
if correct is None and total is None: if correct is None and total is None:
continue continue
scores.append(Score(correct, total, graded, scores.append(Score(correct, total, graded,
module.metadata.get('display_name'))) module.metadata.get('display_name')))
section_total, graded_total = graders.aggregate_scores( section_total, graded_total = graders.aggregate_scores(
...@@ -179,7 +179,7 @@ def progress_summary(student, course, grader, student_module_cache): ...@@ -179,7 +179,7 @@ def progress_summary(student, course, grader, student_module_cache):
def get_score(user, problem, student_module_cache): def get_score(user, problem, student_module_cache):
""" """
Return the score for a user on a problem Return the score for a user on a problem, as a tuple (correct, total).
user: a Student object user: a Student object
problem: an XModule problem: an XModule
...@@ -188,7 +188,7 @@ def get_score(user, problem, student_module_cache): ...@@ -188,7 +188,7 @@ def get_score(user, problem, student_module_cache):
if not (problem.descriptor.stores_state and problem.descriptor.has_score): if not (problem.descriptor.stores_state and problem.descriptor.has_score):
# These are not problems, and do not have a score # These are not problems, and do not have a score
return (None, None) return (None, None)
correct = 0.0 correct = 0.0
# If the ID is not in the cache, add the item # If the ID is not in the cache, add the item
...@@ -219,7 +219,7 @@ def get_score(user, problem, student_module_cache): ...@@ -219,7 +219,7 @@ def get_score(user, problem, student_module_cache):
if total == 0: if total == 0:
log.exception("Cannot reweight a problem with zero weight. Problem: " + str(instance_module)) log.exception("Cannot reweight a problem with zero weight. Problem: " + str(instance_module))
return (correct, total) return (correct, total)
correct = correct * weight / total correct = float(correct) * weight / total
total = weight total = weight
return (correct, total) return (correct, total)
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