User:Akumarm: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
(Created page with "== Fallback Algorithm == === ''Introduction'' === The Fallback Algorithm in Expertiza is designed to ensure automatic topic assignment when the primary Bidding Algorithm fails due to Web Service unavailability. Normally, Expertiza allows reviewers to bid on topics, and the ''ReviewBiddingAlgorithmService'' processes these bids to assign topics. However, when this web service ''fails'' (due to API downtime, server issues, or unexpected errors), the system must swi...")
 
No edit summary
Line 31: Line 31:
If the primary bidding service fails, the system catches the error and calls the Fallback Algorithm.
If the primary bidding service fails, the system catches the error and calls the Fallback Algorithm.


```
    def assign_bidding
def assign_bidding
      assignment_id = params[:assignment_id].to_i
    assignment_id = params[:assignment_id].to_i
      reviewer_ids = AssignmentParticipant.where(parent_id: assignment_id).ids
    reviewer_ids = AssignmentParticipant.where(parent_id: assignment_id).ids
    
    
    begin
      begin
      # Attempt to use the web service
        # Attempt to use the web service
      bidding_data = ReviewBid.bidding_data(assignment_id, reviewer_ids)
        bidding_data = ReviewBid.bidding_data(assignment_id, reviewer_ids)
      matched_topics = ReviewBiddingAlgorithmService.run_bidding_algorithm(bidding_data)
        matched_topics = ReviewBiddingAlgorithmService.run_bidding_algorithm(bidding_data)
    rescue StandardError => e
      rescue StandardError => e
      # If web service fails, trigger fallback
        # If web service fails, trigger fallback
      Rails.logger.error "Web service unavailable: #{e.message}"
        Rails.logger.error "Web service unavailable: #{e.message}"
      matched_topics = ReviewBid.fallback_algorithm(assignment_id, reviewer_ids)
        matched_topics = ReviewBid.fallback_algorithm(assignment_id, reviewer_ids)
    end
      end
    
    
    # Ensure valid topic assignments
      # Ensure valid topic assignments
    matched_topics ||= {}
      matched_topics ||= {}
    reviewer_ids.each { |reviewer_id| matched_topics[reviewer_id.to_s] ||= [] }
      reviewer_ids.each { |reviewer_id| matched_topics[reviewer_id.to_s] ||= [] }
    
    
    Rails.logger.debug "Final matched topics after fallback: #{matched_topics.inspect}"
      Rails.logger.debug "Final matched topics after fallback: #{matched_topics.inspect}"
      
      
    # Assign topics to reviewers
      # Assign topics to reviewers
    ReviewBid.assign_review_topics(assignment_id, reviewer_ids, matched_topics)
      ReviewBid.assign_review_topics(assignment_id, reviewer_ids, matched_topics)
    
    
    # Disable topic selection after assignment
      # Disable topic selection after assignment
    Assignment.find(assignment_id).update(can_choose_topic_to_review: false)
      Assignment.find(assignment_id).update(can_choose_topic_to_review: false)
    
    
    redirect_back fallback_location: root_path
      redirect_back fallback_location: root_path
end
  end
```
📌 **Key Takeaway:** If `ReviewBiddingAlgorithmService` fails, the system **automatically** calls `fallback_algorithm`. 


---
Key Takeaway: If ''ReviewBiddingAlgorithmService'' fails, the system automatically calls fallback_algorithm. 


### **📍 Model Code (Fallback Algorithm)**
===  ''Implementation - Model Code (Fallback Algorithm)''  ===
This method ensures **fair topic assignment** when the bidding system fails.
This method ensures fair topic assignment when the bidding system fails.


