Show response rate for students

pull/1/head
rebuilt 4 years ago
parent b111b2f106
commit 59865cd874

@ -4,7 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [Released]
### Added
- short description to Category
## [Unreleased]
### Added
- Add student response rate
`bundle exec rake db:migrate`

@ -8,7 +8,7 @@ begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
# task(:default).clear
task(:default).clear
task default: :spec
rescue LoadError => e
raise e unless ENV['RAILS_ENV'] == 'production'

@ -117,3 +117,16 @@
background-color: $gray-3;
}
.response-rate {
background-color: $gray-3;
border: 1px solid;
border-color: $gray-2;
width: 150px;
border-radius: 8px;
padding: 8px;
}
.response-rate-percentage {
font-size: 20px;
@extend .sub-header-4;
}

@ -29,6 +29,30 @@ class Seeder
School.where.not(dese_id: dese_ids).destroy_all
end
def seed_respondents(csv_file)
schools = []
year = '2020-21'
academic_year = AcademicYear.find_by_range year
CSV.parse(File.read(csv_file), headers: true) do |row|
dese_id = row['DESE School ID'].strip.to_i
total_students = row["Total Students for Response Rate (#{year})"]
total_teachers = row["Total Teachers for Response Rate (#{year})"]
district_name = row['District'].strip
district = District.find_or_create_by! name: district_name
school = School.find_by dese_id: dese_id, district: district
schools << school
respondent = Respondent.find_or_initialize_by school: school
respondent.total_students = total_students
respondent.total_teachers = total_teachers
respondent.academic_year = academic_year
respondent.save
end
Respondent.where.not(school: schools).destroy_all
end
def seed_sqm_framework(csv_file)
CSV.parse(File.read(csv_file), headers: true) do |row|
category_id = row['Category ID'].strip

@ -0,0 +1,4 @@
class Respondent < ApplicationRecord
belongs_to :school
belongs_to :academic_year
end

@ -7,9 +7,7 @@ class SurveyItemResponse < ActiveRecord::Base
belongs_to :survey_item
def self.score_for_subcategory(subcategory:, school:, academic_year:)
measures = subcategory.measures.select do |measure|
sufficient_data?(measure: measure, school: school, academic_year: academic_year)
end
measures = measures_with_sufficient_data(subcategory: subcategory, school: school, academic_year: academic_year)
return nil unless measures.size.positive?
@ -18,6 +16,27 @@ class SurveyItemResponse < ActiveRecord::Base
end.average
end
def self.average_number_of_student_respondents(subcategory:, school:, academic_year:)
response_count = subcategory.measures.map do |measure|
next 0 unless measure.includes_student_survey_items?
SurveyItemResponse.student_responses_for_measure(measure, school, academic_year).count
end.sum
survey_item_count = subcategory.measures.map do |measure|
measure.student_survey_items.count
end.sum
return 0 unless survey_item_count.positive?
response_count / survey_item_count
end
def self.measures_with_sufficient_data(subcategory:, school:, academic_year:)
subcategory.measures.select do |measure|
sufficient_data?(measure: measure, school: school, academic_year: academic_year)
end
end
def self.responses_for_measure(measure:, school:, academic_year:)
meets_teacher_threshold = teacher_sufficient_data? measure: measure, school: school, academic_year: academic_year
meets_student_threshold = student_sufficient_data? measure: measure, school: school, academic_year: academic_year
@ -88,4 +107,5 @@ class SurveyItemResponse < ActiveRecord::Base
end
!!meets_teacher_threshold
end
private_class_method :measures_with_sufficient_data
end

@ -30,6 +30,13 @@ class SubcategoryPresenter
academic_year: @academic_year)
end
def student_response_rate
@student_response_rate ||= response_rate(type: :total_students) do
SurveyItemResponse.average_number_of_student_respondents(subcategory: @subcategory, school: @school,
academic_year: @academic_year)
end
end
def measure_presenters
@subcategory.measures.includes([:admin_data_items]).sort_by(&:measure_id).map do |measure|
MeasurePresenter.new(measure: measure, academic_year: @academic_year, school: @school)
@ -50,4 +57,16 @@ class SubcategoryPresenter
def measures
@measures ||= @subcategory.measures.includes([:admin_data_items]).order(:measure_id)
end
def response_rate(type:)
number_of_responses = yield
total_responses = Respondent.where(school: @school, academic_year: @academic_year).first
return 0 unless total_responses.present?
total_possible_responses = total_responses.send(type)
return 0 if number_of_responses.nil? || total_possible_responses == 0
(number_of_responses / total_possible_responses * 100).round
end
end

