Rename ResponseRate to ResponseRateCalculator. Create a new response

rate model.  Create a loader to refresh response rates for all
subcategories.

Use precalculated response rates in views

Wrap more elements in page caching

Calculate a response rate for a subcategory if one does not already
exist
This commit is contained in:
rebuilt 2022-06-15 09:35:13 -07:00
parent dfc5202b88
commit c03615cb43
16 changed files with 352 additions and 177 deletions

View file

@ -3,6 +3,9 @@ require 'rails_helper'
describe ResponseRateCalculator, type: :model do
let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) }
let(:survey) { create(:survey, school:, academic_year:) }
let(:short_form_survey) { create(:survey, form: :short, school:, academic_year:) }
let(:respondent) { create(:respondent, school:, academic_year:) }
describe StudentResponseRateCalculator do
let(:subcategory) { create(:subcategory) }
@ -23,13 +26,14 @@ describe ResponseRateCalculator, type: :model do
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD, survey_item: sufficient_student_survey_item_2,
academic_year:, school:, likert_score: 4)
end
context 'when a students take a regular survey' do
before :each do
create(:respondent, school:, academic_year:)
create(:survey, school:, academic_year:)
end
context 'when a students take a regular survey' do
context 'when the average number of student responses per question in a subcategory is equal to the student response threshold' do
before :each do
respondent
survey
end
it 'returns a response rate equal to the response threshold' do
expect(StudentResponseRateCalculator.new(subcategory:, school:,
academic_year:).rate).to eq 25
@ -39,8 +43,8 @@ describe ResponseRateCalculator, type: :model do
context 'when students take the short form survey' do
before :each do
create(:respondent, school:, academic_year:)
create(:survey, form: :short, school:, academic_year:)
respondent
short_form_survey
end
context 'when the average number of student responses per question in a subcategory is equal to the student response threshold' do
@ -49,19 +53,19 @@ describe ResponseRateCalculator, type: :model do
sufficient_student_survey_item_2.update! on_short_form: true
end
it 'returns 100 percent' do
it 'takes into account the responses from both survey items' do
expect(StudentResponseRateCalculator.new(subcategory:, school:,
academic_year:).rate).to eq 25
end
context 'for the same number of responses, if only one of the questions is a short form question, the response rate will be half' do
context 'and only one of the survey items is on the short form' do
before do
sufficient_student_survey_item_2.update! on_short_form: false
end
it 'returns 100 percent' do
it 'the response rate ignores the responses in the non-short form item' do
expect(StudentResponseRateCalculator.new(subcategory:, school:,
academic_year:).rate).to eq 50
academic_year:).rate).to eq 25
end
end
end
@ -69,8 +73,8 @@ describe ResponseRateCalculator, type: :model do
context 'when the average number of teacher responses is greater than the total possible responses' do
before do
create(:respondent, school:, academic_year:)
create(:survey, school:, academic_year:)
respondent
survey
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD * 11, survey_item: sufficient_student_survey_item_2,
academic_year:, school:, likert_score: 1)
end
@ -124,8 +128,8 @@ describe ResponseRateCalculator, type: :model do
context 'when the average number of teacher responses per question in a subcategory is at the threshold' do
before :each do
create(:respondent, school:, academic_year:)
create(:survey, school:, academic_year:)
respondent
survey
end
it 'returns 25 percent' do
expect(TeacherResponseRateCalculator.new(subcategory:, school:,
@ -135,8 +139,8 @@ describe ResponseRateCalculator, type: :model do
context 'when the teacher response rate is not a whole number. eg 29.166%' do
before do
create(:respondent, school:, academic_year:)
create(:survey, school:, academic_year:)
respondent
survey
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD + 1, survey_item: sufficient_teacher_survey_item_3,
academic_year:, school:, likert_score: 1)
end
@ -148,8 +152,8 @@ describe ResponseRateCalculator, type: :model do
context 'when the average number of teacher responses is greater than the total possible responses' do
before do
create(:respondent, school:, academic_year:)
create(:survey, school:, academic_year:)
respondent
survey
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD * 11, survey_item: sufficient_teacher_survey_item_3,
academic_year:, school:, likert_score: 1)
end
@ -169,8 +173,8 @@ describe ResponseRateCalculator, type: :model do
context 'when there is an imbalance in the response rate of the teacher items' do
context 'and one of the teacher items has no associated survey item responses' do
before do
create(:respondent, school:, academic_year:)
create(:survey, school:, academic_year:)
respondent
survey
insufficient_teacher_survey_item_4
end
it 'ignores the empty survey item and returns only the average response rate of teacher survey items with responses' do