```ruby
  def fallback_algorithm(assignment_id, reviewer_ids)
def fallback_algorithm(assignment_id, reviewer_ids)
     Rails.logger.debug "Fallback algorithm triggered for assignment_id: #{assignment_id}"
     Rails.logger.debug "Fallback algorithm triggered for assignment_id: #{assignment_id}"
     matched_topics = {}
     matched_topics = {}
     # Step 1: Get available topics
     # Step 1: Get available topics
     topics = SignUpTopic.where(assignment_id: assignment_id).pluck(:id)
     topics = SignUpTopic.where(assignment_id: assignment_id).pluck(:id)
     Rails.logger.debug "Available topics: #{topics}"
     Rails.logger.debug "Available topics: #{topics}"
     # Step 2: Get team sizes and sort by largest teams first
     # Step 2: Get team sizes and sort by largest teams first
     teams = SignedUpTeam.where(topic_id: topics)
     teams = SignedUpTeam.where(topic_id: topics)
Line 84: Line 77:
                         .group(:topic_id)
                         .group(:topic_id)
                         .count("teams_users.user_id")
                         .count("teams_users.user_id")
     # Sort teams by size (Descending Order)
     # Sort teams by size (Descending Order)
     sorted_teams = teams.sort_by { |_, count| -count }
     sorted_teams = teams.sort_by { |_, count| -count }
     Rails.logger.debug "Teams sorted by size: #{sorted_teams}"
     Rails.logger.debug "Teams sorted by size: #{sorted_teams}"
     # Step 3: Create topic queue (largest teams first)
     # Step 3: Create topic queue (largest teams first)
     topic_queue = sorted_teams.map(&:first)
     topic_queue = sorted_teams.map(&:first)
     # Step 4: Assign topics in a round-robin manner
     # Step 4: Assign topics in a round-robin manner
     topic_index = 0
     topic_index = 0
Line 98: Line 87:
         assigned_topic = nil
         assigned_topic = nil
         self_topic = fetch_self_topic(assignment_id, reviewer_id)
         self_topic = fetch_self_topic(assignment_id, reviewer_id)
         attempts = 0
         attempts = 0
         while assigned_topic.nil? && attempts < topic_queue.size
         while assigned_topic.nil? && attempts < topic_queue.size
Line 109: Line 97:
             attempts += 1
             attempts += 1
         end
         end
         matched_topics[reviewer_id.to_s] = assigned_topic ? [assigned_topic] : []
         matched_topics[reviewer_id.to_s] = assigned_topic ? [assigned_topic] : []
     end
     end
     Rails.logger.debug "Final matched topics after fallback: #{matched_topics.inspect}"
     Rails.logger.debug "Final matched topics after fallback: #{matched_topics.inspect}"
     matched_topics
     matched_topics
end
  end
```
 
📌 **Key Takeaway:** This function ensures **every reviewer receives a topic** without requiring manual intervention.
Key Takeaway: This function ensures every reviewer receives a topic without requiring manual intervention.


---


## **5. Example**
## **5. Example**

Revision as of 21:16, 24 March 2025

Fallback Algorithm

Introduction

The Fallback Algorithm in Expertiza is designed to ensure automatic topic assignment when the primary Bidding Algorithm fails due to Web Service unavailability.

Normally, Expertiza allows reviewers to bid on topics, and the ReviewBiddingAlgorithmService processes these bids to assign topics. However, when this web service fails (due to API downtime, server issues, or unexpected errors), the system must switch to fallback and automatically assign topics to reviewers.

Solution

It is triggered when the web service is unavailable. It ensures fair topic assignment by:

  • Prioritizing topics with the largest teams.
  • Using a round-robin approach to distribute topics among reviewers.
  • Ensuring reviewers do not get their own team’s topic.

When is this Used?

  • The ReviewBiddingAlgorithmService fails due to an API issue.
  • The web service times out or returns an error.
  • Unexpected exceptions occur in the bidding process.

Instead of stopping the review process, the system automatically falls-back to an internal algorithm to distribute topics.

How Does the Fallback Algorithm Work?

The algorithm follows these **four main steps:

  • Fetch available topics – Retrieves all topics for the given assignment.
  • Sort topics by team size – Topics with more members are given priority.
  • Create a topic queue – Topics are arranged in descending order of team size.
  • Assign topics in a round-robin manner – Reviewers are assigned topics while avoiding their own team's topic.

This ensures fair and balanced distribution of topics.

Implementation - Controller Code (Triggering the Fallback)

If the primary bidding service fails, the system catches the error and calls the Fallback Algorithm.

   def assign_bidding
     assignment_id = params[:assignment_id].to_i
     reviewer_ids = AssignmentParticipant.where(parent_id: assignment_id).ids
 
     begin
       # Attempt to use the web service
       bidding_data = ReviewBid.bidding_data(assignment_id, reviewer_ids)
       matched_topics = ReviewBiddingAlgorithmService.run_bidding_algorithm(bidding_data)
     rescue StandardError => e
       # If web service fails, trigger fallback
       Rails.logger.error "Web service unavailable: #{e.message}"
       matched_topics = ReviewBid.fallback_algorithm(assignment_id, reviewer_ids)
     end
 
     # Ensure valid topic assignments
     matched_topics ||= {}
     reviewer_ids.each { |reviewer_id| matched_topics[reviewer_id.to_s] ||= [] }
 
     Rails.logger.debug "Final matched topics after fallback: #{matched_topics.inspect}"
   
     # Assign topics to reviewers
     ReviewBid.assign_review_topics(assignment_id, reviewer_ids, matched_topics)
 
     # Disable topic selection after assignment
     Assignment.find(assignment_id).update(can_choose_topic_to_review: false)
 
     redirect_back fallback_location: root_path
 end

