From 718475662f2c6dd73b64855d88d6106931e79060 Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Tue, 10 Apr 2012 10:34:51 -0400 Subject: [PATCH 1/7] added weighting system for the midterm --- djangoapps/courseware/grades.py | 58 ++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/djangoapps/courseware/grades.py b/djangoapps/courseware/grades.py index c7e67143b0..edbc54f0ec 100644 --- a/djangoapps/courseware/grades.py +++ b/djangoapps/courseware/grades.py @@ -12,7 +12,7 @@ from student.models import UserProfile log = logging.getLogger("mitx.courseware") -Score = namedtuple("Score", "earned possible graded section") +Score = namedtuple("Score", "earned possible weight graded section") def get_grade(user, problem, cache): ## HACK: assumes max score is fixed per problem @@ -83,6 +83,7 @@ def grade_sheet(student): graded = True if s.get('graded') == "true" else False scores=[] + weighted=False if len(problems)>0: for p in problems: (correct,total) = get_grade(student, p, response_by_id) @@ -100,20 +101,33 @@ def grade_sheet(student): correct = random.randrange( max(total-2, 1) , total + 1 ) else: correct = total - - scores.append( Score(int(correct),total, graded, s.get("name")) ) - - - section_total = Score(sum([score.earned for score in scores]), - sum([score.possible for score in scores]), - False, - p.get("id")) - - graded_total = Score(sum([score.earned for score in scores if score.graded]), - sum([score.possible for score in scores if score.graded]), - True, - p.get("id")) - + if p.get("weight"): + weighted=True + scores.append( Score(int(correct),total, p.get("weight", 1), graded, p.get("name")) ) + if weighted: + total_correct_graded = sum([(score.earned*1.0/score.possible)*int(score.weight) for score in scores if score.graded]) + total_possible_graded = sum([int(score.weight) for score in scores if score.graded]) + total_correct = sum([(score.earned*1.0/score.possible)*int(score.weight) for score in scores]) + total_possible = sum([int(score.weight) for score in scores]) + section_weight = s.get("weight", 1) + else: + total_correct_graded=sum([score.earned for score in scores if score.graded]) + total_possible_graded=sum([score.possible for score in scores if score.graded]) + total_correct = sum([score.earned for score in scores]) + total_possible = sum([score.possible for score in scores]) + section_weight = None + #regardless of whether or not it is graded + section_total = Score(total_correct, + total_possible, + section_weight, + False, + p.get("id")) + #selecting only graded things + graded_total = Score(total_correct_graded, + total_possible_graded, + section_weight, + True, + p.get("id")) #Add the graded total to totaled_scores format = s.get('format') if s.get('format') else "" subtitle = s.get('subtitle') if s.get('subtitle') else format @@ -136,11 +150,10 @@ def grade_sheet(student): 'chapter' : c.get("name"), 'sections' : sections,}) - grade_summary = grade_summary_6002x(totaled_scores) - - return {'courseware_summary' : chapters, - 'grade_summary' : grade_summary} + return {'courseware_summary' : chapters, #all assessments as they appear in the course definition + 'grade_summary' : grade_summary, #graded assessments only + } def grade_summary_6002x(totaled_scores): @@ -210,10 +223,11 @@ def grade_summary_6002x(totaled_scores): #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 + #This is a hack, but I have no intention of having this function be useful for anything but 6.002x anyway, so I don't want to make it pretty. + midterm_score = totaled_scores['Midterm'][0] if 'Midterm' in totaled_scores else Score('?', '?', '?', True, "?") + midterm_percentage = midterm_score.earned * 1.0 / midterm_score.possible if 'Midterm' in totaled_scores else 0 - final_score = Score('?', '?', True, "?") + final_score = Score('?', '?', '?', True, "?") final_percentage = 0 if settings.GENERATE_PROFILE_SCORES: From 79a0a9bf7d6ddd482529f51871fbe9cd90467b41 Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Wed, 11 Apr 2012 10:09:42 -0400 Subject: [PATCH 2/7] include the final once it exists --- djangoapps/courseware/grades.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/djangoapps/courseware/grades.py b/djangoapps/courseware/grades.py index edbc54f0ec..6ff0dc08ea 100644 --- a/djangoapps/courseware/grades.py +++ b/djangoapps/courseware/grades.py @@ -227,8 +227,8 @@ def grade_summary_6002x(totaled_scores): midterm_score = totaled_scores['Midterm'][0] if 'Midterm' in totaled_scores else Score('?', '?', '?', True, "?") midterm_percentage = midterm_score.earned * 1.0 / midterm_score.possible if 'Midterm' in totaled_scores else 0 - final_score = Score('?', '?', '?', True, "?") - final_percentage = 0 + final_score = totaled_scores['Final'][0] if 'Final' in totaled_scores else Score('?', '?', '?', True, "?") + final_percentage = midterm_score.earned * 1.0 / midterm_score.possible if 'Midterm' in totaled_scores else 0 if settings.GENERATE_PROFILE_SCORES: midterm_score = Score(random.randrange(50, 150), 150, True, "?") From a49a2fc600273be00254d483956ca83017dfc12e Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Wed, 11 Apr 2012 10:27:34 -0400 Subject: [PATCH 3/7] oops - final/midterm --- djangoapps/courseware/grades.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangoapps/courseware/grades.py b/djangoapps/courseware/grades.py index 6ff0dc08ea..3722495555 100644 --- a/djangoapps/courseware/grades.py +++ b/djangoapps/courseware/grades.py @@ -228,7 +228,7 @@ def grade_summary_6002x(totaled_scores): midterm_percentage = midterm_score.earned * 1.0 / midterm_score.possible if 'Midterm' in totaled_scores else 0 final_score = totaled_scores['Final'][0] if 'Final' in totaled_scores else Score('?', '?', '?', True, "?") - final_percentage = midterm_score.earned * 1.0 / midterm_score.possible if 'Midterm' in totaled_scores else 0 + final_percentage = midterm_score.earned * 1.0 / midterm_score.possible if 'Final' in totaled_scores else 0 if settings.GENERATE_PROFILE_SCORES: midterm_score = Score(random.randrange(50, 150), 150, True, "?") From bfff82228756e053380375ac329b0d9fb15caa74 Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Wed, 11 Apr 2012 10:28:55 -0400 Subject: [PATCH 4/7] AGH! midterm/final --- djangoapps/courseware/grades.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangoapps/courseware/grades.py b/djangoapps/courseware/grades.py index 3722495555..910bb49871 100644 --- a/djangoapps/courseware/grades.py +++ b/djangoapps/courseware/grades.py @@ -228,7 +228,7 @@ def grade_summary_6002x(totaled_scores): midterm_percentage = midterm_score.earned * 1.0 / midterm_score.possible if 'Midterm' in totaled_scores else 0 final_score = totaled_scores['Final'][0] if 'Final' in totaled_scores else Score('?', '?', '?', True, "?") - final_percentage = midterm_score.earned * 1.0 / midterm_score.possible if 'Final' in totaled_scores else 0 + final_percentage = final_score.earned * 1.0 / final_score.possible if 'Final' in totaled_scores else 0 if settings.GENERATE_PROFILE_SCORES: midterm_score = Score(random.randrange(50, 150), 150, True, "?") From e49fd7002ddcc1c27b82548d3e15311fe6a3eceb Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Fri, 13 Apr 2012 14:34:09 -0400 Subject: [PATCH 5/7] respones to code review --- djangoapps/courseware/grades.py | 50 +++++++++----------- djangoapps/courseware/modules/capa_module.py | 7 ++- djangoapps/courseware/tests.py | 33 +++++++++++++ templates/problem.html | 6 ++- 4 files changed, 65 insertions(+), 31 deletions(-) diff --git a/djangoapps/courseware/grades.py b/djangoapps/courseware/grades.py index 910bb49871..144f926c7f 100644 --- a/djangoapps/courseware/grades.py +++ b/djangoapps/courseware/grades.py @@ -83,7 +83,6 @@ def grade_sheet(student): graded = True if s.get('graded') == "true" else False scores=[] - weighted=False if len(problems)>0: for p in problems: (correct,total) = get_grade(student, p, response_by_id) @@ -101,33 +100,9 @@ def grade_sheet(student): correct = random.randrange( max(total-2, 1) , total + 1 ) else: correct = total - if p.get("weight"): - weighted=True - scores.append( Score(int(correct),total, p.get("weight", 1), graded, p.get("name")) ) - if weighted: - total_correct_graded = sum([(score.earned*1.0/score.possible)*int(score.weight) for score in scores if score.graded]) - total_possible_graded = sum([int(score.weight) for score in scores if score.graded]) - total_correct = sum([(score.earned*1.0/score.possible)*int(score.weight) for score in scores]) - total_possible = sum([int(score.weight) for score in scores]) - section_weight = s.get("weight", 1) - else: - total_correct_graded=sum([score.earned for score in scores if score.graded]) - total_possible_graded=sum([score.possible for score in scores if score.graded]) - total_correct = sum([score.earned for score in scores]) - total_possible = sum([score.possible for score in scores]) - section_weight = None - #regardless of whether or not it is graded - section_total = Score(total_correct, - total_possible, - section_weight, - False, - p.get("id")) - #selecting only graded things - graded_total = Score(total_correct_graded, - total_possible_graded, - section_weight, - True, - p.get("id")) + scores.append( Score(int(correct),total, float(p.get("weight", 1)), graded, p.get("name")) ) + + section_total, graded_total = aggregate_scores(scores) #Add the graded total to totaled_scores format = s.get('format') if s.get('format') else "" subtitle = s.get('subtitle') if s.get('subtitle') else format @@ -155,6 +130,25 @@ def grade_sheet(student): 'grade_summary' : grade_summary, #graded assessments only } +def aggregate_scores(scores): + total_correct_graded = sum((score.earned*1.0/score.possible)*score.weight for score in scores if score.graded) + total_possible_graded = sum(score.weight for score in scores if score.graded) + total_correct = sum((score.earned*1.0/score.possible)*score.weight for score in scores) + total_possible = sum(score.weight for score in scores) + #regardless of whether or not it is graded + all_total = Score(total_correct, + total_possible, + 1, + False, + "summary") + #selecting only graded things + graded_total = Score(total_correct_graded, + total_possible_graded, + 1, + True, + "summary") + + return all_total, graded_total def grade_summary_6002x(totaled_scores): """ diff --git a/djangoapps/courseware/modules/capa_module.py b/djangoapps/courseware/modules/capa_module.py index 71f76fa66a..86958dcf1c 100644 --- a/djangoapps/courseware/modules/capa_module.py +++ b/djangoapps/courseware/modules/capa_module.py @@ -71,7 +71,9 @@ class Module(XModule): def get_problem_html(self, encapsulate=True): html = self.lcp.get_html() content={'name':self.name, - 'html':html} + 'html':html, + 'weight': self.weight, + } # We using strings as truthy values, because the terminology of the check button # is context-specific. @@ -136,7 +138,7 @@ class Module(XModule): self.max_attempts = None dom2 = etree.fromstring(xml) - + self.explanation=content_parser.item(dom2.xpath('/problem/@explain'), default="closed") self.explain_available=content_parser.item(dom2.xpath('/problem/@explain_available')) @@ -186,6 +188,7 @@ class Module(XModule): self.filename=content_parser.item(dom2.xpath('/problem/@filename')) filename=settings.DATA_DIR+"/problems/"+self.filename+".xml" self.name=content_parser.item(dom2.xpath('/problem/@name')) + self.weight=content_parser.item(dom2.xpath('/problem/@weight')) if self.rerandomize == 'never': seed = 1 else: diff --git a/djangoapps/courseware/tests.py b/djangoapps/courseware/tests.py index 2b2b354177..9cf19b03b9 100644 --- a/djangoapps/courseware/tests.py +++ b/djangoapps/courseware/tests.py @@ -4,6 +4,7 @@ import numpy import courseware.modules import courseware.capa.calc as calc +from grades import Score, aggregate_scores class ModelsTest(unittest.TestCase): def setUp(self): @@ -53,3 +54,35 @@ class ModelsTest(unittest.TestCase): exception_happened = True self.assertTrue(exception_happened) +class GraderTest(unittest.TestCase): + + def test_weighted_grading(self): + scores = [] + + all, graded = aggregate_scores(scores) + self.assertTrue(all.earned == 0) + self.assertTrue(graded.earned == 0) + self.assertTrue(all.possible == 0) + self.assertTrue(graded.possible == 0) + + scores.append(Score(0,5,1,False, 'foo')) + all, graded = aggregate_scores(scores) + self.assertTrue(all.earned == 0) + self.assertTrue(graded.earned == 0) + print all + self.assertTrue(all.possible == 1) + self.assertTrue(graded.possible == 0) + + scores.append(Score(3,5,1,True, 'foo')) + all, graded = aggregate_scores(scores) + self.assertTrue(all.earned == 3.0/5) + self.assertTrue(graded.earned == 3.0/5) + self.assertTrue(all.possible == 2) + self.assertTrue(graded.possible == 1) + + scores.append(Score(2,5,2,True, 'foo')) + all, graded = aggregate_scores(scores) + self.assertTrue(all.earned == 7.0/5) + self.assertTrue(graded.earned == 7.0/5) + self.assertTrue(all.possible == 4) + self.assertTrue(graded.possible == 3) diff --git a/templates/problem.html b/templates/problem.html index f332dda378..cd332f1b74 100644 --- a/templates/problem.html +++ b/templates/problem.html @@ -1,4 +1,8 @@ -