View file

@ -0,0 +1,140 @@
require 'rails_helper'
describe ResponseRateLoader do
let(:school) { School.find_by_slug 'milford-high-school' }
let(:academic_year) { AcademicYear.find_by_range '2020-21' }
let(:respondents) do
respondents = Respondent.where(school:, academic_year:).first
respondents.total_students = 10
respondents.total_teachers = 10
respondents.save
end
let(:short_form_survey) do
survey = Survey.find_by(school:, academic_year:)
survey.form = :short
survey.save
survey
end
let(:subcategory) { Subcategory.find_by_subcategory_id '5D' }
let(:s_acst_q1) { SurveyItem.find_by_survey_item_id 's-acst-q1' }
let(:s_acst_q2) { SurveyItem.find_by_survey_item_id 's-acst-q2' } # short form
let(:s_acst_q3) { SurveyItem.find_by_survey_item_id 's-acst-q3' }
let(:s_poaf_q1) { SurveyItem.find_by_survey_item_id 's-poaf-q1' }
let(:s_poaf_q2) { SurveyItem.find_by_survey_item_id 's-poaf-q2' }
let(:s_poaf_q3) { SurveyItem.find_by_survey_item_id 's-poaf-q3' } # short form
let(:s_poaf_q4) { SurveyItem.find_by_survey_item_id 's-poaf-q4' }
let(:t_phya_q2) { SurveyItem.find_by_survey_item_id 't-phya-q2' }
let(:t_phya_q3) { SurveyItem.find_by_survey_item_id 't-phya-q3' }
let(:s_acst) { Scale.find_by_scale_id 's-acst' }
let(:s_poaf) { Scale.find_by_scale_id 's-poaf' }
let(:t_phya) { Scale.find_by_scale_id 't-phya' }
let(:response_rate) { ResponseRate.find_by(subcategory:, school:, academic_year:) }
before :each do
Rails.application.load_seed
respondents
end
after :each do
DatabaseCleaner.clean
end
describe 'self.refresh' do
context 'When refreshing response rates' do
context 'and half the students responded to each question' do
before :each do
create_list(:survey_item_response, 5, survey_item: s_acst_q1, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: s_acst_q2, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: s_acst_q3, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: s_poaf_q1, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: s_poaf_q2, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: s_poaf_q3, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: s_poaf_q4, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: t_phya_q2, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: t_phya_q3, likert_score: 3, school:, academic_year:)
ResponseRateLoader.refresh
end
it 'populates the database with response rates' do
expect(s_acst_q1.survey_item_id).to eq 's-acst-q1'
expect(subcategory.subcategory_id).to eq '5D'
expect(subcategory.name).to eq 'Health'
expect(s_acst.score(school:, academic_year:)).to eq 3
expect(s_poaf.score(school:, academic_year:)).to eq 3
expect(t_phya.score(school:, academic_year:)).to eq 3
expect(response_rate.student_response_rate).to eq 50
expect(response_rate.teacher_response_rate).to eq 50
expect(response_rate.meets_student_threshold).to be true
expect(response_rate.meets_teacher_threshold).to be true
end
context 'when running the loader a second time' do
it 'is idempotent' do
response_count = ResponseRate.count
ResponseRateLoader.refresh
second_count = ResponseRate.count
expect(response_count).to eq second_count
end
end
end
context 'and only the first question for each scale was asked; e.g. like on a short form' do
before :each do
create_list(:survey_item_response, 5, survey_item: s_acst_q1, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: s_poaf_q1, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: t_phya_q2, likert_score: 3, school:, academic_year:)
ResponseRateLoader.refresh
end
it 'only takes into account the first question and ignores the other questions in the scale' do
expect(response_rate.student_response_rate).to eq 50
expect(response_rate.teacher_response_rate).to eq 50
end
end
context 'and no respondent entry exists for the school and year' do
before do
Respondent.destroy_all
create_list(:survey_item_response, 5, survey_item: s_acst_q1, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: s_poaf_q1, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 5, survey_item: t_phya_q2, likert_score: 3, school:, academic_year:)
ResponseRateLoader.refresh
end
it 'since no score can be calculated, it returns a default of 100' do
expect(response_rate.student_response_rate).to eq 100
expect(response_rate.teacher_response_rate).to eq 100
end
end
context 'and the school took the short form student survey' do
before :each do
create_list(:survey_item_response, 1, survey_item: s_acst_q1, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 6, survey_item: s_acst_q2, likert_score: 3, school:, academic_year:) # short form
create_list(:survey_item_response, 1, survey_item: s_acst_q3, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 1, survey_item: s_poaf_q1, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 1, survey_item: s_poaf_q2, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 6, survey_item: s_poaf_q3, likert_score: 3, school:, academic_year:) # short form
create_list(:survey_item_response, 1, survey_item: s_poaf_q4, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 1, survey_item: t_phya_q2, likert_score: 3, school:, academic_year:)
create_list(:survey_item_response, 1, survey_item: t_phya_q3, likert_score: 3, school:, academic_year:)
short_form_survey
ResponseRateLoader.refresh
end
it 'only counts responses from survey items on the short form' do
expect(response_rate.student_response_rate).to eq 60
end
end
end
end
end

