Commit af6dd84a by Victor Shnayder

Merge pull request #789 from MITx/feature/bridger/course_grading

Feature/bridger/course grading
parents 9c859fd9 4ee0e258
import abc
import json
import logging
import random
import sys
from collections import namedtuple
......@@ -184,7 +185,7 @@ class CourseGrader(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def grade(self, grade_sheet):
def grade(self, grade_sheet, generate_random_scores=False):
raise NotImplementedError
......@@ -204,13 +205,13 @@ class WeightedSubsectionsGrader(CourseGrader):
def __init__(self, sections):
self.sections = sections
def grade(self, grade_sheet):
def grade(self, grade_sheet, generate_random_scores=False):
total_percent = 0.0
section_breakdown = []
grade_breakdown = []
for subgrader, category, weight in self.sections:
subgrade_result = subgrader.grade(grade_sheet)
subgrade_result = subgrader.grade(grade_sheet, generate_random_scores)
weightedPercent = subgrade_result['percent'] * weight
section_detail = "{0} = {1:.1%} of a possible {2:.0%}".format(category, weightedPercent, weight)
......@@ -237,7 +238,7 @@ class SingleSectionGrader(CourseGrader):
self.short_label = short_label or name
self.category = category or name
def grade(self, grade_sheet):
def grade(self, grade_sheet, generate_random_scores=False):
foundScore = None
if self.type in grade_sheet:
for score in grade_sheet[self.type]:
......@@ -245,12 +246,19 @@ class SingleSectionGrader(CourseGrader):
foundScore = score
break
if foundScore:
percent = foundScore.earned / float(foundScore.possible)
if foundScore or generate_random_scores:
if generate_random_scores: # for debugging!
earned = random.randint(2,15)
possible = random.randint(earned, 15)
else: # We found the score
earned = foundScore.earned
possible = foundScore.possible
percent = earned / float(possible)
detail = "{name} - {percent:.0%} ({earned:.3n}/{possible:.3n})".format(name=self.name,
percent=percent,
earned=float(foundScore.earned),
possible=float(foundScore.possible))
earned=float(earned),
possible=float(possible))
else:
percent = 0.0
......@@ -274,6 +282,9 @@ class AssignmentFormatGrader(CourseGrader):
min_count defines how many assignments are expected throughout the course. Placeholder
scores (of 0) will be inserted if the number of matching sections in the course is < min_count.
If there number of matching sections in the course is > min_count, min_count will be ignored.
show_only_average is to suppress the display of each assignment in this grader and instead
only show the total score of this grader in the breakdown.
category should be presentable to the user, but may not appear. When the grade breakdown is
displayed, scores from the same category will be similar (for example, by color).
......@@ -285,15 +296,16 @@ class AssignmentFormatGrader(CourseGrader):
"HW".
"""
def __init__(self, type, min_count, drop_count, category=None, section_type=None, short_label=None):
def __init__(self, type, min_count, drop_count, category=None, section_type=None, short_label=None, show_only_average=False):
self.type = type
self.min_count = min_count
self.drop_count = drop_count
self.category = category or self.type
self.section_type = section_type or self.type
self.short_label = short_label or self.type
self.show_only_average = show_only_average
def grade(self, grade_sheet):
def grade(self, grade_sheet, generate_random_scores=False):
def totalWithDrops(breakdown, drop_count):
#create an array of tuples with (index, mark), sorted by mark['percent'] descending
sorted_breakdown = sorted(enumerate(breakdown), key=lambda x: -x[1]['percent'])
......@@ -315,20 +327,30 @@ class AssignmentFormatGrader(CourseGrader):
scores = grade_sheet.get(self.type, [])
breakdown = []
for i in range(max(self.min_count, len(scores))):
if i < len(scores):
percentage = scores[i].earned / float(scores[i].possible)
if i < len(scores) or generate_random_scores:
if generate_random_scores: # for debugging!
earned = random.randint(2,15)
possible = random.randint(earned, 15)
section_name = "Generated"
else:
earned = scores[i].earned
possible = scores[i].possible
section_name = scores[i].section
percentage = earned / float(possible)
summary = "{section_type} {index} - {name} - {percent:.0%} ({earned:.3n}/{possible:.3n})".format(index=i + 1,
section_type=self.section_type,
name=scores[i].section,
name=section_name,
percent=percentage,
earned=float(scores[i].earned),
possible=float(scores[i].possible))
earned=float(earned),
possible=float(possible))
else:
percentage = 0
summary = "{section_type} {index} Unreleased - 0% (?/?)".format(index=i + 1, section_type=self.section_type)
short_label = "{short_label} {index:02d}".format(index=i + 1, short_label=self.short_label)
breakdown.append({'percent': percentage, 'label': short_label, 'detail': summary, 'category': self.category})
total_percent, dropped_indices = totalWithDrops(breakdown, self.drop_count)
......@@ -338,8 +360,12 @@ class AssignmentFormatGrader(CourseGrader):
total_detail = "{section_type} Average = {percent:.0%}".format(percent=total_percent, section_type=self.section_type)
total_label = "{short_label} Avg".format(short_label=self.short_label)
if self.show_only_average:
breakdown = []
breakdown.append({'percent': total_percent, 'label': total_label, 'detail': total_detail, 'category': self.category, 'prominent': True})
return {'percent': total_percent,
'section_breakdown': breakdown,
#No grade_breakdown here
......
......@@ -201,7 +201,7 @@ def grade(student, request, course, student_module_cache=None, keep_raw_scores=F
totaled_scores[section_format] = format_scores
grade_summary = course.grader.grade(totaled_scores)
grade_summary = course.grader.grade(totaled_scores, generate_random_scores=settings.GENERATE_PROFILE_SCORES)
# 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
......
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