Example #1
0
def on_init(team_submission_uuid):
    """
    Create a new team staff workflow for a student item and submission.

    Creates a unique team staff workflow for a student item, associated with a
    team submission.

    Note that the staff workflow begins things in on_init() instead of
    on_start(), because staff shoud be able to access the submission
    regardless of which state the workflow is currently in.

    Args:
        team_submission_uuid (str): The team submission associated with this workflow.

    Returns:
        None

    Raises:
        StaffAssessmentInternalError: Raised when there is an internal error
            creating the Workflow.
    """
    try:
        team_submission = team_submissions_api.get_team_submission(
            team_submission_uuid)
        TeamStaffWorkflow.objects.get_or_create(
            course_id=team_submission['course_id'],
            item_id=team_submission['item_id'],
            team_submission_uuid=team_submission_uuid)
    except DatabaseError:
        error_message = (
            "An internal error occurred while creating a new team staff workflow for team submission {}"
        ).format(team_submission_uuid)
        logger.exception(error_message)
        raise StaffAssessmentInternalError(error_message)
Example #2
0
def on_cancel(team_submission_uuid):
    """
    Cancel the team staff workflow for submission.

    Sets the cancelled_at field in team staff workflow.

    Args:
        team_submission_uuid (str): The team_submission UUID associated with this workflow.

    Returns:
        None

    """
    try:
        workflow = TeamStaffWorkflow.objects.get(
            team_submission_uuid=team_submission_uuid)
        workflow.cancelled_at = now()
        workflow.save(update_fields=['cancelled_at'])
    except TeamStaffWorkflow.DoesNotExist:
        # If we can't find a workflow, then we don't have to do anything to
        # cancel it.
        pass
    except DatabaseError:
        error_message = (
            "An internal error occurred while cancelling the team staff workflow for submission {}"
        ).format(team_submission_uuid)
        logger.exception(error_message)
        raise StaffAssessmentInternalError(error_message)
Example #3
0
def get_assessment_scores_by_criteria(team_submission_uuid):
    """Get the staff score for each rubric criterion

    Args:
        team_submission_uuid (str): The team submission uuid is used to get the
            assessment used to score this submission.

    Returns:
        (dict): A dictionary of rubric criterion names, with a score of
            the staff assessments.

    Raises:
        StaffAssessmentInternalError: If any error occurs while retrieving
            information from the scores, an error is raised.
    """
    try:
        # Get most recently graded assessment for a team submission
        team_submission = team_submissions_api.get_team_submission(
            team_submission_uuid)
        assessments = list(
            Assessment.objects.filter(
                submission_uuid__in=team_submission['submission_uuids'],
                score_type=STAFF_TYPE,
            )[:1])

        scores = Assessment.scores_by_criterion(assessments)
        # Since this is only being sent one score, the median score will be the
        # same as the only score.
        return Assessment.get_median_score_dict(scores)
    except DatabaseError:
        error_message = "Error getting staff assessment scores for {}".format(
            team_submission_uuid)
        logger.exception(error_message)
        raise StaffAssessmentInternalError(error_message)
Example #4
0
def get_assessment_scores_by_criteria(submission_uuid):
    """Get the staff score for each rubric criterion

    Args:
        submission_uuid (str): The submission uuid is used to get the
            assessment used to score this submission.

    Returns:
        (dict): A dictionary of rubric criterion names, with a score of
            the staff assessments.

    Raises:
        StaffAssessmentInternalError: If any error occurs while retrieving
            information from the scores, an error is raised.
    """
    try:
        # This will always create a list of length 1
        assessments = list(
            Assessment.objects.filter(
                score_type=STAFF_TYPE, submission_uuid=submission_uuid
            )[:1]
        )
        scores = Assessment.scores_by_criterion(assessments)
        # Since this is only being sent one score, the median score will be the
        # same as the only score.
        return Assessment.get_median_score_dict(scores)
    except DatabaseError:
        error_message = u"Error getting staff assessment scores for {}".format(submission_uuid)
        logger.exception(error_message)
        raise StaffAssessmentInternalError(error_message)