View file

@ -12,6 +12,7 @@ describe 'SQM Application' do
driven_by :rack_test
page.driver.browser.basic_authorize(username, password)
create(:respondent, school:, academic_year:)
create(:survey, school:, academic_year:)
end
context 'when no measures meet their threshold' do

View file

@ -5,6 +5,8 @@ describe 'analyze/index' do
subject { Nokogiri::HTML(rendered) }
let(:category) { create(:category) }
let(:subcategory) { create(:subcategory, category:) }
let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) }
let(:support_for_teaching) do
measure = create(:measure, name: 'Support For Teaching Development & Growth', measure_id: '1A-I', subcategory:)
@ -41,22 +43,20 @@ describe 'analyze/index' do
ideal_low_benchmark: 4.5)
measure
end
let(:academic_year) { create(:academic_year) }
before :each do
# assign :category_presenters, []
# assign :grouped_bar_column_presenters, grouped_bar_column_presenters
assign :academic_year, academic_year
assign :available_academic_years, [academic_year]
assign :selected_academic_years, [academic_year]
# assign :academic_years, [academic_year]
assign :district, create(:district)
assign :school, create(:school)
assign :school, school
assign :category, category
assign :categories, [category]
assign :subcategory, subcategory
assign :subcategories, category.subcategories
assign :measures, [support_for_teaching, effective_leadership, professional_qualifications]
create(:respondent, school:, academic_year:)
create(:survey, school:, academic_year:)
render
end
@ -107,7 +107,10 @@ describe 'analyze/index' do
end
it 'displays disabled checkboxes for years that dont have data' do
ResponseRateLoader.refresh
year_checkbox = subject.css("##{academic_year.range}").first
expect(year_checkbox.name).to eq 'input'
expect(academic_year.range).to eq '2050-51'
expect(year_checkbox).to have_attribute 'disabled'
end
end