diff --git a/app/controllers/lottery_controller.rb b/app/controllers/lottery_controller.rb index 0deeceffbde..24a41468db2 100755 --- a/app/controllers/lottery_controller.rb +++ b/app/controllers/lottery_controller.rb @@ -1,14 +1,12 @@ class LotteryController < ApplicationController + include LotteryHelper include AuthorizationHelper - require 'json' require 'rest_client' - # Give permission to run the bid to appropriate roles def action_allowed? current_user_has_ta_privileges? end - # This method sends a request to a web service that uses k-means and students' bidding data # to build teams automatically. # The webservice tries to create teams with sizes close to the max team size @@ -16,14 +14,13 @@ def action_allowed? # that have similar bidding info/priorities associated with the assignment's sign-up topics. # # rubocop:disable Metrics/AbcSize + def run_intelligent_assignment assignment = Assignment.find(params[:id]) teams = assignment.teams - users_bidding_info = construct_users_bidding_info(assignment.sign_up_topics, teams) bidding_data = { users: users_bidding_info, max_team_size: assignment.max_team_size } ExpertizaLogger.info LoggerMessage.new(controller_name, session[:user].name, "Bidding data for assignment #{assignment.name}: #{bidding_data}", request) - begin url = WEBSERVICE_CONFIG['topic_bidding_webservice_url'] response = RestClient.post url, bidding_data.to_json, content_type: :json, accept: :json @@ -36,12 +33,70 @@ def run_intelligent_assignment rescue StandardError => e flash[:error] = e.message end - redirect_to controller: 'tree_display', action: 'list' end + # Prepares data for displaying the bidding details for each topic within an assignment. + # It calculates the number of bids for each priority (1, 2, 3) per topic and also computes + # the overall percentages of teams that received their first, second, and third choice. + # This method is responsible for calculating the bidding table data for an assignment. + def calculate_bidding_summary_based_on_priority + @assignment = Assignment.find(params[:id]) + @topics = @assignment.sign_up_topics.includes(:bids) + @topic_data = @topics.map do |topic| + # Count the total number of bids for the topic. + total_bids = topic.bids.count + # Count the number of first, second, and third priority bids. + first_bids = topic.bids.where(priority: 1).count + second_bids = topic.bids.where(priority: 2).count + third_bids = topic.bids.where(priority: 3).count + # Extract the team names for the bids. + bidding_teams = topic.bids.includes(:team).map { |bid| bid.team.name } + + # Calculate the percentage of first priority bids. + percentage_first = if total_bids > 0 + # If there are any bids, calculate the percentage. + (first_bids.to_f / total_bids * 100).round(2) + else + # If there are no bids, the percentage is 0. + 0 + end + # Return a hash containing all the calculated and retrieved data for the topic. + { + id: topic.id, + name: topic.topic_name, + first_bids: first_bids, + second_bids: second_bids, + third_bids: third_bids, + total_bids: total_bids, + percentage_first: percentage_first, + bidding_teams: bidding_teams + } + end + end + private + # Computes the count of assigned teams for each priority level (1, 2, 3) across all topics. + # It checks each team associated with a topic and determines if the team's bid matches + # one of the priority levels, incrementing the respective count if so. + def compute_priority_counts(assigned_teams_by_topic, bids_by_topic) + priority_counts = { 1 => 0, 2 => 0, 3 => 0 } + assigned_teams_by_topic.each do |topic_id, teams| + teams.each do |team| + bid_info = bids_by_topic[topic_id].find { |bid| bid[:team] == team } + priority_counts[bid_info[:priority]] += 1 if bid_info + end + end + priority_counts + end + + # Calculates the percentages of teams that received their first, second, and third choice + # based on the counts of teams at each priority level. + def compute_percentages(priority_counts, total_teams) + priority_counts.transform_values { |count| (count.to_f / total_teams * 100).round(2) } + end + # Generate user bidding information hash based on students who haven't signed up yet # This associates a list of bids corresponding to sign_up_topics to a user # Structure of users_bidding_info variable: [{user_id1, bids_1}, {user_id2, bids_2}] @@ -84,11 +139,7 @@ def construct_teams_bidding_info(unassigned_teams, sign_up_topics) # teams def create_new_teams_for_bidding_response(teams, assignment, users_bidding_info) teams.each do |user_ids| - if assignment.auto_assign_mentor - new_team = MentoredTeam.create_team_with_users(assignment.id, user_ids) - else - new_team = AssignmentTeam.create_team_with_users(assignment.id, user_ids) - end + new_team = AssignmentTeam.create_team_with_users(assignment.id, user_ids) # Select data from `users_bidding_info` variable that only related to team members in current team current_team_members_info = users_bidding_info.select { |info| user_ids.include? info[:pid] }.map { |info| info[:ranks] } Bid.merge_bids_from_different_users(new_team.id, assignment.sign_up_topics, current_team_members_info) @@ -102,12 +153,8 @@ def assign_available_slots(teams_bidding_info) teams_bidding_info.each do |tb| tb[:bids].each do |bid| topic_id = bid[:topic_id] - num_of_signed_up_teams = SignedUpTeam.where(topic_id: topic_id).count - max_choosers = SignUpTopic.find(bid[:topic_id]).try(:max_choosers) - if num_of_signed_up_teams < max_choosers - SignedUpTeam.create(team_id: tb[:team_id], topic_id: bid[:topic_id]) - break - end + max_choosers = SignUpTopic.find(topic_id).try(:max_choosers) + SignedUpTeam.create(team_id: tb[:team_id], topic_id: topic_id) if SignedUpTeam.where(topic_id: topic_id).count < max_choosers end end end @@ -124,7 +171,6 @@ def match_new_teams_to_topics(assignment) unassigned_teams = assignment.teams.reload.select do |t| SignedUpTeam.where(team_id: t.id, is_waitlisted: 0).blank? && Bid.where(team_id: t.id).any? end - # Sorting unassigned_teams by team size desc, number of bids in current team asc # again, we need to find a way to to merge bids that came from different previous teams # then sorting unassigned_teams by number of bids in current team (less is better) @@ -134,10 +180,8 @@ def match_new_teams_to_topics(assignment) [TeamsUser.where(team_id: t2.id).size, Bid.where(team_id: t1.id).size] <=> [TeamsUser.where(team_id: t1.id).size, Bid.where(team_id: t2.id).size] end - teams_bidding_info = construct_teams_bidding_info(unassigned_teams, sign_up_topics) assign_available_slots(teams_bidding_info) - # Remove is_intelligent property from assignment so that it can revert to the default sign-up state assignment.update_attributes(is_intelligent: false) flash[:success] = 'The intelligent assignment was successfully completed for ' + assignment.name + '.' diff --git a/app/helpers/lottery_helper.rb b/app/helpers/lottery_helper.rb index cb8b6b6fb52..a5db2b2c76f 100644 --- a/app/helpers/lottery_helper.rb +++ b/app/helpers/lottery_helper.rb @@ -1,2 +1,14 @@ module LotteryHelper + def background_color_by_percentage(percentage) + case percentage + when 0...33 + 'background-color: #ffcccc;' # Light red for low percentages + when 33...66 + 'background-color: #ffcc99;' # Light orange for medium percentages + when 66..100 + 'background-color: #ccffcc;' # Light green for high percentages + else + 'background-color: none;' # No background if outside range + end + end end diff --git a/app/views/assignments/edit/_topics.html.erb b/app/views/assignments/edit/_topics.html.erb index 288ada6b4a6..cce284e3236 100644 --- a/app/views/assignments/edit/_topics.html.erb +++ b/app/views/assignments/edit/_topics.html.erb @@ -1,15 +1,37 @@