Example #5
0
def create_assessment(
        team_submission_uuid,
        scorer_id,
        options_selected,
        criterion_feedback,
        overall_feedback,
        rubric_dict,
        scored_at=None
):
    """
    Creates an assessment for each member of the submitting team.

    Closely mirrors openassessment.assessment.api.staff.py::create_assessment

    Can use _complete_assessment from Staff API as is, but has the side-effect
    of only associating the last graded assessment with the workflow

    Returns:
        dict: the Assessment model, serialized as a dict.
    """
    try:
        try:
            scorer_workflow = TeamStaffWorkflow.objects.get(team_submission_uuid=team_submission_uuid)
        except TeamStaffWorkflow.DoesNotExist:
            scorer_workflow = None

        # Get the submissions for a team
        team_submission = team_submissions_api.get_team_submission(team_submission_uuid)

        assessment_dicts = []
        for submission_uuid in team_submission['submission_uuids']:
            assessment = _complete_assessment(
                submission_uuid,
                scorer_id,
                options_selected,
                criterion_feedback,
                overall_feedback,
                rubric_dict,
                scored_at,
                scorer_workflow
            )
            assessment_dicts.append(full_assessment_dict(assessment))

        return assessment_dicts

    except InvalidRubric as ex:
        error_message = "The rubric definition is not valid."
        logger.exception(error_message)
        raise StaffAssessmentRequestError(error_message) from ex
    except InvalidRubricSelection as ex:
        error_message = "Invalid options were selected in the rubric."
        logger.warning(error_message, exc_info=True)
        raise StaffAssessmentRequestError(error_message) from ex
    except DatabaseError as ex:
        error_message = (
            "An error occurred while creating an assessment by the scorer with this ID: {}"
        ).format(scorer_id)
        logger.exception(error_message)
        raise StaffAssessmentInternalError(error_message) from ex
Example #6
0
    def get_submission_for_review(cls, course_id, item_id, scorer_id):
        """
        Find a submission for staff assessment. This function will find the next
        submission that requires assessment, excluding any submission that has been
        completely graded, or is actively being reviewed by other staff members.

        Args:
            submission_uuid (str): The submission UUID from the student
                requesting a submission for assessment. This is used to explicitly
                avoid giving the student their own submission, and determines the
                associated Peer Workflow.
            item_id (str): The student_item that we would like to retrieve submissions for.
            scorer_id (str): The user id of the staff member scoring this submission

        Returns:
            submission_uuid (str): The submission_uuid for the submission to review.

        Raises:
            StaffAssessmentInternalError: Raised when there is an error retrieving
                the workflows for this request.

        """
        # pylint: disable=unicode-format-string
        timeout = (now() - cls.TIME_LIMIT).strftime("%Y-%m-%d %H:%M:%S")
        try:
            # Search for existing submissions that the scorer has worked on.
            staff_workflows = StaffWorkflow.objects.filter(
                course_id=course_id,
                item_id=item_id,
                scorer_id=scorer_id,
                grading_completed_at=None,
                cancelled_at=None,
            )
            # If no existing submissions exist, then get any other
            # available workflows.
            if not staff_workflows:
                staff_workflows = StaffWorkflow.objects.filter(
                    models.Q(scorer_id='')
                    | models.Q(grading_started_at__lte=timeout),
                    course_id=course_id,
                    item_id=item_id,
                    grading_completed_at=None,
                    cancelled_at=None,
                )
            if not staff_workflows:
                return None

            workflow = staff_workflows[0]
            workflow.scorer_id = scorer_id
            workflow.grading_started_at = now()
            workflow.save()
            return workflow.submission_uuid
        except DatabaseError:
            error_message = (
                u"An internal error occurred while retrieving a submission for staff grading"
            )
            logger.exception(error_message)
            raise StaffAssessmentInternalError(error_message)