@ -7,7 +7,23 @@
<div>
<%= render partial: "gauge_graph", locals: { gauge: subcategory.gauge_presenter, gauge_class: 'gauge-graph-lg', font_class: 'sub-header-3' } %>
</div>
<p class="body-large mx-7"><%= subcategory.description %></p>
<div class="d-flex flex-column mx-7">
<p class="body-large "><%= subcategory.description %></p>
<div class="d-flex justify-content-start">
<div class="body-large text-center response-rate">
<p class="response-rate-percentage"><%= subcategory.student_response_rate %>%</p>
<p>of students responded</p>
</div>
<%# <div class="body-large mx-3 text-center response-rate"> %>
<%# <p class="response-rate-percentage"><%= subcategory.teacher_response_rate %1>%</p> %>
<%# <p>of teachers responded</p> %>
<%# <p> %>
<%# <%= subcategory.total_teachers %1> %>
<%# </p> %>
</%#>
</div>
</div>
</div>
</div>

@ -1,4 +1,4 @@
District,School Name,District Code,School Code,DESE School ID,School Closed In ,Total Students for Response Rate,Total Teachers for Response Rate
District,School Name,District Code,School Code,DESE School ID,School Closed In ,Total Students for Response Rate (2020-21),Total Teachers for Response Rate (2020-21)
Attleboro,A. Irvin Studley Elementary School,1,7,00160001,,75,26.90
Attleboro,Thomas Willett Elementary School,1,9,00160035,,70,26.40
Attleboro,Hyman Fine Elementary School,1,6,00160040,,86,31.50

1 District School Name District Code School Code DESE School ID School Closed In Total Students for Response Rate Total Students for Response Rate (2020-21) Total Teachers for Response Rate Total Teachers for Response Rate (2020-21)
2 Attleboro A. Irvin Studley Elementary School 1 7 00160001 75 26.90
3 Attleboro Thomas Willett Elementary School 1 9 00160035 70 26.40
4 Attleboro Hyman Fine Elementary School 1 6 00160040 86 31.50

@ -0,0 +1,12 @@
class CreateRespondents < ActiveRecord::Migration[7.0]
def change
create_table :respondents do |t|
t.references :school, null: false, foreign_key: true
t.references :academic_year, null: false, foreign_key: true
t.float :total_students
t.float :total_teachers
t.timestamps
end
end
end

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_12_17_164449) do
ActiveRecord::Schema.define(version: 2022_01_24_144902) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
@ -272,6 +272,17 @@ ActiveRecord::Schema.define(version: 2021_12_17_164449) do
t.index ["subcategory_id"], name: "index_measures_on_subcategory_id"
end
create_table "respondents", force: :cascade do |t|
t.bigint "school_id", null: false
t.bigint "academic_year_id", null: false
t.float "total_students"
t.float "total_teachers"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["academic_year_id"], name: "index_respondents_on_academic_year_id"
t.index ["school_id"], name: "index_respondents_on_school_id"
end
create_table "schools", force: :cascade do |t|
t.string "name"
t.integer "district_id"
@ -327,6 +338,8 @@ ActiveRecord::Schema.define(version: 2021_12_17_164449) do
add_foreign_key "legacy_school_categories", "legacy_categories", column: "category_id"
add_foreign_key "legacy_school_categories", "legacy_schools", column: "school_id"
add_foreign_key "measures", "subcategories"
add_foreign_key "respondents", "academic_years"
add_foreign_key "respondents", "schools"
add_foreign_key "subcategories", "categories"
add_foreign_key "survey_item_responses", "academic_years"
add_foreign_key "survey_item_responses", "schools"

@ -4,4 +4,5 @@ seeder = Seeder.new
seeder.seed_academic_years '2020-21', '2021-22'
seeder.seed_districts_and_schools Rails.root.join('data', 'master_list_of_schools_and_districts.csv')
seeder.seed_respondents Rails.root.join('data', 'master_list_of_schools_and_districts.csv')
seeder.seed_sqm_framework Rails.root.join('data', 'sqm_framework.csv')

@ -86,4 +86,9 @@ FactoryBot.define do
description { rand.to_s }
measure
end
factory :respondent do
school
academic_year
end
end

