Commit 581a3512 by Bridger Maxwell

Simplified the parameters for AssignmentFormatGrader

parent 4921ea29
......@@ -19,15 +19,50 @@ SectionPercentage = namedtuple("SectionPercentage", "percentage label summary")
class CourseGrader:
def grade(self, grade_sheet):
raise NotImplementedError
class WeightedSubsectionsGrader(CourseGrader):
"""
This grader takes a list of tuples containing (grader, category_name, weight) and computes
a final grade by totalling the contribution of each sub grader and weighting it
accordingly. For example, the sections may be
[ (homeworkGrader, "Homework", 0.15), (labGrader, "Labs", 0.15), (midtermGrader, "Midterm", 0.30), (finalGrader, "Final", 0.40) ]
All items in section_breakdown for each subgrader will be combined. A grade_breakdown will be
composed using the score from each grader.
"""
def __init__(self, sections):
self.sections = sections
def grade(self, grade_sheet):
total_percent = 0.0
section_breakdown = []
grade_breakdown = []
for subgrader, section_name, weight in self.sections:
subgrade_result = subgrader.grade(grade_sheet)
weightedPercent = subgrade_result['percent'] * weight
section_detail = "{0} = {1:.1%} of a possible {2:.0%}".format(section_name, weightedPercent, weight)
total_percent += weightedPercent
section_breakdown += subgrade_result['section_breakdown']
grade_breakdown.append( {'percent' : weightedPercent, 'detail' : section_detail, 'category' : section_name} )
return {'percent' : total_percent,
'section_breakdown' : section_breakdown,
'grade_breakdown' : grade_breakdown}
class SingleSectionGrader(CourseGrader):
"""
This grades a single section with the format section_format and the name section_name.
If the section_name is not appropriate for the short short_label or category, they each may
be specified individually.
"""
def __init__(self, section_format, section_name, label = None, category = None):
def __init__(self, section_format, section_name, short_label = None, category = None):
self.section_format = section_format
self.section_name = section_name
self.label = label or section_name
self.short_label = short_label or section_name
self.category = category or section_name
def grade(self, grade_sheet):
......@@ -50,15 +85,12 @@ class SingleSectionGrader(CourseGrader):
detail = "{name} - 0% (?/?)".format(name = self.section_name)
breakdown = [{'percent': percent, 'label': self.label, 'detail': detail, 'category': self.category, 'prominent': True}]
breakdown = [{'percent': percent, 'label': self.short_label, 'detail': detail, 'category': self.category, 'prominent': True}]
return {'percent' : percent,
'section_breakdown' : breakdown,
#No grade_breakdown here
}
class AssignmentFormatGrader(CourseGrader):
"""
......@@ -70,38 +102,20 @@ class AssignmentFormatGrader(CourseGrader):
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).
section_detail_formatter is a format string with the parameters (index, name, percent, earned, possible).
ex: "Homework {index} - {name} - {percent:.0%} ({earned:g}/{possible:g})"
section_missing_detail_formatter is a format string with the parameters (index) for
when the minimum number of sections weren't found in the course.
ex: "Unreleased Homework {index} - 0% (?/?)"
section_label_formatter is a format string for a short label with the parameters (index).
These look best when fixed-length.
ex: "HW {index:02d}"
total_detail_formatter is a format string for displaying the average score with the
parameters (percent).
ex: "Homework Average = {percent:.0%}"
section_type is a string that is the type of a singular section. For example, for Labs it
would be "Lab". This defaults to be the same as category.
total_label_formatter is a string (with no parameters).
ex: "HW Avg"
short_label is similar to section_type, but shorter. For example, for Homework it would be
"HW".
"""
def __init__(self, course_format, min_number, drop_count, category, section_detail_formatter, section_missing_detail_formatter,
section_label_formatter, total_detail_formatter, total_label_formatter):
def __init__(self, course_format, min_number, drop_count, category = None, section_type = None, short_label = None):
self.course_format = course_format
self.min_number = min_number
self.drop_count = drop_count
self.category = category
self.section_detail_formatter = section_detail_formatter
self.section_missing_detail_formatter = section_missing_detail_formatter
self.section_label_formatter = section_label_formatter
self.total_detail_formatter = total_detail_formatter
self.total_label_formatter = total_label_formatter
self.category = category or course_format
self.section_type = section_type or course_format
self.short_label = short_label or section_type
def grade(self, grade_sheet):
def totalWithDrops(breakdown, drop_count):
......@@ -124,74 +138,46 @@ class AssignmentFormatGrader(CourseGrader):
for i in range(12):
if i < len(scores):
percentage = scores[i].earned / float(scores[i].possible)
summary = self.section_detail_formatter.format(index = i+1,
summary = "{section_type} {index} - {name} - {percent:.0%} ({earned:g}/{possible:g})".format(index = i+1,
section_type = self.section_type,
name = scores[i].section,
percent = percentage,
earned = scores[i].earned,
possible = scores[i].possible )
else:
percentage = 0
summary = self.section_missing_detail_formatter.format(index = i+1)
summary = "{section_type} {index} Unreleased - 0% (?/?)".format(index = i+1, section_type = self.section_type)
if settings.GENERATE_PROFILE_SCORES:
points_possible = random.randrange(10, 50)
points_earned = random.randrange(5, points_possible)
percentage = points_earned / float(points_possible)
summary = self.section_detail_formatter.format(index = i+1,
summary = "{section_type} {index} - {name} - {percent:.0%} ({earned:g}/{possible:g})".format(index = i+1,
section_type = self.section_type,
name = "Randomly Generated",
percent = percentage,
earned = points_earned,
possible = points_possible )
label = self.section_label_formatter.format(index = i+1)
short_label = "{short_label} {index:02d}".format(index = i+1, short_label = self.short_label)
breakdown.append( {'percent': percentage, 'label': label, 'detail': summary, 'category': self.category} )
breakdown.append( {'percent': percentage, 'label': short_label, 'detail': summary, 'category': self.category} )
total_percent, dropped_indices = totalWithDrops(breakdown, self.drop_count)
for dropped_index in dropped_indices:
breakdown[dropped_index]['mark'] = {'detail': "The lowest {0} scores are dropped.".format(self.drop_count) }
breakdown[dropped_index]['mark'] = {'detail': "The lowest {drop_count} {section_type} scores are dropped.".format(drop_count = self.drop_count, section_type=self.section_type) }
total_detail = self.total_detail_formatter.format(percent = total_percent)
breakdown.append( {'percent': total_percent, 'label': self.total_label_formatter, 'detail': total_detail, 'category': self.category, 'prominent': True} )
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)
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
}
class WeightedSubsectionsGrader(CourseGrader):
"""
This grader takes a list of tuples containing (grader, section_name, weight) and computes
a final grade by totalling the contribution of each sub grader and weighting it
accordingly. For example, the sections may be
[ (homeworkGrader, "Homework", 0.15), (labGrader, "Labs", 0.15), (midtermGrader, "Midterm", 0.30), (finalGrader, "Final", 0.40) ]
"""
def __init__(self, sections):
self.sections = sections
def grade(self, grade_sheet):
total_percent = 0.0
section_breakdown = []
grade_breakdown = []
for subgrader, section_name, weight in self.sections:
subgrade_result = subgrader.grade(grade_sheet)
weightedPercent = subgrade_result['percent'] * weight
section_detail = "{0} = {1:.1%} of a possible {2:.0%}".format(section_name, weightedPercent, weight)
total_percent += weightedPercent
section_breakdown += subgrade_result['section_breakdown']
grade_breakdown.append( {'percent' : weightedPercent, 'detail' : section_detail, 'category' : section_name} )
return {'percent' : total_percent,
'section_breakdown' : section_breakdown,
'grade_breakdown' : grade_breakdown}
def get_score(user, problem, cache):
## HACK: assumes max score is fixed per problem
......@@ -316,14 +302,9 @@ def grade_sheet(student):
'sections' : sections,})
hwGrader = AssignmentFormatGrader("Homework", 12, 2, "Homework", "Homework {index} - {name} - {percent:.0%} ({earned:g}/{possible:g})",
"Unreleased Homework {index} - 0% (?/?)", "HW {index:02d}", "Homework Average = {percent:.0%}", "HW Avg")
labGrader = AssignmentFormatGrader("Lab", 12, 2, "Labs", "Lab {index} - {name} - {percent:.0%} ({earned:g}/{possible:g})",
"Unreleased Lab {index} - 0% (?/?)", "Lab {index:02d}", "Lab Average = {percent:.0%}", "Lab Avg")
hwGrader = AssignmentFormatGrader("Homework", 12, 2, "Homework", "Homework", "HW")
labGrader = AssignmentFormatGrader("Lab", 12, 2, "Labs", "Lab", "Lab")
midtermGrader = SingleSectionGrader("Examination", "Midterm Exam", "Midterm")
finalGrader = SingleSectionGrader("Examination", "Final Exam", "Final")
grader = WeightedSubsectionsGrader( [(hwGrader, hwGrader.category, 0.15), (labGrader, labGrader.category, 0.15),
......@@ -334,122 +315,3 @@ def grade_sheet(student):
return {'courseware_summary' : chapters,
'grade_summary' : grade_summary}
def grade_summary_6002x(totaled_scores):
"""
This function takes the a dictionary of (graded) section scores, and applies the course grading rules to create
the grade_summary. For 6.002x this means homeworks and labs all have equal weight, with the lowest 2 of each
being dropped. There is one midterm and one final.
"""
def totalWithDrops(scores, drop_count):
#Note that this key will sort the list descending
sorted_scores = sorted( enumerate(scores), key=lambda x: -x[1].percentage )
# A list of the indices of the dropped scores
dropped_indices = [score[0] for score in sorted_scores[-drop_count:]]
aggregate_score = 0
for index, score in enumerate(scores):
if index not in dropped_indices:
aggregate_score += score.percentage
aggregate_score /= len(scores) - drop_count
return aggregate_score, dropped_indices
#Figure the homework scores
homework_scores = totaled_scores['Homework'] if 'Homework' in totaled_scores else []
homework_percentages = []
for i in range(12):
if i < len(homework_scores):
percentage = homework_scores[i].earned / float(homework_scores[i].possible)
summary = "Homework {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, homework_scores[i].section , percentage, homework_scores[i].earned, homework_scores[i].possible )
else:
percentage = 0
summary = "Unreleased Homework {0} - 0% (?/?)".format(i + 1)
if settings.GENERATE_PROFILE_SCORES:
points_possible = random.randrange(10, 50)
points_earned = random.randrange(5, points_possible)
percentage = points_earned / float(points_possible)
summary = "Random Homework - {0:.0%} ({1:g}/{2:g})".format( percentage, points_earned, points_possible )
label = "HW {0:02d}".format(i + 1)
homework_percentages.append(SectionPercentage(percentage, label, summary) )
homework_total, homework_dropped_indices = totalWithDrops(homework_percentages, 2)
#Figure the lab scores
lab_scores = totaled_scores['Lab'] if 'Lab' in totaled_scores else []
lab_percentages = []
for i in range(12):
if i < len(lab_scores):
percentage = lab_scores[i].earned / float(lab_scores[i].possible)
summary = "Lab {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, lab_scores[i].section , percentage, lab_scores[i].earned, lab_scores[i].possible )
else:
percentage = 0
summary = "Unreleased Lab {0} - 0% (?/?)".format(i + 1)
if settings.GENERATE_PROFILE_SCORES:
points_possible = random.randrange(10, 50)
points_earned = random.randrange(5, points_possible)
percentage = points_earned / float(points_possible)
summary = "Random Lab - {0:.0%} ({1:g}/{2:g})".format( percentage, points_earned, points_possible )
label = "Lab {0:02d}".format(i + 1)
lab_percentages.append(SectionPercentage(percentage, label, summary) )
lab_total, lab_dropped_indices = totalWithDrops(lab_percentages, 2)
#TODO: Pull this data about the midterm and final from the databse. It should be exactly similar to above, but we aren't sure how exams will be done yet.
midterm_score = Score('?', '?', True, "?")
midterm_percentage = 0
final_score = Score('?', '?', True, "?")
final_percentage = 0
if settings.GENERATE_PROFILE_SCORES:
midterm_score = Score(random.randrange(50, 150), 150, True, "?")
midterm_percentage = midterm_score.earned / float(midterm_score.possible)
final_score = Score(random.randrange(100, 300), 300, True, "?")
final_percentage = final_score.earned / float(final_score.possible)
grade_summary = [
{
'category': 'Homework',
'subscores' : homework_percentages,
'dropped_indices' : homework_dropped_indices,
'totalscore' : homework_total,
'totalscore_summary' : "Homework Average - {0:.0%}".format(homework_total),
'totallabel' : 'HW Avg',
'weight' : 0.15,
},
{
'category': 'Labs',
'subscores' : lab_percentages,
'dropped_indices' : lab_dropped_indices,
'totalscore' : lab_total,
'totalscore_summary' : "Lab Average - {0:.0%}".format(lab_total),
'totallabel' : 'Lab Avg',
'weight' : 0.15,
},
{
'category': 'Midterm',
'totalscore' : midterm_percentage,
'totalscore_summary' : "Midterm - {0:.0%} ({1}/{2})".format(midterm_percentage, midterm_score.earned, midterm_score.possible),
'totallabel' : 'Midterm',
'weight' : 0.30,
},
{
'category': 'Final',
'totalscore' : final_percentage,
'totalscore_summary' : "Final - {0:.0%} ({1}/{2})".format(final_percentage, final_score.earned, final_score.possible),
'totallabel' : 'Final',
'weight' : 0.40,
}
]
return grade_summary
......@@ -25,7 +25,7 @@ $(function () {
categories = {}
tickIndex = 1
sectionSpacer = 0.5
sectionSpacer = 0.25
sectionIndex = 0
ticks = [] #These are the indices and x-axis labels for the data
......
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