Example #7
0
def get_submission_to_assess(course_id, item_id, scorer_id):
    """
    Get a team submission for staff evaluation.

    Retrieves a team submission for assessment for the given staff member.

    Args:
        course_id (str): The course that we would like to fetch submissions from.
        item_id (str): The student_item (problem) that we would like to retrieve submissions for.
        scorer_id (str): The user id of the staff member scoring this submission

    Returns:
        dict: A student submission for assessment. This contains a 'student_item',
            'attempt_number', 'submitted_at', 'created_at', and 'answer' field to be
            used for assessment.

    Raises:
        StaffAssessmentInternalError: Raised when there is an internal error
            retrieving staff workflow information.

    Examples:
        >>> get_submission_to_assess("a_course_id", "an_item_id", "a_scorer_id")
        {
            'student_item': 2,
            'attempt_number': 1,
            'submitted_at': datetime.datetime(2014, 1, 29, 23, 14, 52, 649284, tzinfo=<UTC>),
            'created_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 668850, tzinfo=<UTC>),
            'answer': { ... }
        }

    """
    team_submission_uuid = TeamStaffWorkflow.get_submission_for_review(
        course_id, item_id, scorer_id)
    if team_submission_uuid:
        try:
            submission_data = team_submissions_api.get_team_submission(
                team_submission_uuid)
            return submission_data
        except DatabaseError:
            error_message = (
                "Could not find a team submission with the uuid {}"
            ).format(team_submission_uuid)
            logger.exception(error_message)
            raise StaffAssessmentInternalError(error_message)
    else:
        logger.info(
            "No team submission found for staff to assess ({}, {})".format(
                course_id,
                item_id,
            ))
        return None
Example #8
0
def get_latest_staff_assessment(team_submission_uuid):
    """
    Retrieve the latest staff assessment for a team submission.

    Args:
        team_submission_uuid (str): The UUID of the team submission being assessed.

    Returns:
        dict: The serialized assessment model
        or None if no assessments are available

    Raises:
        StaffAssessmentInternalError if there are problems connecting to the database.

    Example usage:

    >>> get_latest_staff_assessment('10df7db776686822e501b05f452dc1e4b9141fe5')
    {
        'points_earned': 6,
        'points_possible': 12,
        'scored_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 649284 tzinfo=<UTC>),
        'scorer': "staff",
        'feedback': ''
    }

    """
    try:
        # Get the reference submissions
        team_submission = team_submissions_api.get_team_submission(
            team_submission_uuid)

        # Return the most-recently graded assessment for any team member's submisison
        assessment = Assessment.objects.filter(
            submission_uuid__in=team_submission['submission_uuids'],
            score_type=STAFF_TYPE,
        ).first()
    except DatabaseError as ex:
        msg = ("An error occurred while retrieving staff assessments "
               "for the submission with UUID {uuid}: {ex}").format(
                   uuid=team_submission_uuid, ex=ex)
        logger.exception(msg)
        raise StaffAssessmentInternalError(msg)

    if assessment:
        return full_assessment_dict(assessment)

    return None
Example #9
0
def get_latest_staff_assessment(submission_uuid):
    """
    Retrieve the latest staff assessment for a submission.

    Args:
        submission_uuid (str): The UUID of the submission being assessed.

    Returns:
        dict: The serialized assessment model
        or None if no assessments are available

    Raises:
        StaffAssessmentInternalError if there are problems connecting to the database.

    Example usage:

    >>> get_latest_staff_assessment('10df7db776686822e501b05f452dc1e4b9141fe5')
    {
        'points_earned': 6,
        'points_possible': 12,
        'scored_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 649284 tzinfo=<UTC>),
        'scorer': u"staff",
        'feedback': u''
    }

    """
    try:
        assessments = Assessment.objects.filter(
            submission_uuid=submission_uuid,
            score_type=STAFF_TYPE,
        )[:1]
    except DatabaseError as ex:
        msg = (
            u"An error occurred while retrieving staff assessments "
            u"for the submission with UUID {uuid}: {ex}"
        ).format(uuid=submission_uuid, ex=ex)
        logger.exception(msg)
        raise StaffAssessmentInternalError(msg)

    if len(assessments) > 0:
        return full_assessment_dict(assessments[0])
    else:
        return None
