def grade_details( self, submission_uuid, peer_assessments, self_assessment, staff_assessment, is_staff=False ): """ Returns details about the grade assigned to the submission. Args: submission_uuid (str): The id of the submission being graded. peer_assessments (list of dict): Serialized assessment models from the peer API. self_assessment (dict): Serialized assessment model from the self API staff_assessment (dict): Serialized assessment model from the staff API is_staff (bool): True if the grade details are being displayed to staff, else False. Default value is False (meaning grade details are being shown to the learner). Returns: A dictionary with full details about the submission's grade. Example: { criteria: [{ 'label': 'Test name', 'name': 'f78ac7d4ca1e4134b0ba4b40ca212e72', 'prompt': 'Test prompt', 'order_num': 2, 'options': [...] 'feedback': [ 'Good job!', 'Excellent work!', ] }], additional_feedback: [{ }] ... } """ # Import is placed here to avoid model import at project startup. from openassessment.assessment.api import peer as peer_api from openassessment.assessment.api import self as self_api from openassessment.assessment.api import staff as staff_api criteria = copy.deepcopy(self.rubric_criteria_with_labels) def has_feedback(assessments): """ Returns True if at least one assessment has feedback. Args: assessments: A list of assessments Returns: Returns True if at least one assessment has feedback. """ return any( ( assessment and (assessment.get('feedback', None) or has_feedback(assessment.get('individual_assessments', []))) ) for assessment in assessments ) max_scores = peer_api.get_rubric_max_scores(submission_uuid) median_scores = None assessment_steps = self.assessment_steps if staff_assessment: median_scores = staff_api.get_assessment_scores_by_criteria(submission_uuid) elif "peer-assessment" in assessment_steps: median_scores = peer_api.get_assessment_median_scores(submission_uuid) elif "self-assessment" in assessment_steps: median_scores = self_api.get_assessment_scores_by_criteria(submission_uuid) for criterion in criteria: criterion_name = criterion['name'] # Record assessment info for the current criterion criterion['assessments'] = self._graded_assessments( submission_uuid, criterion, assessment_steps, staff_assessment, peer_assessments, self_assessment, is_staff=is_staff, ) # Record whether there is any feedback provided in the assessments criterion['has_feedback'] = has_feedback(criterion['assessments']) # Although we prevent course authors from modifying criteria post-release, # it's still possible for assessments created by course staff to # have criteria that differ from the current problem definition. # It's also possible to circumvent the post-release restriction # if course authors directly import a course into Studio. # If this happens, we simply leave the score blank so that the grade # section can render without error. criterion['median_score'] = median_scores.get(criterion_name, '') criterion['total_value'] = max_scores.get(criterion_name, '') return { 'criteria': criteria, 'additional_feedback': self._additional_feedback( staff_assessment=staff_assessment, peer_assessments=peer_assessments, self_assessment=self_assessment, ), }
def grade_details(self, submission_uuid, peer_assessments, self_assessment, staff_assessment, is_staff=False): """ Returns details about the grade assigned to the submission. Args: submission_uuid (str): The id of the submission being graded. peer_assessments (list of dict): Serialized assessment models from the peer API. self_assessment (dict): Serialized assessment model from the self API staff_assessment (dict): Serialized assessment model from the staff API is_staff (bool): True if the grade details are being displayed to staff, else False. Default value is False (meaning grade details are being shown to the learner). Returns: A dictionary with full details about the submission's grade. Example: { criteria: [{ 'label': 'Test name', 'name': 'f78ac7d4ca1e4134b0ba4b40ca212e72', 'prompt': 'Test prompt', 'order_num': 2, 'options': [...] 'feedback': [ 'Good job!', 'Excellent work!', ] }], additional_feedback: [{ }] ... } """ # Import is placed here to avoid model import at project startup. from openassessment.assessment.api import peer as peer_api from openassessment.assessment.api import self as self_api from openassessment.assessment.api import staff as staff_api criteria = copy.deepcopy(self.rubric_criteria_with_labels) def has_feedback(assessments): """ Returns True if at least one assessment has feedback. Args: assessments: A list of assessments Returns: Returns True if at least one assessment has feedback. """ return any((assessment and ( assessment.get('feedback', None) or has_feedback(assessment.get('individual_assessments', [])))) for assessment in assessments) max_scores = peer_api.get_rubric_max_scores(submission_uuid) median_scores = None assessment_steps = self.assessment_steps if staff_assessment: median_scores = staff_api.get_assessment_scores_by_criteria( submission_uuid) elif "peer-assessment" in assessment_steps: median_scores = peer_api.get_assessment_median_scores( submission_uuid) elif "self-assessment" in assessment_steps: median_scores = self_api.get_assessment_scores_by_criteria( submission_uuid) for criterion in criteria: criterion_name = criterion['name'] # Record assessment info for the current criterion criterion['assessments'] = self._graded_assessments( submission_uuid, criterion, assessment_steps, staff_assessment, peer_assessments, self_assessment, is_staff=is_staff, ) # Record whether there is any feedback provided in the assessments criterion['has_feedback'] = has_feedback(criterion['assessments']) # Although we prevent course authors from modifying criteria post-release, # it's still possible for assessments created by course staff to # have criteria that differ from the current problem definition. # It's also possible to circumvent the post-release restriction # if course authors directly import a course into Studio. # If this happens, we simply leave the score blank so that the grade # section can render without error. criterion['median_score'] = median_scores.get(criterion_name, '') criterion['total_value'] = max_scores.get(criterion_name, '') return { 'criteria': criteria, 'additional_feedback': self._additional_feedback( staff_assessment=staff_assessment, peer_assessments=peer_assessments, self_assessment=self_assessment, ), }
def render_grade_complete(self, workflow): """ Render the grade complete state. Args: workflow (dict): The serialized Workflow model. Returns: tuple of context (dict), template_path (string) """ # Peer specific stuff... assessment_steps = self.assessment_steps submission_uuid = workflow['submission_uuid'] example_based_assessment = None self_assessment = None feedback = None peer_assessments = [] has_submitted_feedback = False if "peer-assessment" in assessment_steps: feedback = peer_api.get_assessment_feedback(submission_uuid) peer_assessments = [ self._assessment_grade_context(asmnt) for asmnt in peer_api.get_assessments(submission_uuid) ] has_submitted_feedback = feedback is not None if "self-assessment" in assessment_steps: self_assessment = self._assessment_grade_context( self_api.get_assessment(submission_uuid)) if "example-based-assessment" in assessment_steps: example_based_assessment = self._assessment_grade_context( ai_api.get_latest_assessment(submission_uuid)) feedback_text = feedback.get('feedback', '') if feedback else '' student_submission = sub_api.get_submission(submission_uuid) # We retrieve the score from the workflow, which in turn retrieves # the score for our current submission UUID. # We look up the score by submission UUID instead of student item # to ensure that the score always matches the rubric. # It's possible for the score to be `None` even if the workflow status is "done" # when all the criteria in the rubric are feedback-only (no options). score = workflow['score'] context = { 'score': score, 'feedback_text': feedback_text, 'student_submission': student_submission, 'peer_assessments': peer_assessments, 'self_assessment': self_assessment, 'example_based_assessment': example_based_assessment, 'rubric_criteria': self._rubric_criteria_grade_context(peer_assessments, self_assessment), 'has_submitted_feedback': has_submitted_feedback, 'allow_file_upload': self.allow_file_upload, 'file_url': self.get_download_url_from_submission(student_submission) } # Update the scores we will display to the user # Note that we are updating a *copy* of the rubric criteria stored in # the XBlock field max_scores = peer_api.get_rubric_max_scores(submission_uuid) median_scores = None if "peer-assessment" in assessment_steps: median_scores = peer_api.get_assessment_median_scores( submission_uuid) elif "self-assessment" in assessment_steps: median_scores = self_api.get_assessment_scores_by_criteria( submission_uuid) elif "example-based-assessment" in assessment_steps: median_scores = ai_api.get_assessment_scores_by_criteria( submission_uuid) if median_scores is not None and max_scores is not None: for criterion in context["rubric_criteria"]: # Although we prevent course authors from modifying criteria post-release, # it's still possible for assessments created by course staff to # have criteria that differ from the current problem definition. # It's also possible to circumvent the post-release restriction # if course authors directly import a course into Studio. # If this happens, we simply leave the score blank so that the grade # section can render without error. criterion["median_score"] = median_scores.get( criterion["name"], '') criterion["total_value"] = max_scores.get( criterion["name"], '') return ('openassessmentblock/grade/oa_grade_complete.html', context)
def render_grade_complete(self, workflow): """ Render the grade complete state. Args: workflow (dict): The serialized Workflow model. Returns: tuple of context (dict), template_path (string) """ # Peer specific stuff... assessment_steps = self.assessment_steps submission_uuid = workflow['submission_uuid'] example_based_assessment = None self_assessment = None feedback = None peer_assessments = [] has_submitted_feedback = False if "peer-assessment" in assessment_steps: feedback = peer_api.get_assessment_feedback(submission_uuid) peer_assessments = [ self._assessment_grade_context(asmnt) for asmnt in peer_api.get_assessments(submission_uuid) ] has_submitted_feedback = feedback is not None if "self-assessment" in assessment_steps: self_assessment = self._assessment_grade_context( self_api.get_assessment(submission_uuid) ) if "example-based-assessment" in assessment_steps: example_based_assessment = self._assessment_grade_context( ai_api.get_latest_assessment(submission_uuid) ) feedback_text = feedback.get('feedback', '') if feedback else '' student_submission = sub_api.get_submission(submission_uuid) # We retrieve the score from the workflow, which in turn retrieves # the score for our current submission UUID. # We look up the score by submission UUID instead of student item # to ensure that the score always matches the rubric. # It's possible for the score to be `None` even if the workflow status is "done" # when all the criteria in the rubric are feedback-only (no options). score = workflow['score'] context = { 'score': score, 'feedback_text': feedback_text, 'student_submission': student_submission, 'peer_assessments': peer_assessments, 'self_assessment': self_assessment, 'example_based_assessment': example_based_assessment, 'rubric_criteria': self._rubric_criteria_grade_context(peer_assessments, self_assessment), 'has_submitted_feedback': has_submitted_feedback, 'allow_file_upload': self.allow_file_upload, 'allow_latex': self.allow_latex, 'file_url': self.get_download_url_from_submission(student_submission) } # Update the scores we will display to the user # Note that we are updating a *copy* of the rubric criteria stored in # the XBlock field max_scores = peer_api.get_rubric_max_scores(submission_uuid) median_scores = None if "peer-assessment" in assessment_steps: median_scores = peer_api.get_assessment_median_scores(submission_uuid) elif "self-assessment" in assessment_steps: median_scores = self_api.get_assessment_scores_by_criteria(submission_uuid) elif "example-based-assessment" in assessment_steps: median_scores = ai_api.get_assessment_scores_by_criteria(submission_uuid) if median_scores is not None and max_scores is not None: for criterion in context["rubric_criteria"]: # Although we prevent course authors from modifying criteria post-release, # it's still possible for assessments created by course staff to # have criteria that differ from the current problem definition. # It's also possible to circumvent the post-release restriction # if course authors directly import a course into Studio. # If this happens, we simply leave the score blank so that the grade # section can render without error. criterion["median_score"] = median_scores.get(criterion["name"], '') criterion["total_value"] = max_scores.get(criterion["name"], '') return ('openassessmentblock/grade/oa_grade_complete.html', context)
def render_grade_complete(self, workflow): """ Render the grade complete state. Args: workflow (dict): The serialized Workflow model. Returns: tuple of context (dict), template_path (string) """ # Peer specific stuff... assessment_steps = self.assessment_steps submission_uuid = workflow['submission_uuid'] if "peer-assessment" in assessment_steps: feedback = peer_api.get_assessment_feedback(submission_uuid) peer_assessments = peer_api.get_assessments(submission_uuid) has_submitted_feedback = feedback is not None else: feedback = None peer_assessments = [] has_submitted_feedback = False if "self-assessment" in assessment_steps: self_assessment = self_api.get_assessment(submission_uuid) else: self_assessment = None feedback_text = feedback.get('feedback', '') if feedback else '' student_submission = sub_api.get_submission(submission_uuid) # We retrieve the score from the workflow, which in turn retrieves # the score for our current submission UUID. # We look up the score by submission UUID instead of student item # to ensure that the score always matches the rubric. score = workflow['score'] context = { 'score': score, 'feedback_text': feedback_text, 'student_submission': student_submission, 'peer_assessments': peer_assessments, 'self_assessment': self_assessment, 'rubric_criteria': self._rubric_criteria_with_feedback(peer_assessments), 'has_submitted_feedback': has_submitted_feedback, } # Update the scores we will display to the user # Note that we are updating a *copy* of the rubric criteria stored in # the XBlock field max_scores = peer_api.get_rubric_max_scores(submission_uuid) if "peer-assessment" in assessment_steps: median_scores = peer_api.get_assessment_median_scores(submission_uuid) elif "self-assessment" in assessment_steps: median_scores = self_api.get_assessment_scores_by_criteria(submission_uuid) if median_scores is not None and max_scores is not None: for criterion in context["rubric_criteria"]: criterion["median_score"] = median_scores[criterion["name"]] criterion["total_value"] = max_scores[criterion["name"]] return ('openassessmentblock/grade/oa_grade_complete.html', context)
def render_grade_complete(self, workflow): """ Render the grade complete state. Args: workflow (dict): The serialized Workflow model. Returns: tuple of context (dict), template_path (string) """ # Peer specific stuff... assessment_steps = self.assessment_steps submission_uuid = workflow['submission_uuid'] if "peer-assessment" in assessment_steps: feedback = peer_api.get_assessment_feedback(submission_uuid) peer_assessments = peer_api.get_assessments(submission_uuid) has_submitted_feedback = feedback is not None else: feedback = None peer_assessments = [] has_submitted_feedback = False if "self-assessment" in assessment_steps: self_assessment = self_api.get_assessment(submission_uuid) else: self_assessment = None feedback_text = feedback.get('feedback', '') if feedback else '' student_submission = sub_api.get_submission(submission_uuid) # We retrieve the score from the workflow, which in turn retrieves # the score for our current submission UUID. # We look up the score by submission UUID instead of student item # to ensure that the score always matches the rubric. score = workflow['score'] context = { 'score': score, 'feedback_text': feedback_text, 'student_submission': student_submission, 'peer_assessments': peer_assessments, 'self_assessment': self_assessment, 'rubric_criteria': self._rubric_criteria_with_feedback(peer_assessments), 'has_submitted_feedback': has_submitted_feedback, } # Update the scores we will display to the user # Note that we are updating a *copy* of the rubric criteria stored in # the XBlock field max_scores = peer_api.get_rubric_max_scores(submission_uuid) if "peer-assessment" in assessment_steps: median_scores = peer_api.get_assessment_median_scores( submission_uuid) elif "self-assessment" in assessment_steps: median_scores = self_api.get_assessment_scores_by_criteria( submission_uuid) if median_scores is not None and max_scores is not None: for criterion in context["rubric_criteria"]: criterion["median_score"] = median_scores[criterion["name"]] criterion["total_value"] = max_scores[criterion["name"]] return ('openassessmentblock/grade/oa_grade_complete.html', context)