@ -1,3 +1,3 @@
District,School Name,District Code,School Code,DESE School ID
Attleboro,Attleboro High School,1,1,00160505
Boston,Samuel Adams Elementary School,2,1,00350302
District,School Name,District Code,School Code,DESE School ID,School Closed In ,Total Students for Response Rate (2020-21),Total Teachers for Response Rate (2020-21)
Attleboro,Attleboro High School,1,1,00160505,,1792,114.70
Boston,Samuel Adams Elementary School,2,1,00350302,,79,28.40

1 District School Name District Code School Code DESE School ID School Closed In Total Students for Response Rate (2020-21) Total Teachers for Response Rate (2020-21)
2 Attleboro Attleboro High School 1 1 00160505 1792 114.70
3 Boston Samuel Adams Elementary School 2 1 00350302 79 28.40

@ -41,7 +41,7 @@ describe Seeder do
context 'when partial data already exists' do
let!(:existing_district) { create(:district, name: 'Boston') }
let!(:removed_school) do
create(:school, name: 'John Oldes Academy', dese_id: 12_345, district: existing_district)
create(:school, name: 'John Oldest Academy', dese_id: 12_345, district: existing_district)
end
let!(:removed_survey_item_response) { create(:survey_item_response, school: removed_school) }
let!(:existing_school) do
@ -87,6 +87,31 @@ describe Seeder do
end
end
context 'respondents' do
before :each do
create(:academic_year, range: '2020-21')
seeder.seed_districts_and_schools sample_districts_and_schools_csv
end
it 'seeds the total number of respondents for a school' do
expect do
seeder.seed_respondents sample_districts_and_schools_csv
end.to change { Respondent.count }.by(2)
end
it 'seeds idempotently' do
expect do
seeder.seed_respondents sample_districts_and_schools_csv
end.to change { Respondent.count }.by(2)
expect(Respondent.all.count).to eq 2
expect do
seeder.seed_respondents sample_districts_and_schools_csv
end.to change { Respondent.count }.by(0)
end
end
context 'the sqm framework' do
before do
school_culture_category = create(:category, category_id: '2', sort_index: -1)

@ -27,10 +27,15 @@ describe SurveyItemResponse, type: :model do
academic_year: ay).average).to eq 4
end
it 'affirms that the result meets the threshold' do
it 'affirms that the result meets the teacher threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_teacher_threshold?).to be true
end
it 'reports the result does not meeet student threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_student_threshold?).to be false
end
end
context "and the average number of responses across the measure's survey items meets the teacher threshold of 17" do
@ -113,10 +118,14 @@ describe SurveyItemResponse, type: :model do
academic_year: ay).average).to eq 4
end
it 'affirms that the result meets the threshold' do
it 'affirms that the result meets the student threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_student_threshold?).to be true
end
it 'notes that the result does not meet the teacher threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_teacher_threshold?).to be false
end
end
context "and the average number of responses across the measure's survey items meets the student threshold of 196" do
@ -297,4 +306,61 @@ describe SurveyItemResponse, type: :model do
academic_year: ay)).to eq 2.5
end
end
describe '.responses_for_measure' do
let(:subcategory) { create(:subcategory) }
let(:sufficient_measure_1) { create(:measure, subcategory: subcategory) }
let(:sufficient_measure_2) { create(:measure, subcategory: subcategory) }
let(:insufficient_measure) { create(:measure, subcategory: subcategory) }
let(:sufficient_teacher_survey_item) { create(:teacher_survey_item, measure: sufficient_measure_1) }
let(:insufficient_teacher_survey_item) { create(:teacher_survey_item, measure: insufficient_measure) }
let(:sufficient_student_survey_item) { create(:student_survey_item, measure: sufficient_measure_2) }
let(:insufficient_student_survey_item) { create(:student_survey_item, measure: insufficient_measure) }
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: sufficient_teacher_survey_item,
academic_year: ay, school: school, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD, survey_item: sufficient_student_survey_item,
academic_year: ay, school: school, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD - 1,
survey_item: insufficient_teacher_survey_item, academic_year: ay, school: school, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1,
survey_item: insufficient_student_survey_item, academic_year: ay, school: school, likert_score: 1)
end
it 'returns only responses in a measure that meets the low threshold' do
expect(SurveyItemResponse.responses_for_measure(measure: sufficient_measure_1, school: school, academic_year: ay).count).to eq SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD
expect(SurveyItemResponse.responses_for_measure(measure: sufficient_measure_2, school: school, academic_year: ay).count).to eq SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD
expect(SurveyItemResponse.responses_for_measure(measure: insufficient_measure, school: school, academic_year: ay)).to be nil
end
end
describe '.average_number_of_student_respondents' do
let(:subcategory) { create(:subcategory) }
let(:sufficient_measure_1) { create(:measure, subcategory: subcategory) }
let(:sufficient_measure_2) { create(:measure, subcategory: subcategory) }
let(:insufficient_measure) { create(:measure, subcategory: subcategory) }
let(:sufficient_teacher_survey_item) { create(:teacher_survey_item, measure: sufficient_measure_1) }
let(:sufficient_student_survey_item_1) { create(:student_survey_item, measure: sufficient_measure_1) }
let(:insufficient_teacher_survey_item) { create(:teacher_survey_item, measure: insufficient_measure) }
let(:sufficient_student_survey_item_2) { create(:student_survey_item, measure: sufficient_measure_2) }
let(:insufficient_student_survey_item) { create(:student_survey_item, measure: insufficient_measure) }
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: sufficient_teacher_survey_item,
academic_year: ay, school: school, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD, survey_item: sufficient_student_survey_item_1,
academic_year: ay, school: school, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD + 1, survey_item: sufficient_student_survey_item_2,
academic_year: ay, school: school, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD - 1,
survey_item: insufficient_teacher_survey_item, academic_year: ay, school: school, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1,
survey_item: insufficient_student_survey_item, academic_year: ay, school: school, likert_score: 1)
end
it 'returns only responses in a measure that meets the low threshold' do
expect(SurveyItemResponse.average_number_of_student_respondents(subcategory: subcategory, school: school, academic_year: ay)).to eq SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD
end
end
end

