canvas-lms/app/models/learning_outcome_result.rb

181 lines
6.4 KiB
Ruby
Raw Normal View History

2011-02-01 09:57:29 +08:00
#
# Copyright (C) 2011 - present Instructure, Inc.
2011-02-01 09:57:29 +08:00
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class LearningOutcomeResult < ActiveRecord::Base
belongs_to :user
belongs_to :learning_outcome
learning outcomes refactor This list is *NOT* complete, some items may have snuck in that I forgot to note, and/or some of the noted items may not be completely functional yet. Specs need to be written around a lot of this, other specs will no doubt need to be fixed. Some things, particularly around LearningOutcomeGroups will need data migrations that aren't there yet. * remove LearningOutcome.non_rubric_outcomes? and replace with false where invoked * remove LearningOutcome.enabled? and replace with true where invoked * remove never-taken branches * remove the shared/aligned_outcomes partial and it's supporting javascript, since it's now empty * remove js handler for add_outcome_alignment_link and supporting method since it only occurred in never-taken branches * mix LearningOutcomeContext into Course and Account * replace LearningOutcomeGroup.default_for(context) with LearningOutcomeContext#root_outcome_group * rename LearningOutcome#content_tags to LearningOutcome#alignments * rename LearningOutcomeGroup#content_tags to LearningOutcomeGroup#child_links, and properly restrict * remove ContentTag[Alignment]#rubric_association_id, add ContentTag[Alignment]#has_rubric_association? that looks at the presence of the content's rubric_association_id * condition off the assignment having a rubric_association rather than filtering tags by has_rubric_association (which just looks back at the assignment). all or none of the assignment's alignments are forced to have the association (via the assignment). this was true in practice before, is now codified (and more efficient) * rename AssessmentQuestionBank#learning_outcome_tags to AssessmentQuestionBank#learning_outcome_alignments * rename Assignment#learning_outcome_tags to Assignment#learning_outcome_alignments * rename Rubric#learning_outcome_tags to Rubric#learning_outcome_alignments * move/rename (Course|Account)#learning_outcome_tags to LearningOutcomeContext#learning_outcome_links * move/rename Account#learning_outcomes (corrected) and Course#learning_outcomes to LearningOutcomeContext#linked_learning_outcomes * move/rename Account#created_learning_outcomes and Course#created_learning_outcomes to LearningOutcomeContext#created_learning_outcomes * clarify and correct usage of linked_learning_outcomes vs. created_learning_outcomes * move/rename (Account|Account)#learning_outcome_groups to LearningOutcomeContext#learning_outcome_groups * remove unused Account#associated_learning_outcomes * just remove one link to a learning outcome when deleting * merge Account#has_outcomes?, Course#has_outcomes? and Course#has_outcomes into LearningOutcomeContext#has_outcomes?, add a use in Context#active_record_types * kill LearningOutcomeGroup#root_learning_outcome_group (unused) * rename LearningOutcomeResult#content_tag to LearningOutcomeResult#alignment * kill unused (and broken) OutcomesController#add_outcome_group * kill unused OutcomesController#update_outcomes_for_asset * kill unused OutcomesController#outcomes_for_asset * remove unused (outside specs, correct specs) AssessmentQuestionBank#outcomes= * remove unused ContentTag#learning_outcome_content * replace ContentTag.learning_outcome_tags_for(asset) (only ever called with asset=an assignment) with call to Assignment#learning_outcome_alignments * remove unused ContentTag.not_rubric * remove (now) unused ContentTag.include_outcome * remove unused LearningOutcome#learning_outcome_group_associations * avoid explicit use of ContentTag in outcome-related specs * replace LearningOutcomeGroup#learning_outcome_tags with LearningOutcomeGroup#child_outcome_links (and only use for outcome links; not tags for child groups) * split ContentTag#create_outcome_result into Submission#create_outcome_result, QuizSubmission#create_outcome_result, and RubricAssessment#create_outcome_result. fix some bugs along the way * refactor ContentTag.outcome_tags_for_banks and some code from QuizSubmission#(track_outcomes|update_outcomes_for_assessment_questions) into QuizSubmission#questions_and_alignments * refactor RubricAssociation#update_outcome_relations and Rubric#update_alignments into LearningOutcome.update_alignments * don't use ContentTag#rubric_association with outcome alignments; use the tag's content's rubric_association in its place (they should have been equal anyways) * refactor LearningOutcome.available_in_context and @context.root_outcome_group.sorted_all_outcomes (only time sorted_all_outcomes is used) into LearningOutcomeContext#available_outcomes and LearningOutcomeContext#available_outcome * overhaul LearningOutcomeGroup#sorted_content and rename to LearningOutcomeGroup#sorted_children. it not returns ContentTags (outcome links) and LearningOutcomeGroups, vs. LearningOutcomes and LearningOutcomeGroups; fix usages appropriately * fix UI for arranging/deleting outcome links and groups within a group to refer to the outcome link rather than the outcome Change-Id: I85d99f2634f7206332cb1f5d5ea575b428988d4b Reviewed-on: https://gerrit.instructure.com/12590 Reviewed-by: Jacob Fugal <jacob@instructure.com> Tested-by: Jacob Fugal <jacob@instructure.com>
2012-07-13 01:16:13 +08:00
belongs_to :alignment, :class_name => 'ContentTag', :foreign_key => :content_tag_id
belongs_to :association_object, polymorphic:
[:rubric_association, :assignment,
{ quiz: 'Quizzes::Quiz', assessment: 'LiveAssessments::Assessment' }],
polymorphic_prefix: :association,
foreign_type: :association_type, foreign_key: :association_id
belongs_to :artifact, polymorphic:
[:rubric_assessment, :submission,
{ quiz_submission: 'Quizzes::QuizSubmission', live_assessments_submission: 'LiveAssessments::Submission' }],
polymorphic_prefix: true
belongs_to :associated_asset, polymorphic:
[:assessment_question, :assignment,
{ quiz: 'Quizzes::Quiz', assessment: 'LiveAssessments::Assessment' }],
polymorphic_prefix: true
belongs_to :context, polymorphic: [:course]
has_many :learning_outcome_question_results, dependent: :destroy
2011-02-01 09:57:29 +08:00
simply_versioned
2011-02-01 09:57:29 +08:00
before_save :infer_defaults
def calculate_percent!
scale_data = scale_params
if needs_scale?(scale_data) && self.score && self.possible
correct score display in LMGB when points possible is 0 fixes OUT-1598 note: It's important to take the quiz after the assignment, per the test plan, in order to properly test out the new error message noted below test plan: - create an outcome and set its total points possible to zero (easiest outcome to work with will have 'latest' as its scoring method - create a question bank and align the outcome (for ease of math, make bank with 10 total points) - create a rubric and align the outcome - create a quiz and align the bank to the quiz (enable multiple attempts for quiz and set method to 'latest' for ease of testing) - create an assignment and align the rubric to the assignment - as the teacher, provide a score for the outcome on the rubric in speedgrader - go to the LMGB, whatever score entered for the rubric should be shown as the outcome's score - as a student, score 70% on the quiz - return to the LMGB as a teacher, the score shown for the outcome should be 2.1/3 - go to the screenreader gradebook (gradebook individual view) - after selecting the proper student and outcome, results shown should match with what's on the standard gradebook - go to the student view of the lmgb - click on the outcome to see the list of alignments - the scores shown on the modal should match with what's shown in the teacher lmgb - open a rails console and do the following: lor=LearningOutcomeResult.last original_percent = lor.percent lor.update_column('percent', nil) - attempt to load the lmgb, you should get an error indication: "There was an error fetching outcome results" - return to your console - do lor.update_column('percent', original_percent) - the lmgb should load fine again Change-Id: I7f9084682eba5c9af3181d0d04c4ed8fc4bd3748 Reviewed-on: https://gerrit.instructure.com/135090 Tested-by: Jenkins Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> Reviewed-by: Neil Gupta <ngupta@instructure.com> QA-Review: Augusto Callejas <acallejas@instructure.com> Product-Review: Sidharth Oberoi <soberoi@instructure.com>
2017-12-09 01:50:33 +08:00
self.percent = calculate_by_scale(scale_data).round(4)
elsif self.score && self.possible
correct score display in LMGB when points possible is 0 fixes OUT-1598 note: It's important to take the quiz after the assignment, per the test plan, in order to properly test out the new error message noted below test plan: - create an outcome and set its total points possible to zero (easiest outcome to work with will have 'latest' as its scoring method - create a question bank and align the outcome (for ease of math, make bank with 10 total points) - create a rubric and align the outcome - create a quiz and align the bank to the quiz (enable multiple attempts for quiz and set method to 'latest' for ease of testing) - create an assignment and align the rubric to the assignment - as the teacher, provide a score for the outcome on the rubric in speedgrader - go to the LMGB, whatever score entered for the rubric should be shown as the outcome's score - as a student, score 70% on the quiz - return to the LMGB as a teacher, the score shown for the outcome should be 2.1/3 - go to the screenreader gradebook (gradebook individual view) - after selecting the proper student and outcome, results shown should match with what's on the standard gradebook - go to the student view of the lmgb - click on the outcome to see the list of alignments - the scores shown on the modal should match with what's shown in the teacher lmgb - open a rails console and do the following: lor=LearningOutcomeResult.last original_percent = lor.percent lor.update_column('percent', nil) - attempt to load the lmgb, you should get an error indication: "There was an error fetching outcome results" - return to your console - do lor.update_column('percent', original_percent) - the lmgb should load fine again Change-Id: I7f9084682eba5c9af3181d0d04c4ed8fc4bd3748 Reviewed-on: https://gerrit.instructure.com/135090 Tested-by: Jenkins Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> Reviewed-by: Neil Gupta <ngupta@instructure.com> QA-Review: Augusto Callejas <acallejas@instructure.com> Product-Review: Sidharth Oberoi <soberoi@instructure.com>
2017-12-09 01:50:33 +08:00
self.percent = percentage
end
self.percent = nil if self.percent && !self.percent.to_f.finite?
end
correct score display in LMGB when points possible is 0 fixes OUT-1598 note: It's important to take the quiz after the assignment, per the test plan, in order to properly test out the new error message noted below test plan: - create an outcome and set its total points possible to zero (easiest outcome to work with will have 'latest' as its scoring method - create a question bank and align the outcome (for ease of math, make bank with 10 total points) - create a rubric and align the outcome - create a quiz and align the bank to the quiz (enable multiple attempts for quiz and set method to 'latest' for ease of testing) - create an assignment and align the rubric to the assignment - as the teacher, provide a score for the outcome on the rubric in speedgrader - go to the LMGB, whatever score entered for the rubric should be shown as the outcome's score - as a student, score 70% on the quiz - return to the LMGB as a teacher, the score shown for the outcome should be 2.1/3 - go to the screenreader gradebook (gradebook individual view) - after selecting the proper student and outcome, results shown should match with what's on the standard gradebook - go to the student view of the lmgb - click on the outcome to see the list of alignments - the scores shown on the modal should match with what's shown in the teacher lmgb - open a rails console and do the following: lor=LearningOutcomeResult.last original_percent = lor.percent lor.update_column('percent', nil) - attempt to load the lmgb, you should get an error indication: "There was an error fetching outcome results" - return to your console - do lor.update_column('percent', original_percent) - the lmgb should load fine again Change-Id: I7f9084682eba5c9af3181d0d04c4ed8fc4bd3748 Reviewed-on: https://gerrit.instructure.com/135090 Tested-by: Jenkins Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> Reviewed-by: Neil Gupta <ngupta@instructure.com> QA-Review: Augusto Callejas <acallejas@instructure.com> Product-Review: Sidharth Oberoi <soberoi@instructure.com>
2017-12-09 01:50:33 +08:00
def percentage
if self.possible.to_f > 0
self.score.to_f / self.possible.to_f
elsif parent_has_mastery?
# the parent should always have a mastery score, if it doesn't
# it means something is broken with the outcome and it will need to
# be corrected. If percent is nil on an Outcome Result associated with
# a Quiz, it will cause a 500 error on the learning mastery gradebook in
# the get_aggregates method in RollupScoreAggregatorHelper
self.score.to_f / parent_outcome.mastery_points.to_f
end
end
2011-02-01 09:57:29 +08:00
def assignment
if self.association_object.is_a?(Assignment)
self.association_object
2011-02-01 09:57:29 +08:00
elsif self.artifact.is_a?(RubricAssessment)
self.artifact.rubric_association.association_object
2011-02-01 09:57:29 +08:00
else
nil
end
end
def save_to_version(attempt)
current_version = self.versions.current.try(:model)
if current_version.try(:attempt) && attempt < current_version.attempt
versions = self.versions.sort_by(&:created_at).reverse.select{|v| v.model.attempt == attempt}
if !versions.empty?
versions.each do |version|
version_data = YAML::load(version.yaml)
version_data["score"] = self.score
version_data["mastery"] = self.mastery
version_data["possible"] = self.possible
version_data["attempt"] = self.attempt
version_data["title"] = self.title
version.yaml = version_data.to_yaml
version.save
end
else
save
end
else
save
end
end
updated learning outcome to retain eval method and info for weighted methods fixes CNVS-16915 (all mentions of 'gradebook' refer to learning mastery gradebook) test plan: - go to gradebook - all existing mastery scores should still default to highest score to retain accuracy of existing calculations - in rails console, set 'calculation_method' to 'latest' for an outcome - all results for that outcome should now show student's latest score on gradebook - in rails console, set method to 'highest' - all results for that outcome should now show student's highest score on gradebook - in rails console, set method to 'n_mastery' and set 'calculation_int' to a number between two and 5 - all results for that outcome should now show avg of student's higest n# of scores (#n being the number set for the outcome's 'calculation_int') - in rails console, set method to 'decaying_average' and set 'calculation_int' to 75 - scores should now appear as they did first time gradebook was viewed on step #2 - in rails console, set 'calculation_int' to 65 - scores for that outcome should now reflect decaying average with 65% weight given to most recent score, and 35% weight for avg of all prior scores - have a student make a new submission to an old assignment and give it a score - the re-submission should now be most recent score for 'latest' and 'decaying_average' methods - amend a score for an old submission without a new submission from the student - the amended score should not become the most recent, but still be accurately reflected in the 25%/35% part of the 'decaying_average' method Change-Id: I330c47cb2288d99e589deca00ddb5a8ee8103a91 Reviewed-on: https://gerrit.instructure.com/44935 Reviewed-by: Simon Williams <simon@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Sean Lewis <slewis@instructure.com> Product-Review: Benjamin Porter <bporter@instructure.com>
2014-11-26 06:56:01 +08:00
def submitted_or_assessed_at
submitted_at || assessed_at
end
scope :for_context_codes, lambda { |codes|
2011-02-01 09:57:29 +08:00
if codes == 'all'
all
2011-02-01 09:57:29 +08:00
else
where(:context_code => codes)
2011-02-01 09:57:29 +08:00
end
}
scope :for_user, lambda { |user| where(:user_id => user) }
scope :custom_ordering, lambda { |param|
2011-02-01 09:57:29 +08:00
orders = {
'recent' => "assessed_at DESC",
'highest' => "score DESC",
'oldest' => "score ASC",
'default' => "assessed_at DESC"
}
order_clause = orders[param] || orders['default']
order(order_clause)
}
scope :for_outcome_ids, lambda { |ids| where(:learning_outcome_id => ids) }
scope :for_association, lambda { |association| where(:association_type => association.class.to_s, :association_id => association.id) }
scope :for_associated_asset, lambda { |associated_asset| where(:associated_asset_type => associated_asset.class.to_s, :associated_asset_id => associated_asset.id) }
scope :active, lambda { where("content_tags.workflow_state <> 'deleted'").joins(:alignment) }
private
def infer_defaults
self.learning_outcome_id = self.alignment.learning_outcome_id
self.context_code = "#{self.context_type.underscore}_#{self.context_id}" rescue nil
self.original_score ||= self.score
self.original_possible ||= self.possible
self.original_mastery = self.mastery if self.original_mastery == nil
calculate_percent!
true
end
def calculate_by_scale(scale_data)
scale_percent = scale_data[:scale_percent]
alignment_mastery = scale_data[:alignment_mastery]
scale_points = (self.possible / scale_percent) - self.possible
scale_cutoff = self.possible - (self.possible * alignment_mastery)
percent_to_scale = (self.score + scale_cutoff) - self.possible
if percent_to_scale > 0 && scale_cutoff > 0
score_adjustment = (percent_to_scale / scale_cutoff) * scale_points
scaled_score = self.score + score_adjustment
(scaled_score / self.possible) * scale_percent
else
(self.score / self.possible) * scale_percent
end
end
def scale_params
parent_mastery = precise_mastery_percent
alignment_mastery = self.alignment.mastery_score
return unless parent_mastery && alignment_mastery
if parent_mastery > 0 && alignment_mastery > 0
{ scale_percent: parent_mastery / alignment_mastery,
alignment_mastery: alignment_mastery }
end
end
correct score display in LMGB when points possible is 0 fixes OUT-1598 note: It's important to take the quiz after the assignment, per the test plan, in order to properly test out the new error message noted below test plan: - create an outcome and set its total points possible to zero (easiest outcome to work with will have 'latest' as its scoring method - create a question bank and align the outcome (for ease of math, make bank with 10 total points) - create a rubric and align the outcome - create a quiz and align the bank to the quiz (enable multiple attempts for quiz and set method to 'latest' for ease of testing) - create an assignment and align the rubric to the assignment - as the teacher, provide a score for the outcome on the rubric in speedgrader - go to the LMGB, whatever score entered for the rubric should be shown as the outcome's score - as a student, score 70% on the quiz - return to the LMGB as a teacher, the score shown for the outcome should be 2.1/3 - go to the screenreader gradebook (gradebook individual view) - after selecting the proper student and outcome, results shown should match with what's on the standard gradebook - go to the student view of the lmgb - click on the outcome to see the list of alignments - the scores shown on the modal should match with what's shown in the teacher lmgb - open a rails console and do the following: lor=LearningOutcomeResult.last original_percent = lor.percent lor.update_column('percent', nil) - attempt to load the lmgb, you should get an error indication: "There was an error fetching outcome results" - return to your console - do lor.update_column('percent', original_percent) - the lmgb should load fine again Change-Id: I7f9084682eba5c9af3181d0d04c4ed8fc4bd3748 Reviewed-on: https://gerrit.instructure.com/135090 Tested-by: Jenkins Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> Reviewed-by: Neil Gupta <ngupta@instructure.com> QA-Review: Augusto Callejas <acallejas@instructure.com> Product-Review: Sidharth Oberoi <soberoi@instructure.com>
2017-12-09 01:50:33 +08:00
def parent_has_mastery?
parent_outcome&.mastery_points.to_f > 0
end
def parent_outcome
self.learning_outcome
end
def needs_scale?(scale_data)
scale_data && scale_data[:scale_percent] != 1.0
end
def precise_mastery_percent
# the outcome's mastery percent is rounded to 2 places. This is normally OK
# but for scaling it's too imprecise and can lead to inaccurate calculations
correct score display in LMGB when points possible is 0 fixes OUT-1598 note: It's important to take the quiz after the assignment, per the test plan, in order to properly test out the new error message noted below test plan: - create an outcome and set its total points possible to zero (easiest outcome to work with will have 'latest' as its scoring method - create a question bank and align the outcome (for ease of math, make bank with 10 total points) - create a rubric and align the outcome - create a quiz and align the bank to the quiz (enable multiple attempts for quiz and set method to 'latest' for ease of testing) - create an assignment and align the rubric to the assignment - as the teacher, provide a score for the outcome on the rubric in speedgrader - go to the LMGB, whatever score entered for the rubric should be shown as the outcome's score - as a student, score 70% on the quiz - return to the LMGB as a teacher, the score shown for the outcome should be 2.1/3 - go to the screenreader gradebook (gradebook individual view) - after selecting the proper student and outcome, results shown should match with what's on the standard gradebook - go to the student view of the lmgb - click on the outcome to see the list of alignments - the scores shown on the modal should match with what's shown in the teacher lmgb - open a rails console and do the following: lor=LearningOutcomeResult.last original_percent = lor.percent lor.update_column('percent', nil) - attempt to load the lmgb, you should get an error indication: "There was an error fetching outcome results" - return to your console - do lor.update_column('percent', original_percent) - the lmgb should load fine again Change-Id: I7f9084682eba5c9af3181d0d04c4ed8fc4bd3748 Reviewed-on: https://gerrit.instructure.com/135090 Tested-by: Jenkins Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> Reviewed-by: Neil Gupta <ngupta@instructure.com> QA-Review: Augusto Callejas <acallejas@instructure.com> Product-Review: Sidharth Oberoi <soberoi@instructure.com>
2017-12-09 01:50:33 +08:00
return unless parent_has_mastery? && parent_outcome.points_possible > 0
parent_outcome.mastery_points.to_f / parent_outcome.points_possible.to_f
end
2011-02-01 09:57:29 +08:00
end