diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index f85a93a8..97758223 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -23,6 +23,15 @@ class DashboardController < SqmApplicationController end def presenter_for_measure(measure) + sufficient_data = SurveyItemResponse.sufficient_data?(measure: measure, academic_year: academic_year, school: school) + + unless sufficient_data + return MeasureGraphRowPresenter.new( + measure: measure, + sufficient_data: false + ) + end + score = SurveyItemResponse.for_measure(measure) .where(academic_year: academic_year, school: school) .average(:likert_score) diff --git a/app/helpers/variance_helper.rb b/app/helpers/variance_helper.rb index e66fbcb3..75684a4c 100644 --- a/app/helpers/variance_helper.rb +++ b/app/helpers/variance_helper.rb @@ -42,4 +42,8 @@ module VarianceHelper def zone_width_percentage 100.0/zones.size end + + def measures_with_insufficient_data(presenters:) + presenters.filter { |presenter| !presenter.sufficient_data? } + end end diff --git a/app/models/measure.rb b/app/models/measure.rb index 50c724f5..b91d22a3 100644 --- a/app/models/measure.rb +++ b/app/models/measure.rb @@ -3,4 +3,12 @@ class Measure < ActiveRecord::Base has_many :survey_items has_many :survey_item_responses, through: :survey_items + + def includes_teacher_survey_items? + survey_items.where("survey_item_id LIKE 't-%'").any? + end + + def includes_student_survey_items? + survey_items.where("survey_item_id LIKE 's-%'").any? + end end diff --git a/app/models/survey_item_response.rb b/app/models/survey_item_response.rb index 92a4428f..a3f85216 100644 --- a/app/models/survey_item_response.rb +++ b/app/models/survey_item_response.rb @@ -1,8 +1,26 @@ class SurveyItemResponse < ActiveRecord::Base + TEACHER_RESPONSE_THRESHOLD = 17 + STUDENT_RESPONSE_THRESHOLD = 196 + belongs_to :academic_year belongs_to :school belongs_to :survey_item 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) } + + def self.sufficient_data?(measure:, academic_year:, school:) + meets_teacher_threshold = true + meets_student_threshold = true + + if measure.includes_teacher_survey_items? + meets_teacher_threshold = SurveyItemResponse.for_measure(measure).where("survey_items.survey_item_id LIKE 't-%'").where(academic_year: academic_year, school: school).count >= TEACHER_RESPONSE_THRESHOLD + end + + if measure.includes_student_survey_items? + meets_student_threshold = SurveyItemResponse.for_measure(measure).where("survey_items.survey_item_id LIKE 's-%'").where(academic_year: academic_year, school: school).count >= STUDENT_RESPONSE_THRESHOLD + end + + meets_teacher_threshold and meets_student_threshold + end end diff --git a/app/presenters/measure_graph_row_presenter.rb b/app/presenters/measure_graph_row_presenter.rb index 04ac8717..9a57cf6f 100644 --- a/app/presenters/measure_graph_row_presenter.rb +++ b/app/presenters/measure_graph_row_presenter.rb @@ -1,8 +1,13 @@ class MeasureGraphRowPresenter include Comparable - def initialize(measure:, score:) + def initialize(measure:, score: 0, sufficient_data: true) @measure = measure @score = score + @sufficient_data = sufficient_data + end + + def sufficient_data? + @sufficient_data end def measure_name diff --git a/app/views/dashboard/_variance_graph.erb b/app/views/dashboard/_variance_graph.erb index 65821877..dafbea83 100644 --- a/app/views/dashboard/_variance_graph.erb +++ b/app/views/dashboard/_variance_graph.erb @@ -1,3 +1,8 @@ + +<% unless measures_with_insufficient_data(presenters: presenters).empty? %> +

Note: The following measures are not displayed due to limited availability of school admin data and/or low survey response rates: <%= measures_with_insufficient_data(presenters: presenters).map(&:measure_name).join('; ') %>.

