<commit_before>"""
Validate changes to an XBlock before it is updated.
"""
from collections import Counter
from openassessment.assessment.serializers import rubric_from_dict, InvalidRubric
from openassessment.assessment.api.student_training import validate_training_examples
from openassessment.xblock.resolve_dates import resolve_dates, DateValidationError, InvalidDateFormat
from openassessment.xblock.data_conversion import convert_training_examples_list_to_dict


def _match_by_order(items, others):
    """
    Given two lists of dictionaries, each containing "order_num" keys,
    return a set of tuples, where the items in the tuple are dictionaries
    with the same "order_num" keys.

    Args:
        items (list of dict): Items to match, each of which must contain a "order_num" key.
        others (list of dict): Items to match, each of which must contain a "order_num" key.

    Returns:
        list of tuples, each containing two dictionaries

    Raises:
        IndexError: A dictionary does no contain a 'order_num' key.
    """
    # Sort each dictionary by its "name" key, then zip them and return
    key_func = lambda x: x['order_num']
    return zip(sorted(items, key=key_func), sorted(others, key=key_func))


def _duplicates(items):
    """
    Given an iterable of items, return a set of duplicate items in the list.

    Args:
        items (list): The list of items, which may contain duplicates.

    Returns:
        set: The set of duplicate items in the list.

    """
    counts = Counter(items)
    return set(x for x in items if counts[x] > 1)


def _is_valid_assessment_sequence(assessments):
    """
    Check whether the sequence of assessments is valid.
    For example, we currently allow self-assessment after peer-assessment,
    but do not allow peer-assessment before self-assessment.

    Args:
        assessments (list of dict): List of assessment dictionaries.

    Returns:
        bool

    """
    valid_sequences = [
        ['self-assessment'],
        ['peer-assessment'],
        ['peer-assessment', 'self-assessment'],
        ['self-assessment', 'peer-assessment'],
        ['student-training', 'peer-assessment'],
        ['student-training', 'peer-assessment', 'self-assessment'],
        ['student-training', 'self-assessment', 'peer-assessment'],
        ['example-based-assessment'],
        ['example-based-assessment', 'self-assessment'],
        ['example-based-assessment', 'peer-assessment'],
        ['example-based-assessment', 'peer-assessment', 'self-assessment'],
        ['example-based-assessment', 'self-assessment', 'peer-assessment'],
        ['example-based-assessment', 'student-training', 'peer-assessment'],
        ['example-based-assessment', 'student-training', 'peer-assessment', 'self-assessment'],
        ['example-based-assessment', 'student-training', 'self-assessment', 'peer-assessment'],
    ]

    sequence = [asmnt.get('name') for asmnt in assessments]
    return sequence in valid_sequences


def validate_assessments(assessments, current_assessments, is_released, _):
    """
    Check that the assessment dict is semantically valid.

    Valid assessment steps are currently:
    * peer, then self
    * self only

    If a question has been released, the type and number of assessment steps
    cannot be changed.

    Args:
        assessments (list of dict): list of serialized assessment models.
        current_assessments (list of dict): list of the current serialized
            assessment models. Used to determine if the assessment configuration
            has changed since the question had been released.
        is_released (boolean) : True if the question has been released.
        _ (function): The service function used to get the appropriate i18n text

    Returns:
        tuple (is_valid, msg) where
            is_valid is a boolean indicating whether the assessment is semantically valid
            and msg describes any validation errors found.
    """
    if len(assessments) == 0:
        return (False, _("This problem must include at least one assessment."))

    # Ensure that we support this sequence of assessments.
    if not _is_valid_assessment_sequence(assessments):
        msg = _("The assessment order you selected is invalid.")
        return False, msg

    for assessment_dict in assessments:
        # Number you need to grade is >= the number of people that need to grade you
        if assessment_dict.get('name') == 'peer-assessment':
            must_grade = assessment_dict.get('must_grade')
            must_be_graded_by = assessment_dict.get('must_be_graded_by')

            if must_grade is None or must_grade < 1:
                return (False, _('The "must_grade" value must be a positive integer.'))

            if must_be_graded_by is None or must_be_graded_by < 1:
                return (False, _('The "must_be_graded_by" value must be a positive integer.'))

            if must_grade < must_be_graded_by:
                return (False, _('The "must_grade" value must be greater than or equal to the "must_be_graded_by" value.'))

        # Student Training must have at least one example, and all
        # examples must have unique answers.
        if assessment_dict.get('name') == 'student-training':
            answers = []
            examples = assessment_dict.get('examples')
            if not examples:
                return False, _('You must provide at least one example response for student training.')
            for example in examples:
                if example.get('answer') in answers:
                    return False, _('Each example response for student training must be unique.')
                answers.append(example.get('answer'))

        # Example-based assessment MUST specify 'ease' or 'fake' as the algorithm ID,
        # at least for now.  Later, we may make this more flexible.
        if assessment_dict.get('name') == 'example-based-assessment':<commit_msg>Update the validation messages for must grade / graded by values<commit_after>"""
Validate changes to an XBlock before it is updated.
"""
from collections import Counter
from openassessment.assessment.serializers import rubric_from_dict, InvalidRubric
from openassessment.assessment.api.student_training import validate_training_examples
from openassessment.xblock.resolve_dates import resolve_dates, DateValidationError, InvalidDateFormat
from openassessment.xblock.data_conversion import convert_training_examples_list_to_dict