Example #10
0
def on_init(team_submission_uuid):
    """
    Create a new team staff workflow for a student item and submission.

    Creates a unique team staff workflow for a student item, associated with a
    team submission.

    Note that the staff workflow begins things in on_init() instead of
    on_start(), because staff shoud be able to access the submission
    regardless of which state the workflow is currently in.

    Args:
        team_submission_uuid (str): The team submission associated with this workflow.

    Returns:
        None

    Raises:
        StaffAssessmentInternalError: Raised when there is an internal error
            creating the Workflow.
    """
    try:
        team_submission = team_submissions_api.get_team_submission(
            team_submission_uuid)
        TeamStaffWorkflow.objects.get_or_create(
            course_id=team_submission['course_id'],
            item_id=team_submission['item_id'],
            team_submission_uuid=team_submission_uuid,
            # submission_uuid is currently not used in any logic in TeamStaffWorkflow, so we don't
            # realy care which submission is chosen and it doesn't need to match the TeamAssessment Workflow.
            # It must be filled because of the unique constraint on the field (can't have multiple '' values)
            submission_uuid=team_submission['submission_uuids'][0],
        )
    except DatabaseError:
        error_message = (
            "An internal error occurred while creating a new team staff workflow for team submission {}"
        ).format(team_submission_uuid)
        logger.exception(error_message)
        raise StaffAssessmentInternalError(error_message)
Example #11
0
def create_assessment(
    submission_uuid,
    scorer_id,
    options_selected,
    criterion_feedback,
    overall_feedback,
    rubric_dict,
    scored_at=None
):
    """Creates an assessment on the given submission.

    Assessments are created based on feedback associated with a particular
    rubric.

    Assumes that the user creating the assessment has the permissions to do so.

    Args:
        submission_uuid (str): The submission uuid for the submission being
            assessed.
        scorer_id (str): The user ID for the user giving this assessment. This
            is required to create an assessment on a submission.
        options_selected (dict): Dictionary mapping criterion names to the
            option names the user selected for that criterion.
        criterion_feedback (dict): Dictionary mapping criterion names to the
            free-form text feedback the user gave for the criterion.
            Since criterion feedback is optional, some criteria may not appear
            in the dictionary.
        overall_feedback (unicode): Free-form text feedback on the submission overall.
        rubric_dict (dict): The rubric model associated with this assessment
        scored_at (datetime): Optional argument to override the time in which
            the assessment took place. If not specified, scored_at is set to
            now.

    Keyword Args:
        scored_at (datetime): Optional argument to override the time in which
            the assessment took place. If not specified, scored_at is set to
            now.

    Returns:
        dict: the Assessment model, serialized as a dict.

    Raises:
        StaffAssessmentRequestError: Raised when the submission_id is invalid, or
            the assessment_dict does not contain the required values to create
            an assessment.
        StaffAssessmentInternalError: Raised when there is an internal error
            while creating a new assessment.

    Examples:
        >>> options_selected = {"clarity": "Very clear", "precision": "Somewhat precise"}
        >>> criterion_feedback = {"clarity": "I thought this essay was very clear."}
        >>> feedback = "Your submission was thrilling."
        >>> create_assessment("Tim", options_selected, criterion_feedback, feedback, rubric_dict)
    """
    try:
        try:
            scorer_workflow = StaffWorkflow.objects.get(submission_uuid=submission_uuid)
        except StaffWorkflow.DoesNotExist:
            scorer_workflow = None

        assessment = _complete_assessment(
            submission_uuid,
            scorer_id,
            options_selected,
            criterion_feedback,
            overall_feedback,
            rubric_dict,
            scored_at,
            scorer_workflow
        )
        return full_assessment_dict(assessment)

    except InvalidRubric:
        error_message = u"The rubric definition is not valid."
        logger.exception(error_message)
        raise StaffAssessmentRequestError(error_message)
    except InvalidRubricSelection:
        error_message = u"Invalid options were selected in the rubric."
        logger.warning(error_message, exc_info=True)
        raise StaffAssessmentRequestError(error_message)
    except DatabaseError:
        error_message = (
            u"An error occurred while creating an assessment by the scorer with this ID: {}"
        ).format(scorer_id)
        logger.exception(error_message)
        raise StaffAssessmentInternalError(error_message)