diff --git a/app/helpers/rollup_score_aggregator_helper.rb b/app/helpers/rollup_score_aggregator_helper.rb index f7a979ab95c..2a7c10f60b9 100644 --- a/app/helpers/rollup_score_aggregator_helper.rb +++ b/app/helpers/rollup_score_aggregator_helper.rb @@ -17,7 +17,9 @@ module RollupScoreAggregatorHelper def aggregate_score - (scores.sum.to_f / scores.size).round(2) + scores = score_sets.pluck(:score) + agg_score = (scores.sum.to_f / scores.size).round(2) + {score: agg_score, results: score_sets.pluck(:result)} end private @@ -42,7 +44,8 @@ module RollupScoreAggregatorHelper def retrieve_scores(results) results.map do |result| - quiz_score?(result) ? scaled_score_from_result(result) : result_score(result) + score = quiz_score?(result) ? scaled_score_from_result(result) : result_score(result) + {score: score, result: result} end end @@ -90,15 +93,15 @@ module RollupScoreAggregatorHelper end end - def scores - @scores || begin + def score_sets + @score_sets || begin case @calculation_method when 'decaying_average' - @scores = retrieve_scores(@aggregate ? @outcome_results : sorted_results) + @score_sets = retrieve_scores(@aggregate ? @outcome_results : sorted_results) when 'n_mastery', 'highest' - @scores = retrieve_scores(@outcome_results) + @score_sets = retrieve_scores(@outcome_results) when 'latest' - @scores = retrieve_scores(@aggregate ? @outcome_results : [sorted_results.last]) + @score_sets = retrieve_scores(@aggregate ? @outcome_results : [sorted_results.last]) end end end diff --git a/app/models/rollup_score.rb b/app/models/rollup_score.rb index 95a894e7fcd..d3bbe031a5d 100644 --- a/app/models/rollup_score.rb +++ b/app/models/rollup_score.rb @@ -20,7 +20,7 @@ class RollupScore PRECISION = 2 - attr_reader :outcome_results, :outcome, :score, :count, :title, :submitted_at + attr_reader :outcome_results, :outcome, :score, :count, :title, :submitted_at, :hide_points def initialize(outcome_results, opts={}) @outcome_results = outcome_results @aggregate = opts[:aggregate_score] @@ -30,7 +30,9 @@ class RollupScore @mastery_points = @outcome.rubric_criterion[:mastery_points] @calculation_method = @outcome.calculation_method || "highest" @calculation_int = @outcome.calculation_int - @score = @aggregate ? aggregate_score : calculate_results + score_set = @aggregate ? aggregate_score : calculate_results + @score = score_set[:score] if score_set + @hide_points = score_set[:results].all?(&:hide_points) if score_set latest_result unless @aggregate end @@ -40,42 +42,52 @@ class RollupScore case @calculation_method when 'decaying_average' return nil if @outcome_results.empty? - decaying_average + decaying_average_set when 'n_mastery' return nil if @outcome_results.length < @calculation_int - n_mastery + n_mastery_set when 'latest' - scores.first.round(PRECISION) + latest_set = score_sets.first + {score: latest_set[:score].round(PRECISION), results: [latest_set[:result]]} when 'highest' - scores.max.round(PRECISION) + highest_set = score_sets.max_by{|set| set[:score]} + {score: highest_set[:score].round(PRECISION), results: [highest_set[:result]]} end end - def n_mastery + def n_mastery_set return unless @outcome.rubric_criterion # mastery_points represents the cutoff score for which results # will be considered towards mastery - tmp_scores = scores.compact.delete_if{|score| score < @mastery_points} - return nil if tmp_scores.length < @calculation_int - (tmp_scores.sum.to_f / tmp_scores.size).round(PRECISION) + tmp_score_sets = score_sets.compact.delete_if{|set| set[:score] < @mastery_points} + return nil if tmp_score_sets.length < @calculation_int + + tmp_scores = tmp_score_sets.pluck(:score) + n_mastery_score = (tmp_scores.sum.to_f / tmp_scores.size).round(PRECISION) + {score: n_mastery_score, results: tmp_score_sets.pluck(:result)} end - def decaying_average + def decaying_average_set # The term "decaying average" can mean different things depending on the user. # There are multiple, reasonable, accurate interpretations. We have chosen # to go with one that is more mathematically a "weighted average", but is # typically what is meant when a "decaying average" is wanted. A true # decaying average may be added in the future. - #default grading method with weight of 65 if none selected. + # default grading method with weight of 65 if none selected. weight = @calculation_int || 65 - tmp_scores = scores - latest = tmp_scores.pop - return latest.round(PRECISION) if tmp_scores.empty? + tmp_score_sets = score_sets + latest = tmp_score_sets.pop - latest_weighted = latest * (0.01 * weight) + if tmp_score_sets.empty? + return { score: latest[:score].round(PRECISION), results: [latest[:result]] } + end + + tmp_scores = tmp_score_sets.pluck(:score) + latest_weighted = latest[:score] * (0.01 * weight) older_avg_weighted = (tmp_scores.sum / tmp_scores.length) * (0.01 * (100 - weight)) - (latest_weighted + older_avg_weighted).round(PRECISION) + decaying_avg_score = (latest_weighted + older_avg_weighted).round(PRECISION) + {score: decaying_avg_score, results: tmp_score_sets.pluck(:result).push(latest[:result])} end end diff --git a/lib/api/v1/outcome_results.rb b/lib/api/v1/outcome_results.rb index 4757e2d9e8c..26659e2723d 100644 --- a/lib/api/v1/outcome_results.rb +++ b/lib/api/v1/outcome_results.rb @@ -204,6 +204,7 @@ module Api::V1::OutcomeResults title: score.title, submitted_at: score.submitted_at, count: score.count, + hide_points: score.hide_points, links: {outcome: score.outcome.id.to_s}, } end diff --git a/lib/outcomes/result_analytics.rb b/lib/outcomes/result_analytics.rb index 382fcda3bc0..543bc9b2026 100644 --- a/lib/outcomes/result_analytics.rb +++ b/lib/outcomes/result_analytics.rb @@ -20,7 +20,7 @@ module Outcomes module ResultAnalytics Rollup = Struct.new(:context, :scores) - Result = Struct.new(:learning_outcome, :score, :count) + Result = Struct.new(:learning_outcome, :score, :count, :hide_points) # Public: Queries learning_outcome_results for rollup. # @@ -81,7 +81,7 @@ module Outcomes rollup_scores = rollups.map(&:scores).flatten outcome_results = rollup_scores.group_by(&:outcome).values aggregate_results = outcome_results.map do |scores| - scores.map{|score| Result.new(score.outcome, score.score, score.count)} + scores.map{|score| Result.new(score.outcome, score.score, score.count, score.hide_points)} end aggregate_rollups = aggregate_results.map do |result| RollupScore.new(result,{aggregate_score: true}) diff --git a/spec/apis/v1/outcome_results_api_spec.rb b/spec/apis/v1/outcome_results_api_spec.rb index f122fab71a6..03d910cf13b 100644 --- a/spec/apis/v1/outcome_results_api_spec.rb +++ b/spec/apis/v1/outcome_results_api_spec.rb @@ -248,7 +248,7 @@ describe "Outcome Results API", type: :request do expect(rollup['links']['user']).to eq outcome_student.id.to_s expect(rollup['scores'].size).to eq 1 rollup['scores'].each do |score| - expect(score.keys.sort).to eq %w(count links score submitted_at title) + expect(score.keys.sort).to eq %w(count hide_points links score submitted_at title) expect(score['count']).to eq 1 expect(score['score']).to eq first_outcome_rating[:points] expect(score['links'].keys.sort).to eq %w(outcome) @@ -283,7 +283,7 @@ describe "Outcome Results API", type: :request do expect(student_ids).to be_include(rollup['links']['user']) expect(rollup['scores'].size).to eq 1 rollup['scores'].each do |score| - expect(score.keys.sort).to eq %w(count links score submitted_at title) + expect(score.keys.sort).to eq %w(count hide_points links score submitted_at title) expect(score['count']).to eq 1 expect([0,1]).to be_include(score['score']) expect(score['links'].keys.sort).to eq %w(outcome) @@ -342,7 +342,7 @@ describe "Outcome Results API", type: :request do expect(outcome_course_sections[0].student_ids.map(&:to_s)).to be_include(rollup['links']['user']) expect(rollup['scores'].size).to eq 1 rollup['scores'].each do |score| - expect(score.keys.sort).to eq %w(count links score submitted_at title) + expect(score.keys.sort).to eq %w(count hide_points links score submitted_at title) expect(score['count']).to eq 1 expect([0,2]).to be_include(score['score']) expect(score['links'].keys.sort).to eq %w(outcome) @@ -491,7 +491,7 @@ describe "Outcome Results API", type: :request do rollup['links']['course'] == @course.id.to_s expect(rollup['scores'].size).to eq 1 rollup['scores'].each do |score| - expect(score.keys.sort).to eq %w(count links score submitted_at title) + expect(score.keys.sort).to eq %w(count hide_points links score submitted_at title) expect(score['count']).to eq 1 expect(score['score']).to eq first_outcome_rating[:points] expect(score['links'].keys.sort).to eq %w(outcome) @@ -516,7 +516,7 @@ describe "Outcome Results API", type: :request do expect(rollup['links']['course']).to eq @course.id.to_s expect(rollup['scores'].size).to eq 1 rollup['scores'].each do |score| - expect(score.keys.sort).to eq %w(count links score submitted_at title) + expect(score.keys.sort).to eq %w(count hide_points links score submitted_at title) expect(score['count']).to eq 2 expect(score['score']).to eq 0.5 expect(score['links'].keys.sort).to eq %w(outcome) @@ -541,7 +541,7 @@ describe "Outcome Results API", type: :request do expect(rollup['links']['course']).to eq outcome_course.id.to_s expect(rollup['scores'].size).to eq 1 rollup['scores'].each do |score| - expect(score.keys.sort).to eq %w(count links score submitted_at title) + expect(score.keys.sort).to eq %w(count hide_points links score submitted_at title) expect(score['count']).to eq outcome_course_sections[0].enrollments.count expect(score['score']).to eq 1 expect(score['links'].keys.sort).to eq %w(outcome) diff --git a/spec/lib/outcomes/result_analytics_spec.rb b/spec/lib/outcomes/result_analytics_spec.rb index 835fa98269e..0034c50f60b 100644 --- a/spec/lib/outcomes/result_analytics_spec.rb +++ b/spec/lib/outcomes/result_analytics_spec.rb @@ -29,7 +29,7 @@ describe Outcomes::ResultAnalytics do # the surrounding database logic MockUser = Struct.new(:id, :name) MockOutcome = Struct.new(:id, :calculation_method, :calculation_int, :rubric_criterion) - class MockOutcomeResult < Struct.new(:user, :learning_outcome, :score, :title, :submitted_at, :assessed_at, :artifact_type, :percent, :possible, :association_id, :association_type) + class MockOutcomeResult < Struct.new(:user, :learning_outcome, :score, :title, :submitted_at, :assessed_at, :hide_points, :artifact_type, :percent, :possible, :association_id, :association_type) def initialize *args return super unless (args.first.is_a?(Hash) && args.length == 1) args.first.each_pair do |k, v| @@ -50,7 +50,7 @@ describe Outcomes::ResultAnalytics do title = args[:title] || "name, o1" outcome = args[:outcome] || create_outcome(args) user = args[:user] || MockUser[10, 'a'] - MockOutcomeResult[user, outcome, score, title, args[:submitted_time], args[:assessed_time]] + MockOutcomeResult[user, outcome, score, title, args[:submitted_time], args[:assessed_time], args[:hide_points]] end def create_outcome(args) @@ -206,6 +206,24 @@ describe Outcomes::ResultAnalytics do expect(rollup.scores.map(&:outcome_results).flatten).to eq rollup_scores.find_all{|score| score.user.id == rollup.context.id} end end + + it 'returns hide_points value of true if all results have hide_points set to true' do + results = [ + outcome_from_score(4.0,{hide_points: true}), + outcome_from_score(5.0, {hide_points: true}), + ] + rollups = ra.rollup_user_results(results) + expect(rollups[0].hide_points).to be true + end + + it 'returns hide_points value of false if any results have hide_points set to false' do + results = [ + outcome_from_score(4.0,{hide_points: true}), + outcome_from_score(5.0,{hide_points: false}), + ] + rollups = ra.rollup_user_results(results) + expect(rollups[0].hide_points).to be false + end end describe '#aggregate_outcome_results_rollup' do