Beispiel #1
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)
Beispiel #2
0
    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
Beispiel #4
0
    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
Beispiel #5
0
 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))
Beispiel #6
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))
Beispiel #7
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)
Beispiel #8
0
    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
Beispiel #9
0
    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