@ -6,22 +6,38 @@ describe SubcategoryPresenter do
let(:subcategory) do
create(:subcategory, name: 'A great subcategory', subcategory_id: 'A', description: 'A great description')
end
let(:survey_respondents) do
create(:respondent, school: school, total_students: SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD, total_teachers: 10.0, academic_year: academic_year)
end
let(:subcategory_presenter) do
survey_respondents
measure1 = create(:measure, subcategory: subcategory)
survey_item1 = create(:teacher_survey_item, measure: measure1, watch_low_benchmark: 4, growth_low_benchmark: 4.25,
approval_low_benchmark: 4.5, ideal_low_benchmark: 4.75)
survey_item2 = create(:student_survey_item, measure: measure1, watch_low_benchmark: 4, growth_low_benchmark: 4.25,
approval_low_benchmark: 4.5, ideal_low_benchmark: 4.75)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item1,
academic_year: academic_year, school: school, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item1,
academic_year: academic_year, school: school, likert_score: 5)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD / 2, survey_item: survey_item2,
academic_year: academic_year, school: school, likert_score: 3)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD / 2, survey_item: survey_item2,
academic_year: academic_year, school: school, likert_score: 3)
measure2 = create(:measure, subcategory: subcategory)
survey_item2 = create(:teacher_survey_item, measure: measure2, watch_low_benchmark: 1.25,
survey_item3 = create(:teacher_survey_item, measure: measure2, watch_low_benchmark: 1.25,
growth_low_benchmark: 1.5, approval_low_benchmark: 1.75, ideal_low_benchmark: 2.0)
survey_item4 = create(:student_survey_item, measure: measure2, watch_low_benchmark: 1.25,
growth_low_benchmark: 1.5, approval_low_benchmark: 1.75, ideal_low_benchmark: 2.0)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item2,
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item3,
academic_year: academic_year, school: school, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item2,
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item3,
academic_year: academic_year, school: school, likert_score: 5)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD / 2, survey_item: survey_item4,
academic_year: academic_year, school: school, likert_score: 3)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD / 2, survey_item: survey_item4,
academic_year: academic_year, school: school, likert_score: 3)
measure_of_only_admin_data = create(:measure, subcategory: subcategory)
create(:admin_data_item, measure: measure_of_only_admin_data, watch_low_benchmark: 2, growth_low_benchmark: 3,
@ -49,6 +65,14 @@ describe SubcategoryPresenter do
expect(subcategory_presenter.gauge_presenter.title).to eq 'Growth'
end
it 'returns the student response rate' do
expect(subcategory_presenter.student_response_rate).to eq 100.0
end
# it 'returns the teacher response rate' do
# expect(subcategory_presenter.teacher_response_rate).to eq 20.0
# end
it 'creates a measure presenter for each measure in a subcategory' do
expect(subcategory_presenter.measure_presenters.count).to eq subcategory.measures.count
end

Loading…
Cancel
Save