+<% end %> + xmlns="http://www.w3.org/2000/svg"> diff --git a/spec/factories.rb b/spec/factories.rb index 6bae32f6..526156f6 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -16,9 +16,9 @@ FactoryBot.define do end factory :sqm_category do - name { "A category" } + name { "A #{rand} category" } description { "A description of a category" } - slug { 'a-category' } + slug { "a-#{rand}-category" } sort_index { 1 } end diff --git a/spec/models/survey_item_response_spec.rb b/spec/models/survey_item_response_spec.rb new file mode 100644 index 00000000..a9e898ae --- /dev/null +++ b/spec/models/survey_item_response_spec.rb @@ -0,0 +1,131 @@ +require 'rails_helper' + +describe SurveyItemResponse, type: :model do + + describe '.sufficient_data?' do + let(:measure) { create(:measure) } + let(:school) { create(:school) } + let(:ay) { create(:academic_year) } + + context 'when the measure includes only teacher data' do + let(:teacher_survey_item_1) { create(:survey_item, survey_item_id: 't-question-1', measure: measure) } + + context 'and there is sufficient teacher data' do + before :each do + SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD.times do + create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school) + end + end + + it 'is truthy' do + expect(SurveyItemResponse.sufficient_data?(measure: measure, academic_year: ay, school: school)).to be_truthy + end + end + + context 'and there is insufficient teacher data' 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) + end + end + + it 'is falsey' do + expect(SurveyItemResponse.sufficient_data?(measure: measure, academic_year: ay, school: school)).to be_falsey + end + end + end + + 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) } + + context 'and there is sufficient student data' do + before :each do + SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD.times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school) + end + end + + it 'is truthy' do + expect(SurveyItemResponse.sufficient_data?(measure: measure, academic_year: ay, school: school)).to be_truthy + end + end + + context 'and there insufficient student data' 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) + end + end + + it 'is falsey' do + expect(SurveyItemResponse.sufficient_data?(measure: measure, academic_year: ay, school: school)).to be_falsey + end + end + end + + context 'when the measure includes both teacher and student data' do + let(:teacher_survey_item_1) { create(:survey_item, survey_item_id: 't-question-1', measure: measure) } + let(:student_survey_item_1) { create(:survey_item, survey_item_id: 's-question-1', measure: measure) } + + context 'and there is sufficient teacher data and sufficient student data' do + before :each do + SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD.times do + create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school) + end + SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD.times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school) + end + end + + it 'is truthy' do + expect(SurveyItemResponse.sufficient_data?(measure: measure, academic_year: ay, school: school)).to be_truthy + end + end + + context 'and there is sufficient teacher data and insufficient student data' do + before :each do + SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD.times do + create(:survey_item_response, survey_item: teacher_survey_item_1, academic_year: ay, school: school) + end + (SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1).times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school) + end + end + + it 'is falsey' do + expect(SurveyItemResponse.sufficient_data?(measure: measure, academic_year: ay, school: school)).to be_falsey + end + end + + context 'and there is insufficient teacher data and sufficient student data' 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) + end + SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD.times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school) + end + end + + it 'is falsey' do + expect(SurveyItemResponse.sufficient_data?(measure: measure, academic_year: ay, school: school)).to be_falsey + end + end + + context 'and there is insufficient teacher data and insufficient student data' 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) + end + (SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1).times do + create(:survey_item_response, survey_item: student_survey_item_1, academic_year: ay, school: school) + end + end + + it 'is falsey' do + expect(SurveyItemResponse.sufficient_data?(measure: measure, academic_year: ay, school: school)).to be_falsey + end + end + end + end +end diff --git a/spec/views/dashboard/index.html.erb_spec.rb b/spec/views/dashboard/index.html.erb_spec.rb new file mode 100644 index 00000000..f3d8646c --- /dev/null +++ b/spec/views/dashboard/index.html.erb_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +describe 'dashboard/index.html.erb' do + + let(:support_for_teaching) { create(:measure, name: 'Support For Teaching Development & Growth') } + let(:effective_leadership) { create(:measure, name: 'Effective Leadership') } + let(:professional_qualifications) { create(:measure, name: 'Professional Qualifications') } + + before :each do + assign :category_presenters, [] + assign :measure_graph_row_presenters, measure_graph_row_presenters + + render + end + + context 'when there are measures for which, in the given academic year, the school has insufficient responses' do + let(:measure_graph_row_presenters) { + [ + MeasureGraphRowPresenter.new(measure: support_for_teaching, score: 0, sufficient_data: false), + MeasureGraphRowPresenter.new(measure: create(:measure), score: 0, sufficient_data: true), + MeasureGraphRowPresenter.new(measure: effective_leadership, score: 0, sufficient_data: false), + MeasureGraphRowPresenter.new(measure: professional_qualifications, score: 0, sufficient_data: false) + ] + } + + it 'displays a note detailing which measures have insufficient responses for the given school & academic year' do + expect(rendered).to match /Note: The following measures are not displayed due to limited availability of school admin data and\/or low survey response rates: Support For Teaching Development & Growth; Effective Leadership; Professional Qualifications./ + end + end + + context 'when there are no measures for which, in the given academic year, the school has insufficient responses' do + let(:measure_graph_row_presenters) { + [ + MeasureGraphRowPresenter.new(measure: create(:measure), score: 0, sufficient_data: true) + ] + } + + it 'does not display a note detailing which measures have insufficient responses for the given school & academic year' do + expect(rendered).not_to match /Note: The following measures are not displayed due to limited availability of school admin data and\/or low survey response rates/ + end + end +end