<commit_before>class Assignment < ActiveRecord::Base
  require 'analytic/assignment_analytic'
  include AssignmentAnalytic
  include DynamicReviewMapping
  has_paper_trail
  belongs_to :course
  belongs_to :wiki_type
  # wiki_type needs to be removed. When an assignment is created, it needs to
  # be created as an instance of a subclass of the Assignment (model) class;
  # then Rails will "automatically' set the type field to the value that
  # designates an assignment of the appropriate type.
  has_many :participants, :class_name => 'AssignmentParticipant', :foreign_key => 'parent_id'
  has_many :participant_review_mappings, :class_name => 'ParticipantReviewResponseMap', :through => :participants, :source => :review_mappings
  has_many :users, :through => :participants
  has_many :due_dates, :dependent => :destroy
  has_many :teams, :class_name => 'AssignmentTeam', :foreign_key => 'parent_id'
  has_many :team_review_mappings, :class_name => 'TeamReviewResponseMap', :through => :teams, :source => :review_mappings
  has_many :invitations, :class_name => 'Invitation', :foreign_key => 'assignment_id', :dependent => :destroy
  has_many :assignment_questionnaires,:dependent => :destroy
  has_many :questionnaires, :through => :assignment_questionnaires
  belongs_to :instructor, :class_name => 'User', :foreign_key => 'instructor_id'
  has_many :sign_up_topics, :foreign_key => 'assignment_id', :dependent => :destroy
  has_many :response_maps, :foreign_key => 'reviewed_object_id', :class_name => 'ResponseMap'
  has_one :assignment_node,:foreign_key => :node_object_id,:dependent => :destroy

  validates_presence_of :name
  validates_uniqueness_of :name, :scope => :course_id

  COMPLETE = 'Finished'
  WAITLIST = 'Waitlist open'

  REVIEW_QUESTIONNAIRES = {:author_feedback => 0, :metareview => 1, :review => 2, :teammate_review => 3}
  #  Review Strategy information.
  RS_INSTRUCTOR_SELECTED = 'Instructor-Selected'
  RS_STUDENT_SELECTED = 'Student-Selected'
  RS_AUTO_SELECTED = 'Auto-Selected'
  REVIEW_STRATEGIES = [RS_INSTRUCTOR_SELECTED, RS_STUDENT_SELECTED, RS_AUTO_SELECTED]

  DEFAULT_MAX_REVIEWERS = 3

  def questionnaires_with_questions
    questionnaires.includes(:questions).joins(:questions)
  end

  def team_assignment?
    true
  end

  def team_assignment
    team_assignment?
  end

  # Returns a set of topics that can be used for taking the quiz.
  # We choose the topics if one of its quiz submissions has been attempted the fewest times so far
  def candidate_topics_for_quiz
    return nil if sign_up_topics.empty?   # This is not a topic assignment
    contributor_set = Array.new(contributors)
    # Reject contributors that have not selected a topic, or have no submissions
    contributor_set.reject! { |contributor| signed_up_topic(contributor).nil? }
    #####contributor_set.reject! { |contributor| !contributor.has_quiz? }
    # Reject contributions of topics whose deadline has passed
    contributor_set.reject! { |contributor| contributor.assignment.get_current_stage(signed_up_topic(contributor).id) == "Complete" or
                              contributor.assignment.get_current_stage(signed_up_topic(contributor).id) == "submission" }

    # Filter the contributors with the least number of reviews
    # (using the fact that each contributor is associated with a topic)
    ###contributor = contributor_set.min_by { |contributor| contributor.quiz_mappings.count }

    ### min_quizzes = contributor.quiz_mappings.count rescue 0
    ###contributor_set.reject! { |contributor| contributor.quiz_mappings.count > min_quizzes + review_topic_threshold }


    candidate_topics = Set.new
    contributor_set.each { |contributor| candidate_topics.add(signed_up_topic(contributor)) }
    candidate_topics
  end


  # Returns a set of topics that can be reviewed.
  # We choose the topics if one of its submissions has received the fewest reviews so far
  def candidate_topics_to_review(reviewer)
    return nil if sign_up_topics.empty? # This is not a topic assignment

    # Initialize contributor set with all teams participating in this assignment
    contributor_set = Array.new(contributors)

 
    # Reject contributors that have not selected a topic, or have no submissions
    contributor_set=reject_by_no_topic_selection_or_no_submission(contributor_set)

    # Reject contributions of topics whose deadline has passed, or which are not reviewable in the current stage
    contributor_set=reject_by_deadline(contributor_set)


    # Filter submission by reviewer him/her self
    contributor_set=reject_own_submission(contributor_set, reviewer)

    # Filter submissions already reviewed by reviewer
    contributor_set=reject_previously_reviewed_submissions(contributor_set, reviewer)

    # Filter the contributors with the least number of reviews
    # (using the fact that each contributor is associated with a topic)
    contributor_set=reject_by_least_reviewed(contributor_set)

    # Add topics for all remaining submissions to a list of available topics for review
    candidate_topics = Set.new
    contributor_set.each { |contributor|
      candidate_topics.add(signed_up_topic(contributor))
    }
    candidate_topics
  end

  #This method is only for the assignments without topics
  def candidate_assignment_teams_to_review

    contributor_set = Array.new(contributors)

    # Reject contributors that have no submissions
    contributor_set.reject! { |contributor| !contributor.has_submissions? }

    # Filter the contributors with the least number of reviews
    # (using the fact that each contributor is associated with a topic)
    contributor = contributor_set.min_by { |contributor| contributor.review_mappings.count }

    min_reviews = contributor.review_mappings.count rescue 0
    contributor_set.reject! { |contributor| contributor.review_mappings.count > min_reviews + review_topic_threshold }

    contributor_set
  end

  def reject_by_least_reviewed(contributor_set)
    contributor = contributor_set.min_by { |contributor| contributor.review_mappings.reject { |review_mapping| review_mapping.response.nil? }.count }
    min_reviews = contributor.review_mappings.reject! { |review_mapping| review_mapping.response.nil? }.count rescue 0
    contributor_set.reject { |contributor| contributor.review_mappings.reject { |review_mapping| review_mapping.response.nil? }.count  > min_reviews + review_topic_threshold }

    return contributor_set
  end

  def reject_previously_reviewed_submissions(contributor_set, reviewer)
    contributor_set = contributor_set.reject { |contributor| contributor.reviewed_by?(reviewer) }
    return contributor_set
  end

  def reject_own_submission(contributor_set, reviewer)
    contributor_set.reject! { |contributor| contributor.teams_users.find_by_user_id(reviewer.user_id) }
    return contributor_set
  end

  def reject_by_deadline(contributor_set)
    contributor_set.reject! { |contributor| contributor.assignment.get_current_stage(signed_up_topic(contributor).id) == 'Complete' or
        !contributor.assignment.review_allowed(signed_up_topic(contributor).id) }
    return contributor_set
  end

  def reject_by_no_topic_selection_or_no_submission(contributor_set)
    contributor_set.reject! { |contributor| signed_up_topic(contributor).nil? or !contributor.has_submissions? }
    return contributor_set
  end

  def has_topics?
    @has_topics ||= !sign_up_topics.empty?
  end

  #assign the reviewer to review the assignment_team's submission. Only used in the assignments that do not have any topic
  def assign_reviewer_dynamically_no_topic(reviewer, assignment_team)
    if assignment_team==nil
      raise "There is no submission right now. Come back later."<commit_msg>Issue fix: Reviewers should be able to take an artifact in round 2 which has already been reviewed in round 1 in "varying rubric" feature #491<commit_after>class Assignment < ActiveRecord::Base
  require 'analytic/assignment_analytic'
  include AssignmentAnalytic
  include DynamicReviewMapping
  has_paper_trail
  belongs_to :course
  belongs_to :wiki_type
  # wiki_type needs to be removed. When an assignment is created, it needs to
  # be created as an instance of a subclass of the Assignment (model) class;
  # then Rails will "automatically' set the type field to the value that
  # designates an assignment of the appropriate type.
  has_many :participants, :class_name => 'AssignmentParticipant', :foreign_key => 'parent_id'
  has_many :participant_review_mappings, :class_name => 'ParticipantReviewResponseMap', :through => :participants, :source => :review_mappings
  has_many :users, :through => :participants
  has_many :due_dates, :dependent => :destroy
  has_many :teams, :class_name => 'AssignmentTeam', :foreign_key => 'parent_id'
  has_many :team_review_mappings, :class_name => 'TeamReviewResponseMap', :through => :teams, :source => :review_mappings
  has_many :invitations, :class_name => 'Invitation', :foreign_key => 'assignment_id', :dependent => :destroy
  has_many :assignment_questionnaires,:dependent => :destroy
  has_many :questionnaires, :through => :assignment_questionnaires
  belongs_to :instructor, :class_name => 'User', :foreign_key => 'instructor_id'
  has_many :sign_up_topics, :foreign_key => 'assignment_id', :dependent => :destroy
  has_many :response_maps, :foreign_key => 'reviewed_object_id', :class_name => 'ResponseMap'
  has_one :assignment_node,:foreign_key => :node_object_id,:dependent => :destroy

  validates_presence_of :name
  validates_uniqueness_of :name, :scope => :course_id

  COMPLETE = 'Finished'
  WAITLIST = 'Waitlist open'

  REVIEW_QUESTIONNAIRES = {:author_feedback => 0, :metareview => 1, :review => 2, :teammate_review => 3}
  #  Review Strategy information.
  RS_INSTRUCTOR_SELECTED = 'Instructor-Selected'
  RS_STUDENT_SELECTED = 'Student-Selected'
  RS_AUTO_SELECTED = 'Auto-Selected'
  REVIEW_STRATEGIES = [RS_INSTRUCTOR_SELECTED, RS_STUDENT_SELECTED, RS_AUTO_SELECTED]

  DEFAULT_MAX_REVIEWERS = 3

  def questionnaires_with_questions
    questionnaires.includes(:questions).joins(:questions)
  end

  def team_assignment?
    true
  end

  def team_assignment
    team_assignment?
  end

  # Returns a set of topics that can be used for taking the quiz.
  # We choose the topics if one of its quiz submissions has been attempted the fewest times so far
  def candidate_topics_for_quiz
    return nil if sign_up_topics.empty?   # This is not a topic assignment
    contributor_set = Array.new(contributors)
    # Reject contributors that have not selected a topic, or have no submissions
    contributor_set.reject! { |contributor| signed_up_topic(contributor).nil? }
    #####contributor_set.reject! { |contributor| !contributor.has_quiz? }
    # Reject contributions of topics whose deadline has passed
    contributor_set.reject! { |contributor| contributor.assignment.get_current_stage(signed_up_topic(contributor).id) == "Complete" or
                              contributor.assignment.get_current_stage(signed_up_topic(contributor).id) == "submission" }

    # Filter the contributors with the least number of reviews
    # (using the fact that each contributor is associated with a topic)
    ###contributor = contributor_set.min_by { |contributor| contributor.quiz_mappings.count }

    ### min_quizzes = contributor.quiz_mappings.count rescue 0
    ###contributor_set.reject! { |contributor| contributor.quiz_mappings.count > min_quizzes + review_topic_threshold }


    candidate_topics = Set.new
    contributor_set.each { |contributor| candidate_topics.add(signed_up_topic(contributor)) }
    candidate_topics
  end


  # Returns a set of topics that can be reviewed.
  # We choose the topics if one of its submissions has received the fewest reviews so far
  def candidate_topics_to_review(reviewer)
    return nil if sign_up_topics.empty? # This is not a topic assignment

    # Initialize contributor set with all teams participating in this assignment
    contributor_set = Array.new(contributors)

 
    # Reject contributors that have not selected a topic, or have no submissions
    contributor_set=reject_by_no_topic_selection_or_no_submission(contributor_set)

    # Reject contributions of topics whose deadline has passed, or which are not reviewable in the current stage
    contributor_set=reject_by_deadline(contributor_set)


    # Filter submission by reviewer him/her self
    contributor_set=reject_own_submission(contributor_set, reviewer)

    if self.varying_rubrics_by_round?
      current_round = self.get_current_round(nil)
      contributor_set = reject__reviewed_submissions_in_current_round(contributor_set, reviewer,current_round)
    else
      # Filter submissions already reviewed by reviewer
      contributor_set=reject_previously_reviewed_submissions(contributor_set, reviewer)
    end


    # Filter the contributors with the least number of reviews
    # (using the fact that each contributor is associated with a topic)
    contributor_set=reject_by_least_reviewed(contributor_set)

    # Add topics for all remaining submissions to a list of available topics for review
    candidate_topics = Set.new
    contributor_set.each { |contributor|
      candidate_topics.add(signed_up_topic(contributor))
    }
    candidate_topics
  end

  #This method is only for the assignments without topics
  def candidate_assignment_teams_to_review

    contributor_set = Array.new(contributors)

    # Reject contributors that have no submissions
    contributor_set.reject! { |contributor| !contributor.has_submissions? }

    # Filter the contributors with the least number of reviews
    # (using the fact that each contributor is associated with a topic)
    contributor = contributor_set.min_by { |contributor| contributor.review_mappings.count }

    min_reviews = contributor.review_mappings.count rescue 0
    contributor_set.reject! { |contributor| contributor.review_mappings.count > min_reviews + review_topic_threshold }

    contributor_set
  end

  def reject_by_least_reviewed(contributor_set)
    contributor = contributor_set.min_by { |contributor| contributor.review_mappings.reject { |review_mapping| review_mapping.response.nil? }.count }
    min_reviews = contributor.review_mappings.reject! { |review_mapping| review_mapping.response.nil? }.count rescue 0
    contributor_set.reject { |contributor| contributor.review_mappings.reject { |review_mapping| review_mapping.response.nil? }.count  > min_reviews + review_topic_threshold }

    return contributor_set
  end

  def reject__reviewed_submissions_in_current_round(contributor_set, reviewer, current_round)
    contributor_set = contributor_set.reject { |contributor| contributor.reviewed_by_in_round?(reviewer,current_round) }
  end

  def reject_previously_reviewed_submissions(contributor_set, reviewer)
    contributor_set = contributor_set.reject { |contributor| contributor.reviewed_by?(reviewer) }
    return contributor_set
  end

  def reject_own_submission(contributor_set, reviewer)
    contributor_set.reject! { |contributor| contributor.teams_users.find_by_user_id(reviewer.user_id) }
    return contributor_set
  end

  def reject_by_deadline(contributor_set)
    contributor_set.reject! { |contributor| contributor.assignment.get_current_stage(signed_up_topic(contributor).id) == 'Complete' or
        !contributor.assignment.review_allowed(signed_up_topic(contributor).id) }
    return contributor_set
  end

  def reject_by_no_topic_selection_or_no_submission(contributor_set)
    contributor_set.reject! { |contributor| signed_up_topic(contributor).nil? or !contributor.has_submissions? }
    return contributor_set
  end

  def has_topics?
    @has_topics ||= !sign_up_topics.empty?
  end

  #assign the reviewer to review the assignment_team's submission. Only used in the assignments that do not have any topic
  def assign_reviewer_dynamically_no_topic(reviewer, assignment_team)
    if assignment_team==nil
      raise "There is no submission right now. Come back later."
{'fraction_non_alphanumeric': 0.073911955977989, 'fraction_numerical': 0.0013131565782891447, 'mean_word_length': 5.074060007595898, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 69, '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': '6504963', 'n_tokens_mistral': 4906, 'n_tokens_neox': 4650, 'n_words': 1238}