From c47574493955f83cd6f912430ae4f468b3bc5f70 Mon Sep 17 00:00:00 2001 From: Nelson Jovel Date: Tue, 19 Apr 2022 12:41:01 -0700 Subject: [PATCH] Modify score calculations. Ignore any survey item scores of 0. Never include zero when performing calculations for scores. --- app/models/measure.rb | 30 ++++++---- app/models/scale.rb | 14 +++-- config/initializers/array_monkey_patches.rb | 4 ++ doc/architectural_decision_records/9.md | 19 ++++++ spec/models/scale_spec.rb | 64 +++++++++++++++------ 5 files changed, 99 insertions(+), 32 deletions(-) create mode 100644 doc/architectural_decision_records/9.md diff --git a/app/models/measure.rb b/app/models/measure.rb index a4f3e08d..f50df8d2 100644 --- a/app/models/measure.rb +++ b/app/models/measure.rb @@ -50,20 +50,17 @@ class Measure < ActiveRecord::Base meets_student_threshold = sufficient_student_data?(school:, academic_year:) meets_teacher_threshold = sufficient_teacher_data?(school:, academic_year:) meets_admin_data_threshold = all_admin_data_collected?(school:, academic_year:) - lacks_sufficient_data = !meets_student_threshold && !meets_teacher_threshold && !includes_admin_data_items? + lacks_sufficient_survey_data = !meets_student_threshold && !meets_teacher_threshold + incalculable_score = lacks_sufficient_survey_data && !includes_admin_data_items? - next Score.new(nil, false, false, false) if lacks_sufficient_data + next Score.new(nil, false, false, false) if incalculable_score scores = [] - scores << teacher_scales.map { |scale| scale.score(school:, academic_year:) }.average if meets_teacher_threshold - scores << student_scales.map { |scale| scale.score(school:, academic_year:) }.average if meets_student_threshold - if includes_admin_data_items? - scores << admin_data_items.map do |admin_data_item| - admin_value = admin_data_item.admin_data_values.where(school:, academic_year:).first - admin_value.likert_score if admin_value.present? - end - end - average = scores.flatten.compact.average + scores << collect_survey_scale_average(teacher_scales, school, academic_year) if meets_teacher_threshold + scores << collect_survey_scale_average(student_scales, school, academic_year) if meets_student_threshold + scores << collect_admin_scale_average(admin_data_items, school, academic_year) if includes_admin_data_items? + + average = scores.flatten.compact.remove_zeros.average next Score.new(nil, false, false, false) if average.nan? @@ -122,6 +119,17 @@ class Measure < ActiveRecord::Base private + def collect_survey_scale_average(scales, school, academic_year) + scales.map { |scale| scale.score(school:, academic_year:) }.average + end + + def collect_admin_scale_average(scales, school, academic_year) + scales.map do |admin_data_item| + admin_value = admin_data_item.admin_data_values.where(school:, academic_year:).first + admin_value.likert_score if admin_value.present? + end + end + def benchmark(name) averages = [] averages << student_survey_items.first.send(name) if includes_student_survey_items? diff --git a/app/models/scale.rb b/app/models/scale.rb index aa0096b1..126e6cf0 100644 --- a/app/models/scale.rb +++ b/app/models/scale.rb @@ -8,12 +8,10 @@ class Scale < ApplicationRecord @score ||= Hash.new do |memo| memo[[school, academic_year]] = begin items = [] - items << student_survey_items(school:, academic_year:) - items << teacher_survey_items + items << collect_survey_item_average(student_survey_items(school:, academic_year:), school, academic_year) + items << collect_survey_item_average(teacher_survey_items, school, academic_year) - items.flatten.map do |survey_item| - survey_item.score(school:, academic_year:) - end.average + items.remove_zeros.average end end @score[[school, academic_year]] @@ -28,6 +26,12 @@ class Scale < ApplicationRecord private + def collect_survey_item_average(survey_items, school, academic_year) + survey_items.map do |survey_item| + survey_item.score(school:, academic_year:) + end.remove_zeros.average + end + def teacher_survey_items survey_items.teacher_survey_items end diff --git a/config/initializers/array_monkey_patches.rb b/config/initializers/array_monkey_patches.rb index 465b716e..2d5ade6c 100644 --- a/config/initializers/array_monkey_patches.rb +++ b/config/initializers/array_monkey_patches.rb @@ -2,6 +2,10 @@ module ArrayMonkeyPatches def average sum.to_f / size end + + def remove_zeros + reject { |item| item == 0 || item.to_f.nan? } + end end Array.include ArrayMonkeyPatches diff --git a/doc/architectural_decision_records/9.md b/doc/architectural_decision_records/9.md new file mode 100644 index 00000000..8aec25a0 --- /dev/null +++ b/doc/architectural_decision_records/9.md @@ -0,0 +1,19 @@ +# Decision record 9 + +# Modify score calculations so that a survey item score of 0 is never included in the average + +## Status + +Completed + +## Context + +Some scores were artificially low because survey items that lacked survey item responses returned a score of 0. 0 did not represent the average likert_scores of survey item responses, only the lack of responses. 0 was incorrectly being averaged with other survey items and artificially dragging down the score. + +## Decision + +Survey item scores of 0 should be ignored and filtered out when performing score calculations. + +## Consequences + +Some average scores have increased. diff --git a/spec/models/scale_spec.rb b/spec/models/scale_spec.rb index d3989a9f..dfbafa1e 100644 --- a/spec/models/scale_spec.rb +++ b/spec/models/scale_spec.rb @@ -12,32 +12,64 @@ RSpec.describe Scale, type: :model do let(:teacher_survey_item_1) { create(:teacher_survey_item, scale:) } let(:teacher_survey_item_2) { create(:teacher_survey_item, scale:) } let(:teacher_survey_item_3) { create(:teacher_survey_item, scale:) } + let(:student_survey_item_1) { create(:student_survey_item, scale:) } - before :each do + context 'when only teacher survey items exist' do + before :each do + create(:survey_item_response, + survey_item: teacher_survey_item_1, academic_year:, school:, likert_score: 3) + create(:survey_item_response, + survey_item: teacher_survey_item_2, academic_year:, school:, likert_score: 4) + create(:survey_item_response, + survey_item: teacher_survey_item_3, academic_year:, school:, likert_score: 5) + end + it 'returns the average of the likert scores of the survey items' do + expect(scale.score(school:, academic_year:)).to eq 4 + end + + context 'when other scales exist' do + before :each do + create(:survey_item_response, + academic_year:, school:, likert_score: 1) + create(:survey_item_response, + academic_year:, school:, likert_score: 1) + create(:survey_item_response, + academic_year:, school:, likert_score: 1) + end + + it 'does not affect the score for the original scale' do + expect(scale.score(school:, academic_year:)).to eq 4 + end + end + end + + context 'when both teacher and student survey items exist' do + before :each do create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year:, school:, likert_score: 3) create(:survey_item_response, survey_item: teacher_survey_item_2, academic_year:, school:, likert_score: 4) create(:survey_item_response, survey_item: teacher_survey_item_3, academic_year:, school:, likert_score: 5) - end - - it 'returns the average of the likert scores of the survey items' do - expect(scale.score(school:, academic_year:)).to eq 4 - end + end + context 'but no survey item responses are linked to student survey items' do + before :each do + student_survey_item_1 + end - context 'when other scales exist' do - before :each do - create(:survey_item_response, - academic_year:, school:, likert_score: 1) - create(:survey_item_response, - academic_year:, school:, likert_score: 1) - create(:survey_item_response, - academic_year:, school:, likert_score: 1) + it 'returns a score that only averages teacher survey items' do + expect(scale.score(school:, academic_year:)).to eq 4 + end end - it 'does not affect the score for the original scale' do - expect(scale.score(school:, academic_year:)).to eq 4 + context 'and some survey item responses exist for a student survey item' do + before :each do + create(:survey_item_response, + survey_item: student_survey_item_1, academic_year:, school:, likert_score: 2) + end + it 'returns a score that gives equal weight to student and teacher survey items' do + expect(scale.score(school:, academic_year:)).to eq 3 + end end end end