Topics for <%= @assignment_form.assignment.name %> assignment


+ + <%= check_box_tag('assignment_form[assignment][allow_suggestions]', 'true', @assignment_form.assignment.allow_suggestions) %> <%= label_tag('assignment_form[assignment][allow_suggestions]', 'Allow topic suggestions from students?') %>
-<%= check_box_tag('assignment_form[assignment][is_intelligent]', 'true', @assignment_form.assignment.is_intelligent?)%> +<%= check_box_tag('assignment_form[assignment][is_intelligent]', 'true', @assignment_form.assignment.is_intelligent?, id: 'enable-bidding-checkbox')%> <%= label_tag('assignment_form[assignment][is_intelligent]', 'Enable bidding for topics?') %> -
+ +<%= button_to 'Show bids by priority', {:controller => 'lottery', :action => 'calculate_bidding_summary_based_on_priority', :id => @assignment_form.assignment.id}, :method => :get, class: 'custom-button', id: 'bidding-button' %> + <%= check_box_tag('assignment_form[assignment][can_review_same_topic]', 'true', @assignment_form.assignment.can_review_same_topic?)%> <%= label_tag('assignment_form[assignment][can_review_same_topic]', 'Enable authors to review others working on same topic?') %> diff --git a/app/views/lottery/calculate_bidding_summary_based_on_priority.html.erb b/app/views/lottery/calculate_bidding_summary_based_on_priority.html.erb new file mode 100644 index 00000000000..ec74bd7822b --- /dev/null +++ b/app/views/lottery/calculate_bidding_summary_based_on_priority.html.erb @@ -0,0 +1,50 @@ + +

