From 2abf2d352ac8421e413510adcc5cd98e3629d1f7 Mon Sep 17 00:00:00 2001 From: Alex Basson Date: Fri, 22 Oct 2021 16:01:59 -0400 Subject: [PATCH] Implement correct policy regarding whether there exist sufficient data for a measure --- app/models/measure.rb | 12 +- app/models/survey_item_response.rb | 14 +- .../features/school_dashboard_feature_spec.rb | 22 +-- spec/models/survey_item_response_spec.rb | 130 +++++++++++++++--- 4 files changed, 142 insertions(+), 36 deletions(-) diff --git a/app/models/measure.rb b/app/models/measure.rb index b91d22a3..0e0624c0 100644 --- a/app/models/measure.rb +++ b/app/models/measure.rb @@ -4,11 +4,19 @@ class Measure < ActiveRecord::Base has_many :survey_item_responses, through: :survey_items + def teacher_survey_items + @teacher_survey_items ||= survey_items.where("survey_item_id LIKE 't-%'") + end + + def student_survey_items + @student_survey_items ||= survey_items.where("survey_item_id LIKE 's-%'") + end + def includes_teacher_survey_items? - survey_items.where("survey_item_id LIKE 't-%'").any? + teacher_survey_items.any? end def includes_student_survey_items? - survey_items.where("survey_item_id LIKE 's-%'").any? + student_survey_items.any? end end diff --git a/app/models/survey_item_response.rb b/app/models/survey_item_response.rb index 8dfb6819..1cd5e694 100644 --- a/app/models/survey_item_response.rb +++ b/app/models/survey_item_response.rb @@ -8,8 +8,8 @@ class SurveyItemResponse < ActiveRecord::Base scope :for_measures, ->(measures) { joins(:survey_item).where('survey_items.measure_id': measures.map(&:id)) } scope :for_measure, ->(measure) { joins(:survey_item).where('survey_items.measure_id': measure.id) } - scope :for_teacher_responses_for_measure, ->(measure) { for_measure(measure).where("survey_items.survey_item_id LIKE 't-%'") } - scope :for_student_responses_for_measure, ->(measure) { for_measure(measure).where("survey_items.survey_item_id LIKE 's-%'") } + scope :teacher_responses_for_measure, ->(measure) { for_measure(measure).where("survey_items.survey_item_id LIKE 't-%'") } + scope :student_responses_for_measure, ->(measure) { for_measure(measure).where("survey_items.survey_item_id LIKE 's-%'") } def self.score_for_measure(measure:, school:, academic_year:) return nil unless SurveyItemResponse.sufficient_data?(measure: measure, school: school, academic_year: academic_year) @@ -26,11 +26,17 @@ class SurveyItemResponse < ActiveRecord::Base meets_student_threshold = true if measure.includes_teacher_survey_items? - meets_teacher_threshold = SurveyItemResponse.for_teacher_responses_for_measure(measure).where(academic_year: academic_year, school: school).count >= TEACHER_RESPONSE_THRESHOLD + teacher_survey_item_responses = SurveyItemResponse.teacher_responses_for_measure(measure).where(academic_year: academic_year, school: school) + average_number_of_survey_item_responses = teacher_survey_item_responses.count / measure.teacher_survey_items.count + + meets_teacher_threshold = average_number_of_survey_item_responses >= TEACHER_RESPONSE_THRESHOLD end if measure.includes_student_survey_items? - meets_student_threshold = SurveyItemResponse.for_student_responses_for_measure(measure).where(academic_year: academic_year, school: school).count >= STUDENT_RESPONSE_THRESHOLD + student_survey_item_responses = SurveyItemResponse.student_responses_for_measure(measure).where(academic_year: academic_year, school: school) + average_number_of_survey_item_responses = student_survey_item_responses.count / measure.student_survey_items.count + + meets_student_threshold = average_number_of_survey_item_responses >= STUDENT_RESPONSE_THRESHOLD end meets_teacher_threshold and meets_student_threshold diff --git a/spec/features/school_dashboard_feature_spec.rb b/spec/features/school_dashboard_feature_spec.rb index 5f67eed1..4c237154 100644 --- a/spec/features/school_dashboard_feature_spec.rb +++ b/spec/features/school_dashboard_feature_spec.rb @@ -29,33 +29,33 @@ feature 'School dashboard', type: feature do let(:password) { 'winchester!' } before :each do + survey_item_responses = [] + survey_items_for_measure_1A_i.each do |survey_item| - 200.times do - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item, likert_score: 4 + SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD.times do + survey_item_responses << SurveyItemResponse.new(response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 4) end end survey_items_for_measure_2A_i.each do |survey_item| - 200.times do - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item, likert_score: 5 + SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD.times do + survey_item_responses << SurveyItemResponse.new(response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 5) end end survey_items_for_measure_4C_i.each do |survey_item| - 200.times do - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item, likert_score: 1 + SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD.times do + survey_item_responses << SurveyItemResponse.new(response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 1) end end survey_items_for_subcategory.each do |survey_item| 200.times do - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item, likert_score: 4 + survey_item_responses << SurveyItemResponse.new(response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 4) end end + + SurveyItemResponse.import survey_item_responses end scenario 'User authentication fails' do diff --git a/spec/models/survey_item_response_spec.rb b/spec/models/survey_item_response_spec.rb index 46bafa48..bd6a4f26 100644 --- a/spec/models/survey_item_response_spec.rb +++ b/spec/models/survey_item_response_spec.rb @@ -11,15 +11,17 @@ describe SurveyItemResponse, type: :model do let(:teacher_survey_item_2) { create(:survey_item, survey_item_id: 't-question-2', measure: measure) } let(:teacher_survey_item_3) { create(:survey_item, survey_item_id: 't-question-3', measure: measure) } - context 'and there is sufficient teacher data' do + context "and the number of responses for each of the measure's survey items meets the teacher threshold of 17" do before :each do - 4.times do + 17.times do create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: 3) - create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: 5) - create(:survey_item_response, survey_item: teacher_survey_item_2, academic_year: ay, school: school, likert_score: 3) - create(:survey_item_response, survey_item: teacher_survey_item_2, academic_year: ay, school: school, likert_score: 5) end - create(:survey_item_response, survey_item: teacher_survey_item_2, academic_year: ay, school: school, likert_score: 4) + 17.times do + create(:survey_item_response, survey_item: teacher_survey_item_2, academic_year: ay, school: school, likert_score: 4) + end + 17.times do + create(:survey_item_response, survey_item: teacher_survey_item_3, academic_year: ay, school: school, likert_score: 5) + end end it 'returns the average of the likert scores of the survey items' do @@ -27,10 +29,53 @@ describe SurveyItemResponse, type: :model do end end - context 'and there is insufficient teacher data' do + context "and the average number of responses across the measure's survey items meets the teacher threshold of 17" do before :each do - (SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD - 1).times do - create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school) + 19.times do + create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: 3) + end + 16.times do + create(:survey_item_response, survey_item: teacher_survey_item_2, academic_year: ay, school: school, likert_score: 4) + end + 16.times do + create(:survey_item_response, survey_item: teacher_survey_item_3, academic_year: ay, school: school, likert_score: 5) + end + end + + it 'returns the average of the likert scores of the survey items' do + average_score = 3.941 + expect(SurveyItemResponse.score_for_measure(measure: measure, school: school, academic_year: ay)).to be_within(0.001).of(average_score) + end + end + + context "and none of the measure's survey items meets the teacher threshold of 17" do + before :each do + 16.times do + create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: rand) + end + 16.times do + create(:survey_item_response, survey_item: teacher_survey_item_2, academic_year: ay, school: school, likert_score: rand) + end + 16.times do + create(:survey_item_response, survey_item: teacher_survey_item_3, academic_year: ay, school: school, likert_score: rand) + end + end + + it 'returns nil' do + expect(SurveyItemResponse.score_for_measure(measure: measure, school: school, academic_year: ay)).to be_nil + end + end + + context "and the average number of responses across the measure's survey items does not meet the teacher threshold of 17" do + before :each do + 18.times do + create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: rand) + end + 16.times do + create(:survey_item_response, survey_item: teacher_survey_item_2, academic_year: ay, school: school, likert_score: rand) + end + 16.times do + create(:survey_item_response, survey_item: teacher_survey_item_3, academic_year: ay, school: school, likert_score: rand) end end @@ -43,26 +88,73 @@ describe SurveyItemResponse, type: :model do context 'when the measure includes only student data' do let(:student_survey_item_1) { create(:survey_item, survey_item_id: 's-question-1', measure: measure) } let(:student_survey_item_2) { create(:survey_item, survey_item_id: 's-question-2', measure: measure) } + let(:student_survey_item_3) { create(:survey_item, survey_item_id: 's-question-3', measure: measure) } - context 'and there is sufficient student data' do + context "and the number of responses for each of the measure's survey items meets the student threshold of 196" do before :each do - (SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD/4).times do - create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: 1) - create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: 5) - create(:survey_item_response, survey_item: student_survey_item_2, academic_year: ay, school: school, likert_score: 1) - create(:survey_item_response, survey_item: student_survey_item_2, academic_year: ay, school: school, likert_score: 5) + 196.times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: 3) + end + 196.times do + create(:survey_item_response, survey_item: student_survey_item_2, academic_year: ay, school: school, likert_score: 4) + end + 196.times do + create(:survey_item_response, survey_item: student_survey_item_3, academic_year: ay, school: school, likert_score: 5) end end it 'returns the average of the likert scores of the survey items' do - expect(SurveyItemResponse.score_for_measure(measure: measure, school: school, academic_year: ay)).to eq 3 + expect(SurveyItemResponse.score_for_measure(measure: measure, school: school, academic_year: ay)).to eq 4 end end - context 'and there insufficient student data' do + context "and the average number of responses across the measure's survey items meets the student threshold of 196" do before :each do - (SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1).times do - create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school) + 200.times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: 3) + end + 195.times do + create(:survey_item_response, survey_item: student_survey_item_2, academic_year: ay, school: school, likert_score: 4) + end + 193.times do + create(:survey_item_response, survey_item: student_survey_item_3, academic_year: ay, school: school, likert_score: 5) + end + end + + it 'returns the average of the likert scores of the survey items' do + average_score = 3.988 + expect(SurveyItemResponse.score_for_measure(measure: measure, school: school, academic_year: ay)).to be_within(0.001).of(average_score) + end + end + + context "and none of the measure's survey items meets the student threshold of 196" do + before :each do + 195.times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: rand) + end + 195.times do + create(:survey_item_response, survey_item: student_survey_item_2, academic_year: ay, school: school, likert_score: rand) + end + 195.times do + create(:survey_item_response, survey_item: student_survey_item_3, academic_year: ay, school: school, likert_score: rand) + end + end + + it 'returns nil' do + expect(SurveyItemResponse.score_for_measure(measure: measure, school: school, academic_year: ay)).to be_nil + end + end + + context "and the average number of responses across the measure's survey items does not meet the student threshold of 196" do + before :each do + 200.times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: rand) + end + 196.times do + create(:survey_item_response, survey_item: student_survey_item_2, academic_year: ay, school: school, likert_score: rand) + end + 191.times do + create(:survey_item_response, survey_item: student_survey_item_3, academic_year: ay, school: school, likert_score: rand) end end