Key Takeaway: If ReviewBiddingAlgorithmService fails, the system automatically calls fallback_algorithm.

Implementation - Model Code (Fallback Algorithm)

This method ensures fair topic assignment when the bidding system fails.

 def fallback_algorithm(assignment_id, reviewer_ids)
   Rails.logger.debug "Fallback algorithm triggered for assignment_id: #{assignment_id}"
   matched_topics = {}
   # Step 1: Get available topics
   topics = SignUpTopic.where(assignment_id: assignment_id).pluck(:id)
   Rails.logger.debug "Available topics: #{topics}"
   # Step 2: Get team sizes and sort by largest teams first
   teams = SignedUpTeam.where(topic_id: topics)
                       .joins(:team)
                       .joins("LEFT JOIN teams_users ON teams.id = teams_users.team_id")
                       .group(:topic_id)
                       .count("teams_users.user_id")
   # Sort teams by size (Descending Order)
   sorted_teams = teams.sort_by { |_, count| -count }
   Rails.logger.debug "Teams sorted by size: #{sorted_teams}"
   # Step 3: Create topic queue (largest teams first)
   topic_queue = sorted_teams.map(&:first)
   # Step 4: Assign topics in a round-robin manner
   topic_index = 0
   reviewer_ids.each do |reviewer_id|
       assigned_topic = nil
       self_topic = fetch_self_topic(assignment_id, reviewer_id)
       attempts = 0
       while assigned_topic.nil? && attempts < topic_queue.size
           topic_id = topic_queue[topic_index % topic_queue.size]
           unless topic_id == self_topic
               assigned_topic = topic_id
               Rails.logger.debug "Assigned topic #{assigned_topic} to reviewer #{reviewer_id}"
               topic_index += 1
           end
           attempts += 1
       end
       matched_topics[reviewer_id.to_s] = assigned_topic ? [assigned_topic] : []
   end
   Rails.logger.debug "Final matched topics after fallback: #{matched_topics.inspect}"
   matched_topics
 end

Key Takeaway: This function ensures every reviewer receives a topic without requiring manual intervention.


    1. **5. Example**
      1. **Scenario:**

- There are **3 reviewers**: `R1`, `R2`, `R3`. - There are **3 topics**: `T1`, `T2`, `T3`. - `T1` has the **largest team size**, so it is prioritized. - `R1` is on `T1`'s team, so they **cannot review `T1`**.

    • Topic Assignments Using Fallback:**

| Reviewer | Assigned Topic | |----------|---------------| | `R1` | `T2` | | `R2` | `T3` | | `R3` | `T1` |

The algorithm ensures: ✅ Topics with **larger teams** are assigned first. ✅ Reviewers **never get their own team's topic**. ✅ Topics are distributed **fairly using round-robin**.

---

    1. **6. Testing the Fallback Algorithm**
      1. **RSpec Test Cases**

To verify correctness, we use **unit tests** in `review_bid_spec.rb`:

```ruby describe '.fallback_algorithm' do

 let(:assignment_id) { 2085 }
 let(:reviewer_ids) { [1, 2, 3] }
 let(:topics) { [101, 102, 103] }
 let(:teams) { { 101 => 5, 102 => 3, 103 => 1 } }
 before do
   allow(SignUpTopic).to receive(:where).with(assignment_id: assignment_id).and_return(double(pluck: topics))
   allow(SignedUpTeam).to receive_message_chain(:where, :joins, :group, :count).and_return(teams)
   allow(SignedUpTeam).to receive(:topic_id).with(assignment_id, 1).and_return(101)
   allow(SignedUpTeam).to receive(:topic_id).with(assignment_id, 2).and_return(102)
   allow(SignedUpTeam).to receive(:topic_id).with(assignment_id, 3).and_return(nil)
 end
 it 'assigns topics in a round-robin manner while avoiding self-assignment' do
   result = ReviewBid.fallback_algorithm(assignment_id, reviewer_ids)
   expect(result['1']).not_to include(101)
   expect(result['2']).not_to include(102)
   expect([101, 102, 103]).to include(result['3'].first)
 end

end ```

---

    1. **7. Conclusion**

The **Fallback Algorithm** ensures that **topics are assigned fairly** even when the primary bidding service fails. By prioritizing **larger teams**, avoiding **self-review**, and using a **round-robin strategy**, the fallback mechanism keeps the **review process smooth and automatic**.