add hide_points to json response on lmgb

closes OUT-2300

test plan (dev-qa):
- create a new outcome
- create a new rubric and align the outcome
- align the rubric to a new assignment, ensure you
  choose the box to remove points from rubric
- assess a student
- go to the LMGB
- the outcome_rollups response should include a
  hide_points field in the json
- hide_points should be 'true' on the rollup for the new outcome
  with the newly created outcome
- attach the newly create outcome to a different assignment, one
  that doesn't have its points hidden on the rubric association
- assess the student
- return to lmgb
- hide_points should be 'false' for the rollup on the new outcome

Change-Id: I0f9accb4ff0cc76f40c08ce08e21091e35df6af8
Reviewed-on: https://gerrit.instructure.com/153547
Tested-by: Jenkins
Reviewed-by: Michael Brewer-Davis <mbd@instructure.com>
Reviewed-by: Augusto Callejas <acallejas@instructure.com>
Product-Review: Michael Brewer-Davis <mbd@instructure.com>
QA-Review: Michael Brewer-Davis <mbd@instructure.com>
This commit is contained in:
Matthew Berns 2018-06-12 15:18:09 -05:00 committed by Matt Berns
parent cb73718207
commit e924133cb8
6 changed files with 68 additions and 34 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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})

View File

@ -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)

View File

@ -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