${ problem['name'] }

+

${ problem['name'] } +% if problem['weight']: +: ${ problem['weight'] } points +% endif +

${ problem['html'] } From 287a086cfbb09144fe2fce7b21b80347194566ed Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Fri, 13 Apr 2012 15:27:26 -0400 Subject: [PATCH 6/7] changed tests --- djangoapps/courseware/tests.py | 36 +++++++++++++++------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/djangoapps/courseware/tests.py b/djangoapps/courseware/tests.py index 9cf19b03b9..4eb32dc2b4 100644 --- a/djangoapps/courseware/tests.py +++ b/djangoapps/courseware/tests.py @@ -60,29 +60,25 @@ class GraderTest(unittest.TestCase): scores = [] all, graded = aggregate_scores(scores) - self.assertTrue(all.earned == 0) - self.assertTrue(graded.earned == 0) - self.assertTrue(all.possible == 0) - self.assertTrue(graded.possible == 0) + self.assertEqual(all, Score(earned=0, possible=0, weight=1, graded=False, section="summary")) + self.assertEqual(graded, Score(earned=0, possible=0, weight=1, graded=True, section="summary")) - scores.append(Score(0,5,1,False, 'foo')) + scores.append(Score(earned=0, possible=5, weight=1, graded=False, section="summary")) all, graded = aggregate_scores(scores) - self.assertTrue(all.earned == 0) - self.assertTrue(graded.earned == 0) - print all - self.assertTrue(all.possible == 1) - self.assertTrue(graded.possible == 0) + self.assertEqual(all, Score(earned=0, possible=1, weight=1, graded=False, section="summary")) + self.assertEqual(graded, Score(earned=0, possible=0, weight=1, graded=True, section="summary")) - scores.append(Score(3,5,1,True, 'foo')) + scores.append(Score(earned=3, possible=5, weight=1, graded=True, section="summary")) all, graded = aggregate_scores(scores) - self.assertTrue(all.earned == 3.0/5) - self.assertTrue(graded.earned == 3.0/5) - self.assertTrue(all.possible == 2) - self.assertTrue(graded.possible == 1) + self.assertEqual(all, Score(earned=3.0/5, possible=2, weight=1, graded=False, section="summary")) + self.assertEqual(graded, Score(earned=3.0/5, possible=0, weight=1, graded=True, section="summary")) - scores.append(Score(2,5,2,True, 'foo')) + scores.append(Score(earned=2, possible=5, weight=2, graded=True, section="summary")) all, graded = aggregate_scores(scores) - self.assertTrue(all.earned == 7.0/5) - self.assertTrue(graded.earned == 7.0/5) - self.assertTrue(all.possible == 4) - self.assertTrue(graded.possible == 3) + self.assertEqual(all, Score(earned=7.0/5, possible=4, weight=1, graded=False, section="summary")) + self.assertEqual(graded, Score(earned=7.0/5, possible=3, weight=1, graded=True, section="summary")) + + scores.append(Score(earned=2, possible=5, weight=0, graded=True, section="summary")) + all, graded = aggregate_scores(scores) + self.assertEqual(all, Score(earned=7.0/5, possible=4, weight=1, graded=False, section="summary")) + self.assertEqual(graded, Score(earned=7.0/5, possible=3, weight=1, graded=True, section="summary")) From 7853af54159e619bc8cc852b74a1c22e58b4869b Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Fri, 13 Apr 2012 16:29:12 -0400 Subject: [PATCH 7/7] more tests --- djangoapps/courseware/tests.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/djangoapps/courseware/tests.py b/djangoapps/courseware/tests.py index 4eb32dc2b4..7eb6aa27de 100644 --- a/djangoapps/courseware/tests.py +++ b/djangoapps/courseware/tests.py @@ -58,6 +58,7 @@ class GraderTest(unittest.TestCase): def test_weighted_grading(self): scores = [] + Score.__sub__=lambda me, other: (me.earned - other.earned) + (me.possible - other.possible) all, graded = aggregate_scores(scores) self.assertEqual(all, Score(earned=0, possible=0, weight=1, graded=False, section="summary")) @@ -70,15 +71,25 @@ class GraderTest(unittest.TestCase): scores.append(Score(earned=3, possible=5, weight=1, graded=True, section="summary")) all, graded = aggregate_scores(scores) - self.assertEqual(all, Score(earned=3.0/5, possible=2, weight=1, graded=False, section="summary")) - self.assertEqual(graded, Score(earned=3.0/5, possible=0, weight=1, graded=True, section="summary")) + self.assertAlmostEqual(all, Score(earned=3.0/5, possible=2, weight=1, graded=False, section="summary")) + self.assertAlmostEqual(graded, Score(earned=3.0/5, possible=1, weight=1, graded=True, section="summary")) scores.append(Score(earned=2, possible=5, weight=2, graded=True, section="summary")) all, graded = aggregate_scores(scores) - self.assertEqual(all, Score(earned=7.0/5, possible=4, weight=1, graded=False, section="summary")) - self.assertEqual(graded, Score(earned=7.0/5, possible=3, weight=1, graded=True, section="summary")) + self.assertAlmostEqual(all, Score(earned=7.0/5, possible=4, weight=1, graded=False, section="summary")) + self.assertAlmostEqual(graded, Score(earned=7.0/5, possible=3, weight=1, graded=True, section="summary")) scores.append(Score(earned=2, possible=5, weight=0, graded=True, section="summary")) all, graded = aggregate_scores(scores) - self.assertEqual(all, Score(earned=7.0/5, possible=4, weight=1, graded=False, section="summary")) - self.assertEqual(graded, Score(earned=7.0/5, possible=3, weight=1, graded=True, section="summary")) + self.assertAlmostEqual(all, Score(earned=7.0/5, possible=4, weight=1, graded=False, section="summary")) + self.assertAlmostEqual(graded, Score(earned=7.0/5, possible=3, weight=1, graded=True, section="summary")) + + scores.append(Score(earned=2, possible=5, weight=3, graded=False, section="summary")) + all, graded = aggregate_scores(scores) + self.assertAlmostEqual(all, Score(earned=13.0/5, possible=7, weight=1, graded=False, section="summary")) + self.assertAlmostEqual(graded, Score(earned=7.0/5, possible=3, weight=1, graded=True, section="summary")) + + scores.append(Score(earned=2, possible=5, weight=.5, graded=True, section="summary")) + all, graded = aggregate_scores(scores) + self.assertAlmostEqual(all, Score(earned=14.0/5, possible=7.5, weight=1, graded=False, section="summary")) + self.assertAlmostEqual(graded, Score(earned=8.0/5, possible=3.5, weight=1, graded=True, section="summary"))