def _compute_block_score( self, block_key, course_structure, submissions_scores, csm_scores, persisted_block=None, ): """ Compute score for the given block. If persisted_values is provided, it is used for possible and weight. """ try: block = course_structure[block_key] except KeyError: # It's possible that the user's access to that # block has changed since the subsection grade # was last persisted. pass else: if getattr(block, 'has_score', False): problem_score = get_score( submissions_scores, csm_scores, persisted_block, block, ) if problem_score: self.locations_to_scores[block_key] = problem_score
def problem_scores(self): """ Overrides the problem_scores member variable in order to return empty scores for all scorable problems in the course. NOTE: The use of `course_data.structure` here is very intentional. It means we look through the user-specific subtree of this subsection, taking into account which problems are visible to the user. """ locations = OrderedDict() # dict of problem locations to ProblemScore for block_key in self.course_data.structure.post_order_traversal( filter_func=possibly_scored, start_node=self.location, ): block = self.course_data.structure[block_key] if getattr(block, 'has_score', False): problem_score = get_score( submissions_scores={}, csm_scores={}, persisted_block=None, block=block, ) if problem_score is not None: locations[block_key] = problem_score return locations
def test_get_score(self, submission_value, csm_value, persisted_block_value, block_value, expected_result): score = scores.get_score( self._create_submissions_scores(submission_value), self._create_csm_scores(csm_value), self._create_persisted_block(persisted_block_value), self._create_block(block_value), ) expected_score = ProblemScore(**expected_result._asdict()) self.assertEquals(score, expected_score)
def get_vertical_score(block_key, course_structure, submissions_scores, csm_scores, persisted_block=None): from lms.djangoapps.grades.scores import get_score, ProblemScore # placed here to avoid circular import if block_key.category != VERTICAL_CATEGORY: return vertical_weight = getattr(course_structure[block_key], "weight", None) if not vertical_weight: return children_keys = course_structure.get_children(block_key) children_scores = [] for child_key in children_keys: try: block = course_structure[child_key] except KeyError: # It's possible that the user's access to that # block has changed since the subsection grade # was last persisted. pass else: if getattr(block, 'has_score', False): problem_score = get_score( submissions_scores, csm_scores, persisted_block, block, ) if problem_score: children_scores.append(problem_score) if not children_scores: return vertical_possible = sum(score.possible for score in children_scores) vertical_earned = sum(score.earned for score in children_scores) weighted_earned = vertical_weight * float( vertical_earned) / vertical_possible weighted_possible = vertical_weight inner_first_attempted = list(score.first_attempted for score in children_scores) vertical_attempted = max( inner_first_attempted) if inner_first_attempted else None vertical_graded = any(score.graded for score in children_scores) vertical_pseudo_problem = ProblemScore(raw_earned=vertical_earned, raw_possible=vertical_possible, weighted_earned=weighted_earned, weighted_possible=weighted_possible, weight=vertical_weight, graded=vertical_graded, first_attempted=vertical_attempted) return vertical_pseudo_problem
def _compute_block_score( self, student, block_key, course_structure, scores_client, submissions_scores, persisted_values, ): """ Compute score for the given block. If persisted_values is provided, it is used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): possible = persisted_values.get('possible', None) weight = persisted_values.get('weight', getattr(block, 'weight', None)) (earned, possible) = get_score( student, block, scores_client, submissions_scores, weight, possible, ) if earned is not None or possible is not None: # There's a chance that the value of graded is not the same # value when the problem was scored. Since we get the value # from the block_structure. # # Cannot grade a problem with a denominator of 0. # TODO: None > 0 is not python 3 compatible. block_graded = self._get_explicit_graded( block, course_structure) if possible > 0 else False self.locations_to_weighted_scores[block.location] = ( Score( earned, possible, block_graded, block_metadata_utils.display_name_with_default_escaped( block), block.location, ), weight, )
def _compute_block_score( self, student, block_key, course_structure, scores_client, submissions_scores, persisted_values, ): """ Compute score for the given block. If persisted_values is provided, it is used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): possible = persisted_values.get('possible', None) weight = persisted_values.get('weight', getattr(block, 'weight', None)) (earned, possible) = get_score( student, block, scores_client, submissions_scores, weight, possible, ) if earned is not None or possible is not None: # There's a chance that the value of graded is not the same # value when the problem was scored. Since we get the value # from the block_structure. # # Cannot grade a problem with a denominator of 0. # TODO: None > 0 is not python 3 compatible. block_graded = self._get_explicit_graded(block, course_structure) if possible > 0 else False self.locations_to_weighted_scores[block.location] = ( Score( earned, possible, block_graded, block_metadata_utils.display_name_with_default_escaped(block), block.location, ), weight, )
def _compute_block_score( # lint-amnesty, pylint: disable=missing-function-docstring block_key, course_structure, submissions_scores, csm_scores, persisted_block=None, ): # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': log.info( 'Computing block score for block: ***{}*** in course: ***{}***.' .format( str(block_key), str(block_key.course_key), )) try: block = course_structure[block_key] except KeyError: # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': log.info( 'User\'s access to block: ***{}*** in course: ***{}*** has changed. ' 'No block score calculated.'.format( str(block_key), str(block_key.course_key))) # It's possible that the user's access to that # block has changed since the subsection grade # was last persisted. else: if getattr(block, 'has_score', False): # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key ) == 'course-v1:UQx+BUSLEAD5x+2T2019': log.info( 'Block: ***{}*** in course: ***{}*** HAS has_score attribute. Continuing.' .format(str(block_key), str(block_key.course_key))) return get_score( submissions_scores, csm_scores, persisted_block, block, ) # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': log.info( 'Block: ***{}*** in course: ***{}*** DOES NOT HAVE has_score attribute. ' 'No block score calculated.'.format( str(block_key), str(block_key.course_key)))
def problem_scores(self): """ Overrides the problem_scores member variable in order to return empty scores for all scorable problems in the course. """ locations = OrderedDict() # dict of problem locations to ProblemScore for block_key in self.course_data.structure.post_order_traversal( filter_func=possibly_scored, start_node=self.location, ): block = self.course_data.structure[block_key] if getattr(block, 'has_score', False): locations[block_key] = get_score( submissions_scores={}, csm_scores={}, persisted_block=None, block=block, ) return locations
def _compute_block_score( self, student, block_key, course_structure, scores_client, submissions_scores, persisted_values=None, ): """ Compute score for the given block. If persisted_values is provided, it will be used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): (earned, possible) = get_score( student, block, scores_client, submissions_scores, ) # There's a chance that the value of weight is not the same value used when the problem was scored, # since we can get the value from either block_structure or CSM/submissions. weight = getattr(block, 'weight', None) if persisted_values: possible = persisted_values.get('possible', possible) weight = persisted_values.get('weight', weight) if earned is not None or possible is not None: # cannot grade a problem with a denominator of 0 block_graded = block.graded if possible > 0 else False self.locations_to_weighted_scores[block.location] = ( Score( earned, possible, block_graded, block_metadata_utils.display_name_with_default_escaped( block), block.location, ), weight, )
def problem_scores(self): """ Overrides the problem_scores member variable in order to return empty scores for all scorable problems in the course. """ locations = OrderedDict() # dict of problem locations to ProblemScore for block_key in self.course_data.structure.post_order_traversal( filter_func=possibly_scored, start_node=self.location, ): block = self.course_data.structure[block_key] if getattr(block, 'has_score', False): problem_score = get_score( submissions_scores={}, csm_scores={}, persisted_block=None, block=block, ) if problem_score is not None: locations[block_key] = problem_score return locations
def _compute_block_score( self, student, block_key, course_structure, scores_client, submissions_scores, persisted_values=None, ): """ Compute score for the given block. If persisted_values is provided, it will be used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): (earned, possible) = get_score( student, block, scores_client, submissions_scores, ) # There's a chance that the value of weight is not the same value used when the problem was scored, # since we can get the value from either block_structure or CSM/submissions. weight = block.weight if persisted_values: possible = persisted_values.get('possible', possible) weight = persisted_values.get('weight', weight) if earned is not None or possible is not None: # cannot grade a problem with a denominator of 0 block_graded = block.graded if possible > 0 else False self.locations_to_weighted_scores[block.location] = ( Score( earned, possible, block_graded, block_metadata_utils.display_name_with_default_escaped(block), block.location, ), weight, )
def _compute_block_score( block_key, course_structure, submissions_scores, csm_scores, persisted_block=None, ): try: block = course_structure[block_key] except KeyError: # It's possible that the user's access to that # block has changed since the subsection grade # was last persisted. pass else: if getattr(block, 'has_score', False): return get_score( submissions_scores, csm_scores, persisted_block, block, )
def _compute_block_score( self, block_key, course_structure, submissions_scores, csm_scores, persisted_block=None, ): """ Compute score for the given block. If persisted_values is provided, it is used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): problem_score = get_score( submissions_scores, csm_scores, persisted_block, block, ) if problem_score: self.locations_to_scores[block_key] = problem_score
def compute(self, student, course_structure, scores_client, submissions_scores): """ Compute the grade of this subsection for the given student and course. """ for descendant_key in course_structure.post_order_traversal( filter_func=possibly_scored, start_node=self.location, ): descendant = course_structure[descendant_key] if not getattr(descendant, 'has_score', False): continue (earned, possible) = get_score( student, descendant, scores_client, submissions_scores, ) if earned is None and possible is None: continue # cannot grade a problem with a denominator of 0 descendant_graded = descendant.graded if possible > 0 else False self.locations_to_scores[descendant.location] = Score( earned, possible, descendant_graded, block_metadata_utils.display_name_with_default_escaped(descendant), descendant.location, ) self.all_total, self.graded_total = graders.aggregate_scores( self.scores, self.display_name, )