<commit_before>import json
import logging
from optparse import make_option

from django.core.management.base import BaseCommand # , CommandError

from courseware.models import StudentModule
from capa.correctmap import CorrectMap

#
# This is aimed at fixing a temporary problem encountered where partial credit was awarded for 
# code problems, but the resulting score (or grade) was mistakenly set to zero
# because of a bug in CorrectMap.get_npoints().
# 
# The fix here is to recalculate the score/grade based on the partial credit.
# To narrow down the set of problems that might need fixing, the StudentModule 
# objects to be checked is filtered down to those:
#
#   grade=0.0  (the grade must have been zeroed out)
#   created < '2013-03-08 05:19:00' (the problem must have been answered before the fix was installed)
#   modified > '2013-03-07 20:18:00' (the problem must have been visited after the bug was introduced)
#   state like '%"npoints": 0.%' (the problem must have some form of partial credit).
#

log = logging.getLogger(__name__)

class Command(BaseCommand):
    
    num_visited = 0
    num_changed = 0
    
    option_list = BaseCommand.option_list + (
        make_option('--save',
                    action='store_true',
                    dest='save_changes',
                    default=False,
                    help='Persist the changes that were encountered.  If not set, no changes are saved.'), )

    def fix_studentmodules(self, save_changes):
        modules = StudentModule.objects.filter(# module_type='problem',
                                               modified__gt='2013-03-07 20:18:00',
                                               created__lt='2013-03-08 05:19:00',
                                               state__contains='"npoints": 0.',
                                               grade=0.0) 
        for module in modules:
            self.fix_studentmodule(module, save_changes)
            
    def fix_studentmodule(self, module, save_changes):
        module_state = module.state
        if module_state is None:
            log.info("No state found for {type} module {id} for student {student} in course {course_id}".format(
                    **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id}))
            return
        
        state_dict = json.loads(module_state)
        self.num_visited += 1
        
        correct_map = CorrectMap()
        if 'correct_map' in state_dict:
            correct_map.set_dict(state_dict['correct_map'])
    
        correct = 0
        for key in correct_map:
            correct += correct_map.get_npoints(key)
    
        if module.grade == correct:
            log.info("Grade matches for {type} module {id} for student {student} in course {course_id}".format(
                    **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id}))
        elif save_changes:
            log.info("Grade changing from {0} to {1} for {type} module {id} for student {student} in course {course_id}".format(
                    module.grade, correct,
                    **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id}))
            module.grade = correct
            module.save()
            self.num_changed += 1
        else:
            log.info("Grade would change from {0} to {1} for {type} module {id} for student {student} in course {course_id}".format(
                    module.grade, correct,
                    **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id}))
            self.num_changed += 1
  
    def handle(self, **options):
        save_changes = 'save_changes' in options and options['save_changes']

        log.info("Starting run: save_changes = {0}".format(save_changes))
 
        self.fix_studentmodules(save_changes)
        
        log.info("Finished run:  updating {0} of {1} modules".format(self.num_changed, self.num_visited))
    <commit_msg>Change date and remove filtering by grade.  Add check for student_answers.<commit_after>import json
import logging
from optparse import make_option

from django.core.management.base import BaseCommand # , CommandError

from courseware.models import StudentModule
from capa.correctmap import CorrectMap

#
# This is aimed at fixing a temporary problem encountered where partial credit was awarded for 
# code problems, but the resulting score (or grade) was mistakenly set to zero
# because of a bug in CorrectMap.get_npoints().
# 
# The fix here is to recalculate the score/grade based on the partial credit.
# To narrow down the set of problems that might need fixing, the StudentModule 
# objects to be checked is filtered down to those:
#
#   created < '2013-03-08 15:45:00' (the problem must have been answered before the fix was installed, on Prod and Edge)
#   modified > '2013-03-07 20:18:00' (the problem must have been visited after the bug was introduced)
#   state like '%"npoints": 0.%' (the problem must have some form of partial credit).
#

log = logging.getLogger(__name__)

class Command(BaseCommand):
    
    num_visited = 0
    num_changed = 0
    
    option_list = BaseCommand.option_list + (
        make_option('--save',
                    action='store_true',
                    dest='save_changes',
                    default=False,
                    help='Persist the changes that were encountered.  If not set, no changes are saved.'), )

    def fix_studentmodules(self, save_changes):
        modules = StudentModule.objects.filter(# module_type='problem',
                                               modified__gt='2013-03-07 20:18:00',
                                               created__lt='2013-03-08 15:45:00',
                                               state__contains='"npoints": 0.')

        for module in modules:
            self.fix_studentmodule_grade(module, save_changes)
            
    def fix_studentmodule_grade(self, module, save_changes):
        module_state = module.state
        if module_state is None:
            # not likely, since we filter on it.  But in general...
            log.info("No state found for {type} module {id} for student {student} in course {course_id}".format(
                    **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id}))
            return
        
        state_dict = json.loads(module_state)
        self.num_visited += 1
        
        # LoncapaProblem.get_score() checks student_answers -- if there are none, we will return a grade of 0
        # Check that this is the case, but do so sooner, before we do any of the other grading work.
        student_answers = state_dict['student_answers']
        if (not student_answers) or len(student_answers) == 0:
            # we should not have a grade here:
            if module.grade != 0:
                log.error("No answer found but grade {grade} exists for {type} module {id} for student {student} in course {course_id}".format(grade=module.grade,
                    type=module.module_type, id=module.module_state_key, student=module.student.username, course_id=module.course_id))
            else:
                log.debug("No answer and no grade found for {type} module {id} for student {student} in course {course_id}".format(grade=module.grade,
                    type=module.module_type, id=module.module_state_key, student=module.student.username, course_id=module.course_id))
            return

        # load into a CorrectMap, as done in LoncapaProblem.__init__():
        correct_map = CorrectMap()
        if 'correct_map' in state_dict:
            correct_map.set_dict(state_dict['correct_map'])
    
        # calculate score the way LoncapaProblem.get_score() works, by deferring to CorrectMap's get_npoints implementation.
        correct = 0
        for key in correct_map:
            correct += correct_map.get_npoints(key)
    
        if module.grade == correct:
            # nothing to change
            log.debug("Grade matches for {type} module {id} for student {student} in course {course_id}".format(
                    type=module.module_type, id=module.module_state_key, student=module.student.username, course_id=module.course_id))
        elif save_changes:
            # make the change
            log.info("Grade changing from {0} to {1} for {type} module {id} for student {student} in course {course_id}".format(
                    module.grade, correct,
                    type=module.module_type, id=module.module_state_key, student=module.student.username, course_id=module.course_id))
            module.grade = correct
            module.save()
            self.num_changed += 1
        else:
            # don't make the change, but log that the change would be made
            log.info("Grade would change from {0} to {1} for {type} module {id} for student {student} in course {course_id}".format(
                    module.grade, correct,
                    type=module.module_type, id=module.module_state_key, student=module.student.username, course_id=module.course_id))
            self.num_changed += 1
  
    def handle(self, **options):
        save_changes = options['save_changes']

        log.info("Starting run: save_changes = {0}".format(save_changes))
 
        self.fix_studentmodules(save_changes)
        
        log.info("Finished run:  updating {0} of {1} modules".format(self.num_changed, self.num_visited))
    
{'fraction_non_alphanumeric': 0.09447764269524006, 'fraction_numerical': 0.015351329074799093, 'mean_word_length': 2.438540559688275, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1791857', 'n_tokens_mistral': 2744, 'n_tokens_neox': 2578, 'n_words': 799}