def _match_by_order(items, others):
    """
    Given two lists of dictionaries, each containing "order_num" keys,
    return a set of tuples, where the items in the tuple are dictionaries
    with the same "order_num" keys.

    Args:
        items (list of dict): Items to match, each of which must contain a "order_num" key.
        others (list of dict): Items to match, each of which must contain a "order_num" key.

    Returns:
        list of tuples, each containing two dictionaries

    Raises:
        IndexError: A dictionary does no contain a 'order_num' key.
    """
    # Sort each dictionary by its "name" key, then zip them and return
    key_func = lambda x: x['order_num']
    return zip(sorted(items, key=key_func), sorted(others, key=key_func))


def _duplicates(items):
    """
    Given an iterable of items, return a set of duplicate items in the list.

    Args:
        items (list): The list of items, which may contain duplicates.

    Returns:
        set: The set of duplicate items in the list.

    """
    counts = Counter(items)
    return set(x for x in items if counts[x] > 1)


def _is_valid_assessment_sequence(assessments):
    """
    Check whether the sequence of assessments is valid.
    For example, we currently allow self-assessment after peer-assessment,
    but do not allow peer-assessment before self-assessment.

    Args:
        assessments (list of dict): List of assessment dictionaries.

    Returns:
        bool

    """
    valid_sequences = [
        ['self-assessment'],
        ['peer-assessment'],
        ['peer-assessment', 'self-assessment'],
        ['self-assessment', 'peer-assessment'],
        ['student-training', 'peer-assessment'],
        ['student-training', 'peer-assessment', 'self-assessment'],
        ['student-training', 'self-assessment', 'peer-assessment'],
        ['example-based-assessment'],
        ['example-based-assessment', 'self-assessment'],
        ['example-based-assessment', 'peer-assessment'],
        ['example-based-assessment', 'peer-assessment', 'self-assessment'],
        ['example-based-assessment', 'self-assessment', 'peer-assessment'],
        ['example-based-assessment', 'student-training', 'peer-assessment'],
        ['example-based-assessment', 'student-training', 'peer-assessment', 'self-assessment'],
        ['example-based-assessment', 'student-training', 'self-assessment', 'peer-assessment'],
    ]

    sequence = [asmnt.get('name') for asmnt in assessments]
    return sequence in valid_sequences


def validate_assessments(assessments, current_assessments, is_released, _):
    """
    Check that the assessment dict is semantically valid.

    Valid assessment steps are currently:
    * peer, then self
    * self only

    If a question has been released, the type and number of assessment steps
    cannot be changed.

    Args:
        assessments (list of dict): list of serialized assessment models.
        current_assessments (list of dict): list of the current serialized
            assessment models. Used to determine if the assessment configuration
            has changed since the question had been released.
        is_released (boolean) : True if the question has been released.
        _ (function): The service function used to get the appropriate i18n text

    Returns:
        tuple (is_valid, msg) where
            is_valid is a boolean indicating whether the assessment is semantically valid
            and msg describes any validation errors found.
    """
    if len(assessments) == 0:
        return (False, _("This problem must include at least one assessment."))

    # Ensure that we support this sequence of assessments.
    if not _is_valid_assessment_sequence(assessments):
        msg = _("The assessment order you selected is invalid.")
        return False, msg

    for assessment_dict in assessments:
        # Number you need to grade is >= the number of people that need to grade you
        if assessment_dict.get('name') == 'peer-assessment':
            must_grade = assessment_dict.get('must_grade')
            must_be_graded_by = assessment_dict.get('must_be_graded_by')

            if must_grade is None or must_grade < 1:
                return (False, _('In peer assessment, the "Must Grade" value must be a positive integer.'))

            if must_be_graded_by is None or must_be_graded_by < 1:
                return (False, _('In peer assessment, the "Graded By" value must be a positive integer.'))

            if must_grade < must_be_graded_by:
                return (False, _('In peer assessment, the "Must Grade" value must be greater than or equal to the "Graded By" value.'))

        # Student Training must have at least one example, and all
        # examples must have unique answers.
        if assessment_dict.get('name') == 'student-training':
            answers = []
            examples = assessment_dict.get('examples')
            if not examples:
                return False, _('You must provide at least one example response for student training.')
            for example in examples:
                if example.get('answer') in answers:
                    return False, _('Each example response for student training must be unique.')
                answers.append(example.get('answer'))

        # Example-based assessment MUST specify 'ease' or 'fake' as the algorithm ID,
        # at least for now.  Later, we may make this more flexible.
        if assessment_dict.get('name') == 'example-based-assessment':
{'fraction_non_alphanumeric': 0.08911468310152411, 'fraction_numerical': 0.0009994170067460647, 'mean_word_length': 2.9591163864160897, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7539477', 'n_tokens_mistral': 3214, 'n_tokens_neox': 3102, 'n_words': 1175}