def getActualLatePenaltyPercent(record, exam): """combines the constant and daily late penalties into one number""" days_late = record.days_late(grace_period=exam.grace_period) max_possible = compute_penalties( 100.0, 1, 0, record.late, exam.late_penalty, late_days=days_late, daily_late_penalty=exam.daily_late_penalty) return round(100.0 - max_possible, 1)
def test_daily_penalty(self): """Unit test for the discount function """ #Only daily penalty and late penalty self.assertTrue(self.float_compare(compute_penalties(100, 1, 0, False, 0, late_days=0, daily_late_penalty=0), 100)) self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 0, late_days=3, daily_late_penalty=10), 100*.9*.9*.9)) self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 50, late_days=3, daily_late_penalty=10), 100*.5*.9*.9*.9)) self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, False, 50, late_days=3, daily_late_penalty=10), 100)) self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 0, late_days=3, daily_late_penalty=110), 0)) #all penalties self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 15, True, 50, late_days=3, daily_late_penalty=10), 100*.85*.85*.5*.9*.9*.9))
def forwards(self, orm): "Write your forwards methods here." # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..." c = 0 for exam in orm['c2g.Exam'].objects.all(): batch = 50 qset = orm['c2g.ExamRecord'].objects.filter(models.Q(attempt_number__gt=1) | models.Q(late=True), complete=True, exam=exam) count = qset.count() print "Exam Records to search for this exam: %d" % count for i in xrange(0, count, batch): print "Searching %d ExamRecords" % i for er in qset[i:i+batch].select_related('examrecordscore'): # convert complete exams with scores which are either late or are re-attempts try: if er.examrecordscore and isinstance(er.examrecordscore.raw_score, (int, float)): er.score = compute_penalties(er.examrecordscore.raw_score, er.attempt_number, exam.resubmission_penalty, er.late, exam.late_penalty) er.save() c += 1 except orm['c2g.ExamRecordScore'].DoesNotExist: #If there is no ExamRecordScore, there's now raw score and we pass pass print "Exam Records Converted: %d" % c
def handle(self, *args, **options): errors = 0 regrades = 0 updates = 0 if len(args) != 1: raise CommandError("exam id is required") examid = args[0] exam_obj = Exam.objects.get(id__exact=examid) autograder = AutoGrader(exam_obj.xml_metadata) examRecords = ExamRecord.objects \ .select_related('examrecordscore', 'student') \ .filter(exam_id__exact=examid, complete=True) if not parser and (options['start_time'] or options['end_time']): raise CommandError("Can't parse start and end times without having 'dateutil' installed.\nSee http://labix.org/python-dateutil") if options['start_time']: start = parser.parse(options['start_time']) examRecords = examRecords.filter(time_created__gt=start) if options['end_time']: end = parser.parse(options['end_time']) examRecords = examRecords.filter(time_created__lt=end) if options['student_ids']: sidlist = options['student_ids'].split(',') examRecords = examRecords.filter(student__in=sidlist) # search in reverse ID order so that the latest attempts get precedence. If two # attempts have the same score, then want the latest attempt to be the one that # matters. examRecords = examRecords.order_by('-id') # this executes the query if len(examRecords) == 0: print "warning: no exam records found, is that what you intended?" return count = 1 for er in examRecords: ers_created = False ers = er.examrecordscore if ers is None: ers = ExamRecordScore(record=er, raw_score=0.0) ers_id_string = "new" ers_created = True else: ers_id_string = str(ers.id) print "ExamRecord %d, %d of %d".encode('ascii','ignore') % (er.id, count, len(examRecords)) count += 1 try: score_before = er.score rawscore_before = ers.raw_score if score_before == None: # scores of 0 come back from model as None score_before = 0.0 # not sure why but they do if rawscore_before == None: rawscore_before = 0.0 score_after = 0.0 rawscore_after = 0.0 submitted = json.loads(er.json_data) regrade = {} for prob, v in submitted.iteritems(): if isinstance(v,list): # multiple choice case student_input = map(lambda li: li['value'], v) regrade[prob] = autograder.grade(prob, student_input) else: # single answer case student_input = v['value'] regrade[prob] = autograder.grade(prob, student_input) if 'feedback' in regrade[prob]: del regrade[prob]['feedback'] # remove giant feedback field if 'score' in regrade[prob]: rawscore_after += float(regrade[prob]['score']) is_late = er.time_created > exam_obj.grace_period if er.attempt_number == 0: print "ERROR: examrecord %d: skip, attempt_number=0".encode('ascii','ignore') \ % er.id errors += 1 continue if options['penalties']: days_late = er.days_late(grace_period=exam_obj.grace_period) score_after = compute_penalties(rawscore_after, er.attempt_number, exam_obj.resubmission_penalty, is_late, exam_obj.late_penalty, late_days=days_late, daily_late_penalty=exam_obj.daily_late_penalty) else: score_after = rawscore_after s = er.student try: es = ExamScore.objects.get(exam=exam_obj, student=s) es_id_string = str(es.id) examscore_before = es.score except ExamScore.DoesNotExist: es = ExamScore(course=er.course, exam=exam_obj, student=s) es_id_string = "new" examscore_before = -1 examscore_after = max(examscore_before, score_after) #raw = raw score, score = with penalties, agg = exam_score, over all attempts status_line = u"\"%s\", \"%s\", %s, %s, %s, " \ % (s.first_name, s.last_name, s.username, s.email, er.time_created) status_line += u"raw[%s]:%0.1f->%0.1f " \ % (ers_id_string, rawscore_before, rawscore_after) status_line += u"score[%d]:%0.1f->%0.1f " \ % (er.id, score_before, score_after) status_line += u"agg[%s]:%0.1f->%0.1f " \ % (es_id_string, examscore_before, examscore_after) status_line += u"late:%d->%d" \ % (er.late, is_late) if score_before == score_after and rawscore_before == rawscore_after \ and examscore_before == examscore_after and is_late == er.late : print "OK: " + status_line.encode('ascii','ignore') continue regrades += 1 print "REGRADE: " + status_line.encode('ascii','ignore') if not options['dryrun']: if score_before != score_after or is_late != er.late: er.json_score_data = json.dumps(regrade) er.score = score_after er.late = is_late er.save() updates += 1 if ers_created or rawscore_before != rawscore_after: ers.raw_score = rawscore_after ers.save() updates += 1 if examscore_before != examscore_after: es.score = examscore_after es.examrecordscore = ers es.save() updates += 1 # exception handler around big ExamRecords loop -- trust me, it lines up # this just counts and skips offending rows so we can keep making progress except Exception as e: print u"ERROR: examrecord %d: cannot regrade: %s".encode('ascii','ignore') \ % (er.id, unicode(e)) errors += 1 continue print print "## SUMMARY ##" print "# Errors: %d" % errors print "# Regrades: %d" % regrades print "# Database rows updated: %d" % updates
def test_resubmission_and_late_penalty(self): """Unit test for the discount function """ def float_compare(a, b, tolerance=0.001): print "(%f, %f)" % (a,b) return b * (1-tolerance) <= a and a <= b * (1+tolerance) #Only resub penalty self.assertTrue(float_compare(compute_penalties(100, 1, 0, False, 0), 100)) self.assertTrue(float_compare(compute_penalties(100.0, 1, 0, False, 0), 100.0)) self.assertTrue(float_compare(compute_penalties(100.0, 2, 15, False, 0), 85.0)) self.assertTrue(float_compare(compute_penalties(100.0, 3, 15, False, 0), 72.25)) self.assertTrue(float_compare(compute_penalties(100.0, 3, 150, False, 0), 0)) #Only late penalty self.assertTrue(float_compare(compute_penalties(100.0, 1, 0, True, 50), 50.0)) self.assertTrue(float_compare(compute_penalties(100.0, 1, 0, False, 50), 100.0)) self.assertTrue(float_compare(compute_penalties(100.0, 1, 0, True, 150), 0)) self.assertTrue(float_compare(compute_penalties(100.0, 1, 0, False, 150), 100.0)) #Both penalties self.assertTrue(float_compare(compute_penalties(100.0, 2, 15, True, 50), 42.5)) self.assertTrue(float_compare(compute_penalties(100.0, 3, 15, True, 50), 36.125)) self.assertTrue(float_compare(compute_penalties(100.0, 3, 15, True, 150), 0)) self.assertTrue(float_compare(compute_penalties(100.0, 3, 150, True, 50), 0))
def test_resubmission_and_late_penalty(self): """Unit test for the discount function """ #Only resub penalty self.assertTrue(self.float_compare(compute_penalties(100, 1, 0, False, 0), 100)) self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, False, 0), 100.0)) self.assertTrue(self.float_compare(compute_penalties(100.0, 2, 15, False, 0), 85.0)) self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 15, False, 0), 72.25)) self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 150, False, 0), 0)) #Only late penalty self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 50), 50.0)) self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, False, 50), 100.0)) self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 150), 0)) self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, False, 150), 100.0)) #Both penalties self.assertTrue(self.float_compare(compute_penalties(100.0, 2, 15, True, 50), 42.5)) self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 15, True, 50), 36.125)) self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 15, True, 150), 0)) self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 150, True, 50), 0))
def getActualLatePenaltyPercent(record, exam): """combines the constant and daily late penalties into one number""" days_late = record.days_late(grace_period=exam.grace_period) max_possible = compute_penalties(100.0, 1, 0, record.late, exam.late_penalty, late_days=days_late, daily_late_penalty=exam.daily_late_penalty) return round(100.0 - max_possible, 1)
def handle(self, *args, **options): errors = 0 regrades = 0 updates = 0 if len(args) != 1: raise CommandError("exam id is required") examid = args[0] exam_obj = Exam.objects.get(id__exact=examid) autograder = AutoGrader(exam_obj.xml_metadata) examRecords = ExamRecord.objects \ .select_related('examrecordscore', 'student') \ .filter(exam_id__exact=examid, complete=True) if not parser and (options['start_time'] or options['end_time']): raise CommandError( "Can't parse start and end times without having 'dateutil' installed.\nSee http://labix.org/python-dateutil" ) if options['start_time']: start = parser.parse(options['start_time']) examRecords = examRecords.filter(time_created__gt=start) if options['end_time']: end = parser.parse(options['end_time']) examRecords = examRecords.filter(time_created__lt=end) if options['student_ids']: sidlist = options['student_ids'].split(',') examRecords = examRecords.filter(student__in=sidlist) # search in reverse ID order so that the latest attempts get precedence. If two # attempts have the same score, then want the latest attempt to be the one that # matters. examRecords = examRecords.order_by('-id') # this executes the query if len(examRecords) == 0: print "warning: no exam records found, is that what you intended?" return count = 1 for er in examRecords: ers_created = False ers = er.examrecordscore if ers is None: ers = ExamRecordScore(record=er, raw_score=0.0) ers_id_string = "new" ers_created = True else: ers_id_string = str(ers.id) print "ExamRecord %d, %d of %d".encode( 'ascii', 'ignore') % (er.id, count, len(examRecords)) count += 1 try: score_before = er.score rawscore_before = ers.raw_score if score_before == None: # scores of 0 come back from model as None score_before = 0.0 # not sure why but they do if rawscore_before == None: rawscore_before = 0.0 score_after = 0.0 rawscore_after = 0.0 submitted = json.loads(er.json_data) regrade = {} for prob, v in submitted.iteritems(): if isinstance(v, list): # multiple choice case student_input = map(lambda li: li['value'], v) regrade[prob] = autograder.grade(prob, student_input) else: # single answer case student_input = v['value'] regrade[prob] = autograder.grade(prob, student_input) if 'feedback' in regrade[prob]: del regrade[prob][ 'feedback'] # remove giant feedback field if 'score' in regrade[prob]: rawscore_after += float(regrade[prob]['score']) is_late = er.time_created > exam_obj.grace_period if er.attempt_number == 0: print "ERROR: examrecord %d: skip, attempt_number=0".encode('ascii','ignore') \ % er.id errors += 1 continue if options['penalties']: days_late = er.days_late( grace_period=exam_obj.grace_period) score_after = compute_penalties( rawscore_after, er.attempt_number, exam_obj.resubmission_penalty, is_late, exam_obj.late_penalty, late_days=days_late, daily_late_penalty=exam_obj.daily_late_penalty) else: score_after = rawscore_after s = er.student try: es = ExamScore.objects.get(exam=exam_obj, student=s) es_id_string = str(es.id) examscore_before = es.score except ExamScore.DoesNotExist: es = ExamScore(course=er.course, exam=exam_obj, student=s) es_id_string = "new" examscore_before = -1 examscore_after = max(examscore_before, score_after) #raw = raw score, score = with penalties, agg = exam_score, over all attempts status_line = u"\"%s\", \"%s\", %s, %s, %s, " \ % (s.first_name, s.last_name, s.username, s.email, er.time_created) status_line += u"raw[%s]:%0.1f->%0.1f " \ % (ers_id_string, rawscore_before, rawscore_after) status_line += u"score[%d]:%0.1f->%0.1f " \ % (er.id, score_before, score_after) status_line += u"agg[%s]:%0.1f->%0.1f " \ % (es_id_string, examscore_before, examscore_after) status_line += u"late:%d->%d" \ % (er.late, is_late) if score_before == score_after and rawscore_before == rawscore_after \ and examscore_before == examscore_after and is_late == er.late : print "OK: " + status_line.encode('ascii', 'ignore') continue regrades += 1 print "REGRADE: " + status_line.encode('ascii', 'ignore') if not options['dryrun']: if score_before != score_after or is_late != er.late: er.json_score_data = json.dumps(regrade) er.score = score_after er.late = is_late er.save() updates += 1 if ers_created or rawscore_before != rawscore_after: ers.raw_score = rawscore_after ers.save() updates += 1 if examscore_before != examscore_after: es.score = examscore_after es.examrecordscore = ers es.save() updates += 1 # exception handler around big ExamRecords loop -- trust me, it lines up # this just counts and skips offending rows so we can keep making progress except Exception as e: print u"ERROR: examrecord %d: cannot regrade: %s".encode('ascii','ignore') \ % (er.id, unicode(e)) errors += 1 continue print print "## SUMMARY ##" print "# Errors: %d" % errors print "# Regrades: %d" % regrades print "# Database rows updated: %d" % updates
def handle(self, *args, **options): errors = 0 regrades = 0 updates = 0 if len(args) != 1: raise CommandError("exam id is required") examid = args[0] exam_obj = Exam.objects.get(id__exact=examid) autograder = AutoGrader(exam_obj.xml_metadata) examRecords = ExamRecord.objects \ .select_related('examrecordscore', 'student') \ .filter(exam_id__exact=examid, complete=True) if options['start_time']: start = parser.parse(options['start_time']) examRecords = examRecords.filter(time_created__gt=start) if options['end_time']: end = parser.parse(options['end_time']) examRecords = examRecords.filter(time_created__lt=end) if options['student_ids']: sidlist = string.split(options['student_ids'], ',') examRecords = examRecords.filter(student__in=sidlist) # this executes the query if len(examRecords) == 0: print "warning: no exam records found, is that what you intended?" return count = 1 for er in examRecords: ers_created = False ers = er.examrecordscore if ers is None: ers = ExamRecordScore(record=er, raw_score=0.0) ers_created = True print "ExamRecord %d, %d of %d" % (er.id, count, len(examRecords)) count += 1 try: score_before = er.score rawscore_before = ers.raw_score if score_before == None: # scores of 0 come back from model as None score_before = 0.0 # not sure why but they do if rawscore_before == None: # scores of 0 come back from model as None rawscore_before = 0.0 # not sure why but they do score_after = 0.0 rawscore_after = 0.0 submitted = json.loads(er.json_data) regrade = {} for prob, v in submitted.iteritems(): if isinstance(v,list): # multiple choice case student_input = map(lambda li: li['value'], v) regrade[prob] = autograder.grade(prob, student_input) else: # single answer case student_input = v['value'] regrade[prob] = autograder.grade(prob, student_input) if 'feedback' in regrade[prob]: del regrade[prob]['feedback'] # remove giant feedback field if 'score' in regrade[prob]: rawscore_after += float(regrade[prob]['score']) is_late = er.time_created > exam_obj.grace_period if er.attempt_number == 0: print "ERROR: examrecord %d: skip, attempt_number=0" % er.id errors += 1 next score_after = compute_penalties(rawscore_after, er.attempt_number, exam_obj.resubmission_penalty, is_late, exam_obj.late_penalty) s = er.student try: es = ExamScore.objects.get(exam=exam_obj, student=s) examscore_before = es.score except ExamScore.DoesNotExist: es = ExamScore(course=er.course, exam=exam_obj, student=s) examscore_before = -1 examscore_after = max(examscore_before, score_after) #raw = raw score, score = with penalties, agg = exam_score, over all attempts status_line = "%d, \"%s\", \"%s\", %s, %s, %s, raw:%0.1f->%0.1f score:%0.1f->%0.1f agg:%0.1f->%0.1f late:%d->%d" \ % (er.id, s.first_name, s.last_name, s.username, s.email, str(er.time_created), rawscore_before, rawscore_after, score_before, score_after, examscore_before, examscore_after, er.late, is_late) if score_before == score_after and rawscore_before == rawscore_after \ and examscore_before == examscore_after and is_late == er.late : print "OK: " + status_line continue regrades += 1 print "REGRADE: " + status_line if not options['dryrun']: if score_before != score_after or is_late != er.late: er.json_score_data = json.dumps(regrade) er.score = score_after er.late = is_late er.save() updates += 1 if ers_created or rawscore_before != rawscore_after: ers.raw_score = rawscore_after ers.save() updates += 1 if examscore_before != examscore_after: es.score = examscore_after es.save() updates += 1 # exception handler around big ExamRecords loop -- trust me, it lines up # this just counts and skips offending rows so we can keep making progress except Exception as e: print "ERROR: examrecord %d: cannot regrade: %s" % (er.id, str(e)) errors += 1 continue print print "## SUMMARY ##" print "# Errors: %d" % errors print "# Regrades: %d" % regrades print "# Database rows updated: %d" % updates