Assignment Bidding Summary by Priority: <%= @assignment.name %>

+ + + + + + + + + + + + + + + + + + + + + <% @topic_data.each do |topic| %> + + + + + + + + + + + + + + <% end %> + +
Topic idTopic name#1 bids#2 bids#3 bidsTotal bidsPercentage of #1 bidsBidding teams
<%= topic[:id] %><%= topic[:name] %><%= topic[:first_bids] %><%= topic[:second_bids] %><%= topic[:third_bids] %><%= topic[:total_bids] %> + + <%= topic[:percentage_first] %> % + + + <% topic[:bidding_teams].each do |team| %> + <%= team %>
+ <% end %> +
+ + +<%= link_to 'Back', :back %> diff --git a/config/routes.rb b/config/routes.rb index c2ae428631d..899361fa183 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -665,4 +665,7 @@ post 'student_task/publishing_rights_update', controller: :student_task, action: :publishing_rights_update, method: :put get 'student_view/flip_view', controller: :student_view, action: :flip_view # updated route and added specific controller action upon accessing this route + # Add a new route for Calculating bidding summary based on priority + get 'lottery/run_intelligent_assignment/:id', to: 'lottery#run_intelligent_assignment', as: 'run_intelligent_assignment' + get 'assignments/:id/calculate_bidding_summary_based_on_priority', to: 'lottery#calculate_bidding_summary_based_on_priority', as: 'calculate_bidding_summary_based_on_priority' end diff --git a/spec/controllers/lottery_controller_spec.rb b/spec/controllers/lottery_controller_spec.rb index 07865b36c67..b816fad8ec2 100644 --- a/spec/controllers/lottery_controller_spec.rb +++ b/spec/controllers/lottery_controller_spec.rb @@ -72,6 +72,44 @@ end end + describe '#calculate_bidding_summary_based_on_priority' do + it 'calculates and returns bidding summary data for topics' do + # Setup test data + # Instead of using assignment: assignment, use assignment_id: assignment.id to directly set the foreign key + team = create(:team, assignment_id: assignment.id) + bid = create(:bid, topic: topic1, team: team, priority: 1) + team_name = create(:team_name, team: team) + + allow(Assignment).to receive(:find).with(assignment.id).and_return(assignment) + allow(assignment).to receive(:sign_up_topics).and_return([topic1]) + allow(topic1).to receive_message_chain(:bids, :includes).and_return([bid]) + allow(bid).to receive_message_chain(:team, :name).and_return(team_name) + + # Mock params + params = { id: assignment.id } + allow(controller).to receive(:params).and_return(params) + + # Expected data structure from calculate_bidding_summary_based_on_priority + expected_topic_data = [ + { + id: topic1.id, + name: topic1.topic_name, + first_bids: 1, + second_bids: 0, + third_bids: 0, + total_bids: 1, + percentage_first: 100.0, + bidding_teams: [team_name.name] + } + ] + + # Call the method + controller.instance_variable_set(:@assignment, assignment) + expect(controller.calculate_bidding_summary_based_on_priority).to eq(expected_topic_data) + end +end + + describe '#construct_users_bidding_info' do it 'generate users bidding information hash' do # Only members in assignment_team1 and assignment_team2 are involved in the bidding process diff --git a/spec/helpers/lottery_helper_spec.rb b/spec/helpers/lottery_helper_spec.rb new file mode 100644 index 00000000000..511d949361e --- /dev/null +++ b/spec/helpers/lottery_helper_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +# Assuming your helper module is in the helpers folder +RSpec.describe LotteryHelper, type: :helper do + # Test for low percentage range + describe '#background_color_by_percentage' do + it 'returns light red for low percentages' do + expect(helper.background_color_by_percentage(10)).to eq('background-color: #ffcccc;') + end + + # Test for medium percentage range + it 'returns light orange for medium percentages' do + expect(helper.background_color_by_percentage(50)).to eq('background-color: #ffcc99;') + end + + # Test for high percentage range + it 'returns light green for high percentages' do + expect(helper.background_color_by_percentage(80)).to eq('background-color: #ccffcc;') + end + + # Test for percentage out of range + it 'returns no background for percentages out of range' do + expect(helper.background_color_by_percentage(101)).to eq('background-color: none;') + expect(helper.background_color_by_percentage(-1)).to eq('background-color: none;') + end + end +end