Add scales to framework. Change calculations to first group and then

average those groupings and the way up the framework.  Likert scores for
a survey_item are averaged.  Then all the survey_items in a scale are
averaged.  Then student scales in a measure are averaged.  And teacher
scales in a measure are averaged.  Then the average of those two
calculations becomes the score for a measure.  Then the measures in a
subcategory are averaged.
pull/1/head
rebuilt 4 years ago
parent 1ca88bf6d1
commit d4df7cbc06

@ -26,10 +26,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bump ruby version to 3.1.0
- Add previous year data.
remove AcademicYear "2021-22"
`ay = AcademicYear.find_by_range("2021-22") `
`ay.destroy`
seed database with new AcademicYear
`bundle exec rake db:seed`
load previous year survey responses
`bundle exec rake data:load_survey_responses`
- Add scale to framework. Calculations for scores bubble up through the framework.

@ -7,7 +7,7 @@ class OverviewController < SqmApplicationController
private
def presenter_for_measure(measure)
score = SurveyItemResponse.score_for_measure(measure: measure, school: @school, academic_year: @academic_year)
score = measure.score(school: @school, academic_year: @academic_year)
VarianceChartRowPresenter.new(measure: measure, score: score)
end

@ -16,7 +16,7 @@ class SqmApplicationController < ApplicationController
@schools = School.includes([:district]).where(district: @district).order(:name)
@academic_year = AcademicYear.find_by_range params[:year]
@academic_years = AcademicYear.all.order(range: :desc)
@has_empty_dataset = Measure.none_meet_threshold? school: @school, academic_year: @academic_year
@has_empty_dataset = Measure.all.all? do |measure| measure.none_meet_threshold? school: @school, academic_year: @academic_year end
end
def district_slug

@ -3,7 +3,7 @@ require 'csv'
class Seeder
def seed_academic_years(*academic_year_ranges)
academic_year_ranges.each do |range|
AcademicYear.find_or_create_by! range: range
AcademicYear.find_or_create_by! range:
end
end
@ -89,8 +89,11 @@ class Seeder
measure.save!
data_item_id = row['Survey Item ID'].strip
scale_id = data_item_id.split('-')[0..1].join('-')
scale = Scale.find_or_create_by! scale_id: scale_id, measure: measure
if %w[Teachers Students].include? row['Source']
survey_item = SurveyItem.find_or_create_by! survey_item_id: data_item_id, measure: measure
survey_item = SurveyItem.where(survey_item_id: data_item_id, scale:).first_or_create
survey_item.watch_low_benchmark = watch_low if watch_low
survey_item.growth_low_benchmark = growth_low if growth_low
survey_item.approval_low_benchmark = approval_low if approval_low
@ -99,7 +102,7 @@ class Seeder
end
if row['Source'] == 'Admin Data'
admin_data_item = AdminDataItem.find_or_create_by! admin_data_item_id: data_item_id, measure: measure
admin_data_item = AdminDataItem.where(admin_data_item_id: data_item_id, scale:).first_or_create
admin_data_item.watch_low_benchmark = watch_low if watch_low
admin_data_item.growth_low_benchmark = growth_low if growth_low
admin_data_item.approval_low_benchmark = approval_low if approval_low

@ -1,11 +1,11 @@
class AdminDataItem < ActiveRecord::Base
belongs_to :measure
belongs_to :scale
scope :for_measures, ->(measure) {
joins(:measure).where('admin_data_items.measure': measure)
scope :for_measures, lambda { |measures|
joins(:scale).where('scale.measure': measures)
}
scope :non_hs_items_for_measures, ->(measure) {
scope :non_hs_items_for_measures, lambda { |measure|
for_measures(measure).where(hs_only_item: false)
}
end

@ -1,34 +1,40 @@
class Measure < ActiveRecord::Base
belongs_to :subcategory
has_many :survey_items
has_many :admin_data_items
has_many :scales
has_many :admin_data_items, through: :scales
has_many :survey_items, through: :scales
has_many :survey_item_responses, through: :survey_items
def self.none_meet_threshold?(school:, academic_year:)
none? do |measure|
SurveyItemResponse.sufficient_data?(measure: measure, school: school, academic_year: academic_year)
end
def none_meet_threshold?(school:, academic_year:)
!sufficient_data?(school:, academic_year:)
end
def teacher_survey_items
@teacher_survey_items ||= survey_items.where("survey_item_id LIKE 't-%'")
@teacher_survey_items ||= survey_items.teacher_survey_items
end
def student_survey_items
@student_survey_items ||= survey_items.where("survey_item_id LIKE 's-%'")
@student_survey_items ||= survey_items.student_survey_items
end
def teacher_scales
@teacher_scales ||= scales.teacher_scales
end
def student_scales
@student_scales ||= scales.student_scales
end
def includes_teacher_survey_items?
@includes_teacher_survey_items ||= teacher_survey_items.any?
teacher_survey_items.any?
end
def includes_student_survey_items?
@includes_student_survey_items ||= student_survey_items.any?
student_survey_items.any?
end
def includes_admin_data_items?
@includes_admin_data_items ||= admin_data_items.any?
admin_data_items.any?
end
def sources
@ -39,32 +45,61 @@ class Measure < ActiveRecord::Base
sources
end
def score(school:, academic_year:)
@score ||= Hash.new do |memo|
meets_student_threshold = sufficient_student_data?(school:, academic_year:)
meets_teacher_threshold = sufficient_teacher_data?(school:, academic_year:)
next Score.new(nil, false, false) if !meets_student_threshold && !meets_teacher_threshold
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
memo[[school, academic_year]] = Score.new(scores.average, meets_teacher_threshold, meets_student_threshold)
end
@score[[school, academic_year]]
end
def warning_low_benchmark
1
end
def watch_low_benchmark
return @watch_low_benchmark unless @watch_low_benchmark.nil?
@watch_low_benchmark = benchmark(:watch_low_benchmark)
@watch_low_benchmark ||= benchmark(:watch_low_benchmark)
end
def growth_low_benchmark
return @growth_low_benchmark unless @growth_low_benchmark.nil?
@growth_low_benchmark = benchmark(:growth_low_benchmark)
@growth_low_benchmark ||= benchmark(:growth_low_benchmark)
end
def approval_low_benchmark
return @approval_low_benchmark unless @approval_low_benchmark.nil?
@approval_low_benchmark = benchmark(:approval_low_benchmark)
@approval_low_benchmark ||= benchmark(:approval_low_benchmark)
end
def ideal_low_benchmark
return @ideal_low_benchmark unless @ideal_low_benchmark.nil?
@ideal_low_benchmark ||= benchmark(:ideal_low_benchmark)
end
def sufficient_student_data?(school:, academic_year:)
return false unless includes_student_survey_items?
average_response_count = student_survey_items.map do |survey_item|
survey_item.survey_item_responses.where(school:, academic_year:).count
end.average
average_response_count >= SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD
end
def sufficient_teacher_data?(school:, academic_year:)
return false unless includes_teacher_survey_items?
average_response_count = teacher_survey_items.map do |survey_item|
survey_item.survey_item_responses.where(school:, academic_year:).count
end.average
average_response_count >= SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD
end
@ideal_low_benchmark = benchmark(:ideal_low_benchmark)
def sufficient_data?(school:, academic_year:)
sufficient_student_data?(school:, academic_year:) || sufficient_teacher_data?(school:, academic_year:)
end
private

@ -1,4 +1,4 @@
class ResponseRate
module ResponseRate
def initialize(subcategory:, school:, academic_year:)
@subcategory = subcategory
@school = school

@ -0,0 +1,21 @@
class Scale < ApplicationRecord
belongs_to :measure
has_many :survey_items
has_many :admin_data_items
def score(school:, academic_year:)
@score ||= Hash.new do |memo|
memo[[school, academic_year]] = survey_items.map do |survey_item|
survey_item.score(school:, academic_year:)
end.average
end
@score[[school, academic_year]]
end
scope :teacher_scales, lambda {
where("scale_id LIKE 't-%'")
}
scope :student_scales, lambda {
where("scale_id LIKE 's-%'")
}
end

@ -1,17 +1,18 @@
class StudentResponseRate < ResponseRate
def rate
super
end
class StudentResponseRate
include ResponseRate
private
def survey_item_count
@student_survey_item_count ||= SurveyItem.student_survey_items_for_measures(@subcategory.measures).count
@student_survey_item_count ||= @subcategory.measures.map { |measure| measure.student_survey_items.count }.sum
end
def response_count
@student_response_count ||= SurveyItemResponse.student_responses_for_measures(@subcategory.measures, @school,
@academic_year).count
@student_response_count ||= @subcategory.measures.map do |measure|
measure.student_survey_items.map do |survey_item|
survey_item.survey_item_responses.where(school: @school, academic_year: @academic_year).count
end.sum
end.sum
end
def total_possible_responses

@ -2,4 +2,12 @@ class Subcategory < ActiveRecord::Base
belongs_to :category
has_many :measures
def score(school:, academic_year:)
scores = measures.includes([:survey_items]).map do |measure|
measure.score(school:, academic_year:).average
end
scores = scores.reject(&:nil?)
scores.average
end
end

@ -1,11 +1,21 @@
class SurveyItem < ActiveRecord::Base
belongs_to :measure
belongs_to :scale
has_one :measure, through: :scale
has_many :survey_item_responses
scope :student_survey_items_for_measures, lambda { |measures|
joins(:measure).where(measure: measures).where("survey_item_id LIKE 's-%'")
def score(school:, academic_year:)
@score ||= Hash.new do |memo|
memo[[school, academic_year]] = survey_item_responses.where(school:, academic_year:).average(:likert_score).to_f
end
@score[[school, academic_year]]
end
scope :student_survey_items, lambda {
where("survey_item_id LIKE 's-%'")
}
scope :teacher_survey_items_for_measures, lambda { |measures|
joins(:measure).where(measure: measures).where("survey_item_id LIKE 't-%'")
scope :teacher_survey_items, lambda {
where("survey_item_id LIKE 't-%'")
}
end

@ -5,100 +5,4 @@ class SurveyItemResponse < ActiveRecord::Base
belongs_to :academic_year
belongs_to :school
belongs_to :survey_item
def self.score_for_subcategory(subcategory:, school:, academic_year:)
measures = measures_with_sufficient_data(subcategory: subcategory, school: school, academic_year: academic_year)
return nil unless measures.size.positive?
measures.map do |measure|
responses_for_measure(measure: measure, school: school, academic_year: academic_year).average(:likert_score)
end.average
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
meets_all_thresholds = meets_teacher_threshold && meets_student_threshold
if meets_all_thresholds
SurveyItemResponse.for_measure(measure, school, academic_year)
elsif meets_teacher_threshold
SurveyItemResponse.teacher_responses_for_measure(measure, school, academic_year)
elsif meets_student_threshold
SurveyItemResponse.student_responses_for_measure(measure, school, academic_year)
end
end
def self.score_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
survey_item_responses = responses_for_measure(measure: measure, school: school, academic_year: academic_year)
score_for_measure = survey_item_responses.average(:likert_score) unless survey_item_responses.nil?
Score.new(score_for_measure, meets_teacher_threshold, meets_student_threshold)
end
def self.sufficient_data?(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
meets_teacher_threshold || meets_student_threshold
end
scope :for_measure, lambda { |measure, school, academic_year|
joins(:survey_item)
.where('survey_items.measure': measure)
.where(school: school, academic_year: academic_year)
}
scope :for_measures, lambda { |measures, school, academic_year|
joins(:survey_item)
.where('survey_items.measure': measures)
.where(school: school, academic_year: academic_year)
}
scope :teacher_responses_for_measure, lambda { |measure, school, academic_year|
for_measure(measure, school, academic_year)
.where("survey_items.survey_item_id LIKE 't-%'")
}
scope :teacher_responses_for_measures, lambda { |measures, school, academic_year|
for_measures(measures, school, academic_year)
.where("survey_items.survey_item_id LIKE 't-%'")
}
scope :student_responses_for_measure, lambda { |measure, school, academic_year|
for_measure(measure, school, academic_year)
.where("survey_items.survey_item_id LIKE 's-%'")
}
scope :student_responses_for_measures, lambda { |measures, school, academic_year|
for_measures(measures, school, academic_year)
.where("survey_items.survey_item_id LIKE 's-%'")
}
def self.student_sufficient_data?(measure:, school:, academic_year:)
if measure.includes_student_survey_items?
student_survey_item_responses = SurveyItemResponse.student_responses_for_measure(measure, school, academic_year)
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_student_threshold
end
def self.teacher_sufficient_data?(measure:, school:, academic_year:)
if measure.includes_teacher_survey_items?
teacher_survey_item_responses = SurveyItemResponse.teacher_responses_for_measure(measure, school, academic_year)
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
!!meets_teacher_threshold
end
private_class_method :measures_with_sufficient_data
end

@ -1,4 +1,6 @@
class TeacherResponseRate < ResponseRate
class TeacherResponseRate
include ResponseRate
def rate
cap_at_100(super)
end
@ -10,12 +12,15 @@ class TeacherResponseRate < ResponseRate
end
def survey_item_count
@teacher_survey_item_count ||= SurveyItem.teacher_survey_items_for_measures(@subcategory.measures).count
@teacher_survey_item_count ||= @subcategory.measures.map { |measure| measure.teacher_survey_items.count }.sum
end
def response_count
@teacher_response_count ||= SurveyItemResponse.teacher_responses_for_measures(@subcategory.measures, @school,
@academic_year).count
@teacher_response_count ||= @subcategory.measures.map do |measure|
measure.teacher_survey_items.map do |survey_item|
survey_item.survey_item_responses.where(school: @school, academic_year: @academic_year).count
end.sum
end.sum
end
def total_possible_responses

@ -1,6 +1,6 @@
class GaugePresenter
def initialize(scale:, score:)
@scale = scale
def initialize(zones:, score:)
@zones = zones
@score = score
end
@ -17,7 +17,7 @@ class GaugePresenter
end
def key_benchmark_percentage
percentage_for @scale.approval_zone.low_benchmark
percentage_for @zones.approval_zone.low_benchmark
end
def boundary_percentage_for(zone)
@ -38,31 +38,31 @@ class GaugePresenter
private
def watch_low_boundary
percentage_for @scale.watch_zone.low_benchmark
percentage_for @zones.watch_zone.low_benchmark
end
def growth_low_boundary
percentage_for @scale.growth_zone.low_benchmark
percentage_for @zones.growth_zone.low_benchmark
end
def approval_low_boundary
percentage_for @scale.approval_zone.low_benchmark
percentage_for @zones.approval_zone.low_benchmark
end
def ideal_low_boundary
percentage_for @scale.ideal_zone.low_benchmark
percentage_for @zones.ideal_zone.low_benchmark
end
def zone
@scale.zone_for_score(@score)
@zones.zone_for_score(@score)
end
def percentage_for(number)
return nil if number.nil?
scale_minimum = @scale.warning_zone.low_benchmark
scale_maximum = @scale.ideal_zone.high_benchmark
zones_minimum = @zones.warning_zone.low_benchmark
zones_maximum = @zones.ideal_zone.high_benchmark
(number - scale_minimum) / (scale_maximum - scale_minimum)
(number - zones_minimum) / (zones_maximum - zones_minimum)
end
end

@ -18,7 +18,7 @@ class MeasurePresenter
end
def gauge_presenter
GaugePresenter.new(scale: scale, score: score_for_measure.average)
GaugePresenter.new(zones:, score: score_for_measure.average)
end
def data_item_accordion_id
@ -45,11 +45,11 @@ class MeasurePresenter
private
def score_for_measure
@score ||= SurveyItemResponse.score_for_measure(measure: @measure, academic_year: @academic_year, school: @school)
@score ||= @measure.score(school: @school, academic_year: @academic_year)
end
def scale
Scale.new(
def zones
Zones.new(
watch_low_benchmark: @measure.watch_low_benchmark,
growth_low_benchmark: @measure.growth_low_benchmark,
approval_low_benchmark: @measure.approval_low_benchmark,

@ -1,9 +1,9 @@
class SubcategoryCardPresenter
attr_reader :name
def initialize(name:, scale:, score:)
def initialize(name:, zones:, score:)
@name = name
@scale = scale
@zones = zones
@score = score
end
@ -22,6 +22,6 @@ class SubcategoryCardPresenter
private
def zone
@scale.zone_for_score(@score)
@zones.zone_for_score(@score)
end
end

@ -22,16 +22,15 @@ class SubcategoryPresenter
end
def gauge_presenter
GaugePresenter.new(scale:, score: average_score)
GaugePresenter.new(zones:, score: average_score)
end
def subcategory_card_presenter
SubcategoryCardPresenter.new(name: @subcategory.name, scale:, score: average_score)
SubcategoryCardPresenter.new(name: @subcategory.name, zones:, score: average_score)
end
def average_score
@average_score ||= SurveyItemResponse.score_for_subcategory(subcategory: @subcategory, school: @school,
academic_year: @academic_year)
@average_score ||= @subcategory.score(school: @school, academic_year: @academic_year)
end
def student_response_rate
@ -48,7 +47,7 @@ class SubcategoryPresenter
end
def measure_presenters
@subcategory.measures.includes([:admin_data_items]).sort_by(&:measure_id).map do |measure|
@subcategory.measures.sort_by(&:measure_id).map do |measure|
MeasurePresenter.new(measure:, academic_year: @academic_year, school: @school)
end
end
@ -56,19 +55,17 @@ class SubcategoryPresenter
private
def admin_data_item_count
if @school.is_hs
AdminDataItem.for_measures(@subcategory.measures).count
else
AdminDataItem.non_hs_items_for_measures(@subcategory.measures).count
end
return AdminDataItem.for_measures(@subcategory.measures).count if @school.is_hs
AdminDataItem.non_hs_items_for_measures(@subcategory.measures).count
end
def format_a_non_applicable_rate(rate)
rate == [0, 0] ? %w[N A] : rate
end
def scale
Scale.new(
def zones
Zones.new(
watch_low_benchmark: measures.map(&:watch_low_benchmark).average,
growth_low_benchmark: measures.map(&:growth_low_benchmark).average,
approval_low_benchmark: measures.map(&:approval_low_benchmark).average,

@ -96,12 +96,12 @@ class VarianceChartRowPresenter
end
def zone
scale = Scale.new(
zones = Zones.new(
watch_low_benchmark: @measure.watch_low_benchmark,
growth_low_benchmark: @measure.growth_low_benchmark,
approval_low_benchmark: @measure.approval_low_benchmark,
ideal_low_benchmark: @measure.ideal_low_benchmark
)
scale.zone_for_score(@score)
zones.zone_for_score(@score)
end
end

@ -1,4 +1,4 @@
class Scale
class Zones
def initialize(watch_low_benchmark:, growth_low_benchmark:, approval_low_benchmark:, ideal_low_benchmark:)
@watch_low_benchmark = watch_low_benchmark
@growth_low_benchmark = growth_low_benchmark
@ -34,9 +34,10 @@ class Scale
end
def zone_for_score(score)
return insufficient_data if score.nil?
return insufficient_data if score.is_a?(Float) && score.nan?
case score
when nil
insufficient_data
when ideal_zone.low_benchmark..ideal_zone.high_benchmark
ideal_zone
when approval_zone.low_benchmark..approval_zone.high_benchmark

@ -1,7 +1,7 @@
Rails.application.configure do
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.alert = false
Bullet.bullet_logger = true
Bullet.console = true
# Bullet.growl = true

@ -0,0 +1,10 @@
class CreateScales < ActiveRecord::Migration[7.0]
def change
create_table :scales do |t|
t.string :scale_id, index: { unique: true }, null: false
t.references :measure, null: false, foreign_key: true
t.timestamps
end
end
end

@ -0,0 +1,6 @@
class AddScaleToSurveyItem < ActiveRecord::Migration[7.0]
def change
add_reference :survey_items, :scale, null: false, foreign_key: true
remove_reference :survey_items, :measure, null: false, foreign_key: true
end
end

@ -0,0 +1,7 @@
class AddScaleToAdminDataItem < ActiveRecord::Migration[7.0]
def change
add_reference :admin_data_items, :scale, null: false, foreign_key: true
remove_reference :admin_data_items, :measure
add_index :admin_data_items, :admin_data_item_id, unique: true
end
end

@ -10,10 +10,8 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_02_11_122234) do
ActiveRecord::Schema[7.0].define(version: 2022_02_17_170442) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
enable_extension "plpgsql"
create_table "academic_years", id: :serial, force: :cascade do |t|
@ -22,16 +20,18 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
end
create_table "admin_data_items", force: :cascade do |t|
t.integer "measure_id", null: false
t.string "admin_data_item_id", null: false
t.string "description"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.float "watch_low_benchmark"
t.float "growth_low_benchmark"
t.float "approval_low_benchmark"
t.float "ideal_low_benchmark"
t.boolean "hs_only_item", default: false
t.bigint "scale_id", null: false
t.index ["admin_data_item_id"], name: "index_admin_data_items_on_admin_data_item_id", unique: true
t.index ["scale_id"], name: "index_admin_data_items_on_scale_id"
end
create_table "categories", id: :serial, force: :cascade do |t|
@ -39,8 +39,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.text "description"
t.string "slug"
t.integer "sort_index"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "category_id", null: false
t.string "short_description"
t.index ["slug"], name: "index_categories_on_slug", unique: true
@ -50,22 +50,22 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.string "name"
t.string "slug"
t.integer "qualtrics_code"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "legacy_attempts", id: :serial, force: :cascade do |t|
t.integer "recipient_id"
t.integer "schedule_id"
t.integer "recipient_schedule_id"
t.datetime "sent_at"
t.datetime "responded_at"
t.datetime "sent_at", precision: nil
t.datetime "responded_at", precision: nil
t.integer "question_id"
t.integer "translation_id"
t.integer "answer_index"
t.integer "open_response_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.text "twilio_details"
t.string "twilio_sid"
t.integer "student_id"
@ -78,8 +78,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.text "description"
t.string "external_id"
t.integer "parent_category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.string "slug"
t.float "benchmark"
t.string "benchmark_description"
@ -90,8 +90,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
create_table "legacy_districts", id: :serial, force: :cascade do |t|
t.string "name"
t.integer "state_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.string "slug"
t.integer "qualtrics_code"
t.index ["slug"], name: "index_legacy_districts_on_slug", unique: true
@ -101,8 +101,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.string "name"
t.text "description"
t.text "question_ids"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
end
create_table "legacy_questions", id: :serial, force: :cascade do |t|
@ -113,8 +113,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.string "option4"
t.string "option5"
t.integer "category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "target_group", default: 0
t.boolean "for_recipient_students", default: false
t.boolean "reverse", default: false
@ -126,8 +126,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.string "name"
t.text "description"
t.text "recipient_ids"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["school_id"], name: "index_legacy_recipient_lists_on_school_id"
end
@ -136,10 +136,10 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.integer "schedule_id"
t.text "upcoming_question_ids"
t.text "attempted_question_ids"
t.datetime "last_attempt_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "next_attempt_at"
t.datetime "last_attempt_at", precision: nil
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.datetime "next_attempt_at", precision: nil
t.string "queued_question_ids"
end
@ -154,8 +154,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.string "income"
t.boolean "opted_out", default: false
t.integer "school_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.string "email"
t.string "slug"
t.integer "attempts_count", default: 0
@ -176,8 +176,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.boolean "random", default: false
t.integer "recipient_list_id"
t.integer "question_list_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "time", default: 960
t.index ["school_id"], name: "index_legacy_schedules_on_school_id"
end
@ -188,8 +188,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.integer "attempt_count", default: 0
t.integer "response_count", default: 0
t.integer "answer_index_total", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.float "nonlikert"
t.float "zscore"
t.string "year"
@ -207,16 +207,16 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.integer "response_count"
t.float "response_rate"
t.string "year"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "response_total"
end
create_table "legacy_schools", id: :serial, force: :cascade do |t|
t.string "name"
t.integer "district_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.text "description"
t.string "slug"
t.integer "student_count"
@ -233,31 +233,31 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.string "age"
t.string "ethnicity"
t.integer "recipient_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
end
create_table "legacy_user_schools", id: :serial, force: :cascade do |t|
t.integer "user_id"
t.integer "school_id"
t.integer "district_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
end
create_table "legacy_users", id: :serial, force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "reset_password_sent_at", precision: nil
t.datetime "remember_created_at", precision: nil
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.datetime "current_sign_in_at", precision: nil
t.datetime "last_sign_in_at", precision: nil
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["email"], name: "index_legacy_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_legacy_users_on_reset_password_token", unique: true
end
@ -267,8 +267,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.string "name"
t.integer "subcategory_id", null: false
t.text "description"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["measure_id"], name: "index_measures_on_measure_id"
t.index ["subcategory_id"], name: "index_measures_on_subcategory_id"
end
@ -278,20 +278,29 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
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.datetime "created_at", null: false
t.datetime "updated_at", 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 "scales", force: :cascade do |t|
t.string "scale_id", null: false
t.bigint "measure_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["measure_id"], name: "index_scales_on_measure_id"
t.index ["scale_id"], name: "index_scales_on_scale_id", unique: true
end
create_table "schools", force: :cascade do |t|
t.string "name"
t.integer "district_id"
t.text "description"
t.string "slug"
t.integer "qualtrics_code"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "dese_id", null: false
t.boolean "is_hs", default: false
t.index ["dese_id"], name: "index_schools_on_dese_id", unique: true
@ -301,8 +310,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.string "name"
t.integer "category_id"
t.text "description"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "subcategory_id", null: false
end
@ -312,8 +321,8 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
t.integer "survey_item_id", null: false
t.string "response_id", null: false
t.integer "academic_year_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["academic_year_id"], name: "index_survey_item_responses_on_academic_year_id"
t.index ["response_id"], name: "index_survey_item_responses_on_response_id"
t.index ["school_id"], name: "index_survey_item_responses_on_school_id"
@ -321,20 +330,20 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
end
create_table "survey_items", id: :serial, force: :cascade do |t|
t.integer "measure_id", null: false
t.string "survey_item_id", null: false
t.string "prompt"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.float "watch_low_benchmark"
t.float "growth_low_benchmark"
t.float "approval_low_benchmark"
t.float "ideal_low_benchmark"
t.index ["measure_id"], name: "index_survey_items_on_measure_id"
t.bigint "scale_id", null: false
t.index ["scale_id"], name: "index_survey_items_on_scale_id"
t.index ["survey_item_id"], name: "index_survey_items_on_survey_item_id"
end
add_foreign_key "admin_data_items", "measures"
add_foreign_key "admin_data_items", "scales"
add_foreign_key "legacy_recipient_lists", "legacy_schools", column: "school_id"
add_foreign_key "legacy_schedules", "legacy_schools", column: "school_id"
add_foreign_key "legacy_school_categories", "legacy_categories", column: "category_id"
@ -342,9 +351,10 @@ ActiveRecord::Schema.define(version: 2022_02_11_122234) do
add_foreign_key "measures", "subcategories"
add_foreign_key "respondents", "academic_years"
add_foreign_key "respondents", "schools"
add_foreign_key "scales", "measures"
add_foreign_key "subcategories", "categories"
add_foreign_key "survey_item_responses", "academic_years"
add_foreign_key "survey_item_responses", "schools"
add_foreign_key "survey_item_responses", "survey_items"
add_foreign_key "survey_items", "measures"
add_foreign_key "survey_items", "scales"
end

@ -0,0 +1,26 @@
# Application decision record 2
# Add scale to framework
## Status
Completed
## Context
The calculations needed to be reworked. When calculating a score for a measure, we had looped through all the survey_items_responses for a measure and taken the average for all of them. Doing it that way means student responses are given more weight than teacher responses since the number of students responses will outnumber teacher responses. Another consequence is that survey_items with more questions will be prioritized over ones with fewer questions since they will create more survey item responses.
Story: #181205114 and #181209931
## Decision
Change the calculations so that scores bubble up through the framework hierarchy.
## Consequences
Added scales to framework. Changed calculations to first group based on the most atomic bits of the framework and then
average those groupings as we go up the framework. First, likert scores are averaged for
all survey item responses in a survey item. Then all the survey items in a scale are
averaged. Then student scales in a measure are averaged. And teacher
scales in a measure are averaged. The average of student and teacher scales becomes the score for a measure. Then the measures in a
subcategory are averaged.

@ -13,7 +13,7 @@ FactoryBot.define do
factory :academic_year do
range { '2050-51' }
initialize_with { AcademicYear.find_or_initialize_by(range: range) }
initialize_with { AcademicYear.find_or_initialize_by(range:) }
end
factory :category, class: 'Category' do
@ -35,9 +35,10 @@ FactoryBot.define do
measures_count { 2 }
end
after(:create) do |subcategory, evaluator|
create_list(:measure, evaluator.measures_count, subcategory: subcategory).each do |measure|
survey_item = create(:teacher_survey_item, measure: measure)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item)
create_list(:measure, evaluator.measures_count, subcategory:).each do |measure|
scale = create(:scale, measure: measure)
survey_item = create(:teacher_survey_item, scale:)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item:)
end
end
end
@ -54,9 +55,20 @@ FactoryBot.define do
# end
end
factory :scale do
measure
scale_id { "A Scale #{rand}" }
factory :teacher_scale do
scale_id {"t-#{rand}"}
end
factory :student_scale do
scale_id {"s-#{rand}"}
end
end
factory :survey_item do
scale
prompt { 'What do YOU think?' }
measure
factory :teacher_survey_item do
survey_item_id { "t-#{rand}" }
watch_low_benchmark { 2.0 }
@ -84,7 +96,7 @@ FactoryBot.define do
factory :admin_data_item do
admin_data_item_id { rand.to_s }
description { rand.to_s }
measure
scale
end
factory :respondent do

@ -123,9 +123,11 @@ describe Seeder do
before do
school_culture_category = create(:category, category_id: '2', sort_index: -1)
safety_subcategory = create(:subcategory, subcategory_id: '2A', category: school_culture_category)
student_physical_safety_measure = create(:measure, measure_id: '2A-i', subcategory: safety_subcategory)
create(:survey_item, survey_item_id: 's-phys-q1', measure: student_physical_safety_measure)
create(:admin_data_item, admin_data_item_id: 'a-phys-i1', measure: student_physical_safety_measure)
physical_safety_measure = create(:measure, measure_id: '2A-i', subcategory: safety_subcategory)
student_physical_safety_scale = create(:scale, scale_id: 's-phys', measure: physical_safety_measure)
create(:survey_item, survey_item_id: 's-phys-q1', scale: student_physical_safety_scale)
admin_physical_safety_scale = create(:scale, scale_id: 'a-phys', measure: physical_safety_measure)
create(:admin_data_item, admin_data_item_id: 'a-phys-i1', scale: admin_physical_safety_scale)
end
it 'creates new objects as necessary' do
@ -133,10 +135,12 @@ describe Seeder do
seeder.seed_sqm_framework sample_sqm_framework_csv
end.to change { Category.count }.by(4)
.and change { Subcategory.count }.by(15)
.and change { Measure.count }.by(31)
.and change {
SurveyItem.count
}.by(136)
.and change { Measure.count }.by(31).and change {
Scale.count
}.by(51)
.and change {
SurveyItem.count
}.by(136)
.and change {
AdminDataItem.count
}.by(32)
@ -174,6 +178,14 @@ describe Seeder do
expect(measure.description).to eq 'This is a measure description.'
end
it 'updates scale references' do
scale = Scale.find_by_scale_id 't-pcom'
measure = Measure.find_by_measure_id '1A-iii'
survey_item = SurveyItem.find_by_survey_item_id 't-pcom-q1'
expect(scale.measure).to eq measure
expect(scale.survey_items).to include survey_item
end
it 'does not overwrite the survey item benchmarks with admin data benchmarks' do
survey_item = SurveyItem.find_by_survey_item_id 't-prep-q1'
expect(survey_item.approval_low_benchmark).to eq 3.5

@ -2,6 +2,12 @@ require 'rails_helper'
RSpec.describe Measure, type: :model do
let(:measure) { create(:measure) }
let(:scale) { create(:scale, measure:) }
let(:teacher_scale) { create(:teacher_scale, measure:) }
let(:student_scale) { create(:student_scale, measure:) }
let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) }
let(:admin_watch_low_benchmark) { 2.0 }
let(:admin_growth_low_benchmark) { 3.0 }
let(:admin_approval_low_benchmark) { 4.0 }
@ -17,166 +23,450 @@ RSpec.describe Measure, type: :model do
let(:teacher_approval_low_benchmark) { 3.2 }
let(:teacher_ideal_low_benchmark) { 4.2 }
context 'when a measure includes only one admin data item' do
before do
create(:admin_data_item, measure: measure,
watch_low_benchmark: admin_watch_low_benchmark,
growth_low_benchmark: admin_growth_low_benchmark,
approval_low_benchmark: admin_approval_low_benchmark,
ideal_low_benchmark: admin_ideal_low_benchmark)
end
it 'returns a watch low benchmark equal to the admin data item watch low benchmark' do
expect(measure.watch_low_benchmark).to be 2.0
end
describe 'benchmarks' do
context 'when a measure includes only one admin data item' do
before do
create(:admin_data_item, scale:,
watch_low_benchmark: admin_watch_low_benchmark,
growth_low_benchmark: admin_growth_low_benchmark,
approval_low_benchmark: admin_approval_low_benchmark,
ideal_low_benchmark: admin_ideal_low_benchmark)
end
it 'returns a watch low benchmark equal to the admin data item watch low benchmark' do
expect(measure.watch_low_benchmark).to be 2.0
end
it 'returns the source as an admin_data_item' do
expect(measure.sources).to eq [:admin_data]
it 'returns the source as an admin_data_item' do
expect(measure.sources).to eq [:admin_data]
end
end
end
context 'when a measure includes only student survey items' do
before do
create(:student_survey_item, measure: measure,
watch_low_benchmark: student_watch_low_benchmark,
growth_low_benchmark: student_growth_low_benchmark,
approval_low_benchmark: student_approval_low_benchmark,
ideal_low_benchmark: student_ideal_low_benchmark)
end
it 'returns a watch low benchmark equal to the student survey item watch low benchmark ' do
expect(measure.watch_low_benchmark).to be 1.5
context 'when a measure includes only student survey items' do
before do
create(:student_survey_item, scale:,
watch_low_benchmark: student_watch_low_benchmark,
growth_low_benchmark: student_growth_low_benchmark,
approval_low_benchmark: student_approval_low_benchmark,
ideal_low_benchmark: student_ideal_low_benchmark)
end
it 'returns a watch low benchmark equal to the student survey item watch low benchmark ' do
expect(measure.watch_low_benchmark).to be 1.5
end
it 'returns a warning low benchmark equal to the student survey item warning low benchmark ' do
expect(measure.warning_low_benchmark).to eq 1
end
it 'returns the source as student_surveys' do
expect(measure.sources).to eq [:student_surveys]
end
end
it 'returns a warning low benchmark equal to the student survey item warning low benchmark ' do
expect(measure.warning_low_benchmark).to eq 1
end
it 'returns the source as student_surveys' do
expect(measure.sources).to eq [:student_surveys]
end
end
context 'when a measure includes only teacher survey items' do
before do
create(:teacher_survey_item, measure: measure,
watch_low_benchmark: teacher_watch_low_benchmark,
growth_low_benchmark: teacher_growth_low_benchmark,
approval_low_benchmark: teacher_approval_low_benchmark,
ideal_low_benchmark: teacher_ideal_low_benchmark)
end
it 'returns a watch low benchmark equal to the teacher survey item watch low benchmark ' do
expect(measure.watch_low_benchmark).to be 1.2
context 'when a measure includes only teacher survey items' do
before do
create(:teacher_survey_item, scale:,
watch_low_benchmark: teacher_watch_low_benchmark,
growth_low_benchmark: teacher_growth_low_benchmark,
approval_low_benchmark: teacher_approval_low_benchmark,
ideal_low_benchmark: teacher_ideal_low_benchmark)
end
it 'returns a watch low benchmark equal to the teacher survey item watch low benchmark ' do
expect(measure.watch_low_benchmark).to be 1.2
end
it 'returns a warning low benchmark equal to the teacher survey item warning low benchmark ' do
expect(measure.warning_low_benchmark).to eq 1
end
it 'returns the source as teacher_surveys' do
expect(measure.sources).to eq [:teacher_surveys]
end
end
it 'returns a warning low benchmark equal to the teacher survey item warning low benchmark ' do
expect(measure.warning_low_benchmark).to eq 1
end
it 'returns the source as teacher_surveys' do
expect(measure.sources).to eq [:teacher_surveys]
end
end
context 'when a measure includes admin data and student survey items' do
before do
create_list(:admin_data_item, 3, measure: measure,
watch_low_benchmark: admin_watch_low_benchmark,
growth_low_benchmark: admin_growth_low_benchmark,
approval_low_benchmark: admin_approval_low_benchmark,
ideal_low_benchmark: admin_ideal_low_benchmark)
create(:student_survey_item, measure: measure,
watch_low_benchmark: student_watch_low_benchmark,
growth_low_benchmark: student_growth_low_benchmark,
approval_low_benchmark: student_approval_low_benchmark,
ideal_low_benchmark: student_ideal_low_benchmark)
end
context 'when a measure includes admin data and student survey items' do
before do
create_list(:admin_data_item, 3, scale:,
watch_low_benchmark: admin_watch_low_benchmark,
growth_low_benchmark: admin_growth_low_benchmark,
approval_low_benchmark: admin_approval_low_benchmark,
ideal_low_benchmark: admin_ideal_low_benchmark)
it 'returns the average of admin and student benchmarks where each admin data item has a weight of 1 and student survey items all together have a weight of 1' do
# (2*3 + 1.5)/4
expect(measure.watch_low_benchmark).to be 1.875
end
it 'returns the source as admin and student survey items' do
expect(measure.sources).to eq [:admin_data, :student_surveys]
end
end
create(:student_survey_item, scale:,
watch_low_benchmark: student_watch_low_benchmark,
growth_low_benchmark: student_growth_low_benchmark,
approval_low_benchmark: student_approval_low_benchmark,
ideal_low_benchmark: student_ideal_low_benchmark)
end
context 'when a measure includes admin data and teacher survey items' do
before do
create_list(:admin_data_item, 3, measure: measure,
watch_low_benchmark: admin_watch_low_benchmark,
growth_low_benchmark: admin_growth_low_benchmark,
approval_low_benchmark: admin_approval_low_benchmark,
ideal_low_benchmark: admin_ideal_low_benchmark)
create(:teacher_survey_item, measure: measure,
watch_low_benchmark: teacher_watch_low_benchmark,
growth_low_benchmark: teacher_growth_low_benchmark,
approval_low_benchmark: teacher_approval_low_benchmark,
ideal_low_benchmark: teacher_ideal_low_benchmark)
it 'returns the average of admin and student benchmarks where each admin data item has a weight of 1 and student survey items all together have a weight of 1' do
# (2*3 + 1.5)/4
expect(measure.watch_low_benchmark).to be 1.875
end
it 'returns the source as admin and student survey items' do
expect(measure.sources).to eq %i[admin_data student_surveys]
end
end
it 'returns the average of admin and teacher benchmarks where each admin data item has a weight of 1 and teacher survey items all together have a weight of 1' do
# (2*3 + 1.2)/4
expect(measure.watch_low_benchmark).to be 1.8
end
it 'returns the source as admin and teacher survey items' do
expect(measure.sources).to eq [:admin_data, :teacher_surveys]
end
end
context 'when a measure includes admin data and teacher survey items' do
before do
create_list(:admin_data_item, 3, scale:,
watch_low_benchmark: admin_watch_low_benchmark,
growth_low_benchmark: admin_growth_low_benchmark,
approval_low_benchmark: admin_approval_low_benchmark,
ideal_low_benchmark: admin_ideal_low_benchmark)
create(:teacher_survey_item, scale:,
watch_low_benchmark: teacher_watch_low_benchmark,
growth_low_benchmark: teacher_growth_low_benchmark,
approval_low_benchmark: teacher_approval_low_benchmark,
ideal_low_benchmark: teacher_ideal_low_benchmark)
end
context 'when a measure includes student and teacher survey items' do
before do
create_list(:student_survey_item, 3, measure: measure,
watch_low_benchmark: student_watch_low_benchmark,
growth_low_benchmark: student_growth_low_benchmark,
approval_low_benchmark: student_approval_low_benchmark,
ideal_low_benchmark: student_ideal_low_benchmark)
create_list(:teacher_survey_item, 3, measure: measure,
watch_low_benchmark: teacher_watch_low_benchmark,
growth_low_benchmark: teacher_growth_low_benchmark,
approval_low_benchmark: teacher_approval_low_benchmark,
ideal_low_benchmark: teacher_ideal_low_benchmark)
it 'returns the average of admin and teacher benchmarks where each admin data item has a weight of 1 and teacher survey items all together have a weight of 1' do
# (2*3 + 1.2)/4
expect(measure.watch_low_benchmark).to be 1.8
end
it 'returns the source as admin and teacher survey items' do
expect(measure.sources).to eq %i[admin_data teacher_surveys]
end
end
it 'returns the average of student and teacher benchmarks where teacher survey items all together have a weight of 1 and all student survey items have a weight of 1' do
# (1.2+ 1.5)/2
expect(measure.watch_low_benchmark).to be 1.35
context 'when a measure includes student and teacher survey items' do
before do
create_list(:student_survey_item, 3, scale:,
watch_low_benchmark: student_watch_low_benchmark,
growth_low_benchmark: student_growth_low_benchmark,
approval_low_benchmark: student_approval_low_benchmark,
ideal_low_benchmark: student_ideal_low_benchmark)
create_list(:teacher_survey_item, 3, scale:,
watch_low_benchmark: teacher_watch_low_benchmark,
growth_low_benchmark: teacher_growth_low_benchmark,
approval_low_benchmark: teacher_approval_low_benchmark,
ideal_low_benchmark: teacher_ideal_low_benchmark)
end
it 'returns the average of student and teacher benchmarks where teacher survey items all together have a weight of 1 and all student survey items have a weight of 1' do
# (1.2+ 1.5)/2
expect(measure.watch_low_benchmark).to be 1.35
end
it 'returns the source as student and teacher survey items' do
expect(measure.sources).to eq %i[student_surveys teacher_surveys]
end
end
it 'returns the source as student and teacher survey items' do
expect(measure.sources).to eq [:student_surveys, :teacher_surveys]
context 'when a measure includes admin data and student and teacher survey items' do
before do
create_list(:admin_data_item, 3, scale:,
watch_low_benchmark: admin_watch_low_benchmark,
growth_low_benchmark: admin_growth_low_benchmark,
approval_low_benchmark: admin_approval_low_benchmark,
ideal_low_benchmark: admin_ideal_low_benchmark)
create_list(:student_survey_item, 3, scale:,
watch_low_benchmark: student_watch_low_benchmark,
growth_low_benchmark: student_growth_low_benchmark,
approval_low_benchmark: student_approval_low_benchmark,
ideal_low_benchmark: student_ideal_low_benchmark)
create_list(:teacher_survey_item, 3, scale:,
watch_low_benchmark: teacher_watch_low_benchmark,
growth_low_benchmark: teacher_growth_low_benchmark,
approval_low_benchmark: teacher_approval_low_benchmark,
ideal_low_benchmark: teacher_ideal_low_benchmark)
end
it 'returns the average of admin and teacher benchmarks where each admin data item has a weight of 1 and teacher survey items all together have a weight of 1, and student surveys have a weight of 1' do
# (2 * 3 + 1.2 + 1.5)/ 5
expect(measure.watch_low_benchmark).to be_within(0.001).of 1.74
# (3 * 3 + 2.2 + 2.5)/ 5
expect(measure.growth_low_benchmark).to be_within(0.001).of 2.74
# (4 * 3 + 3.2 + 3.5)/ 5
expect(measure.approval_low_benchmark).to be_within(0.001).of 3.74
# (5 * 3 + 4.2 + 4.5)/ 5
expect(measure.ideal_low_benchmark).to be_within(0.001).of 4.74
end
it 'returns the source as admin student and teacher survey items' do
expect(measure.sources).to eq %i[admin_data student_surveys teacher_surveys]
end
end
end
context 'when a measure includes admin data and student and teacher survey items' do
before do
create_list(:admin_data_item, 3, measure: measure,
watch_low_benchmark: admin_watch_low_benchmark,
growth_low_benchmark: admin_growth_low_benchmark,
approval_low_benchmark: admin_approval_low_benchmark,
ideal_low_benchmark: admin_ideal_low_benchmark)
create_list(:student_survey_item, 3, measure: measure,
watch_low_benchmark: student_watch_low_benchmark,
growth_low_benchmark: student_growth_low_benchmark,
approval_low_benchmark: student_approval_low_benchmark,
ideal_low_benchmark: student_ideal_low_benchmark)
create_list(:teacher_survey_item, 3, measure: measure,
watch_low_benchmark: teacher_watch_low_benchmark,
growth_low_benchmark: teacher_growth_low_benchmark,
approval_low_benchmark: teacher_approval_low_benchmark,
ideal_low_benchmark: teacher_ideal_low_benchmark)
describe '.score' do
context 'when the measure includes only teacher data' do
let(:teacher_survey_item_1) { create(:teacher_survey_item, scale: teacher_scale) }
let(:teacher_survey_item_2) { create(:teacher_survey_item, scale: teacher_scale) }
let(:teacher_survey_item_3) { create(:teacher_survey_item, scale: teacher_scale) }
context "and the number of responses for each of the measure's survey items meets the teacher threshold of 17" do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year:, school:, likert_score: 3)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_2, academic_year:, school:, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
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(measure.score(school:, academic_year:).average).to eq 4
end
it 'affirms that the result meets the teacher threshold' do
expect(measure.score(school:, academic_year:).meets_teacher_threshold?).to be true
end
it 'reports the result does not meeet student threshold' do
expect(measure.score(school:, academic_year:).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
before :each do
create_list(:survey_item_response, 19, survey_item: teacher_survey_item_1, academic_year:, school:,
likert_score: 3)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_2, academic_year:, school:,
likert_score: 4)
create_list(:survey_item_response, 16, 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
average_score = 4
expect(measure.score(school:, academic_year:).average).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
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_1, academic_year:, school:,
likert_score: rand)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_2, academic_year:, school:,
likert_score: rand)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_3, academic_year:, school:,
likert_score: rand)
end
it 'returns nil' do
expect(measure.score(school:, academic_year:).average).to be_nil
end
it 'affirms that the result does not meet the threshold' do
expect(measure.score(school:, academic_year:).meets_teacher_threshold?).to be false
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
create_list(:survey_item_response, 18, survey_item: teacher_survey_item_1, academic_year:, school:,
likert_score: rand)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_2, academic_year:, school:,
likert_score: rand)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_3, academic_year:, school:,
likert_score: rand)
end
it 'returns nil' do
expect(measure.score(school:, academic_year:).average).to be_nil
end
it 'affirms that the result does not meet the threshold' do
expect(measure.score(school:, academic_year:).meets_teacher_threshold?).to be false
end
end
end
it 'returns the average of admin and teacher benchmarks where each admin data item has a weight of 1 and teacher survey items all together have a weight of 1, and student surveys have a weight of 1' do
# (2 * 3 + 1.2 + 1.5)/ 5
expect(measure.watch_low_benchmark).to be_within(0.001).of 1.74
# (3 * 3 + 2.2 + 2.5)/ 5
expect(measure.growth_low_benchmark).to be_within(0.001).of 2.74
# (4 * 3 + 3.2 + 3.5)/ 5
expect(measure.approval_low_benchmark).to be_within(0.001).of 3.74
# (5 * 3 + 4.2 + 4.5)/ 5
expect(measure.ideal_low_benchmark).to be_within(0.001).of 4.74
context 'when the measure includes only student data' do
let(:student_survey_item_1) { create(:student_survey_item, scale: student_scale) }
let(:student_survey_item_2) { create(:student_survey_item, scale: student_scale) }
let(:student_survey_item_3) { create(:student_survey_item, scale: student_scale) }
context "and the number of responses for each of the measure's survey items meets the student threshold of 196" do
before :each do
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_1, academic_year:, school:, likert_score: 3)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_2, academic_year:, school:, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_3, academic_year:, school:, likert_score: 5)
end
it 'returns the average of the likert scores of the survey items' do
expect(measure.score(school:, academic_year:).average).to eq 4
end
it 'affirms that the result meets the student threshold' do
expect(measure.score(school:, academic_year:).meets_student_threshold?).to be true
end
it 'notes that the result does not meet the teacher threshold' do
expect(measure.score(school:, academic_year:).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
before :each do
create_list(:survey_item_response, 200, survey_item: student_survey_item_1, academic_year:,
school:, likert_score: 3)
create_list(:survey_item_response, 195, survey_item: student_survey_item_2, academic_year:,
school:, likert_score: 4)
create_list(:survey_item_response, 193, survey_item: student_survey_item_3, academic_year:,
school:, likert_score: 5)
end
it 'returns the average of the likert scores of the survey items' do
average_score = 4
expect(measure.score(school:, academic_year:).average).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
create_list(:survey_item_response, 195, survey_item: student_survey_item_1, academic_year:,
school:, likert_score: rand)
create_list(:survey_item_response, 195, survey_item: student_survey_item_2, academic_year:,
school:, likert_score: rand)
create_list(:survey_item_response, 195, survey_item: student_survey_item_3, academic_year:,
school:, likert_score: rand)
end
it 'returns nil' do
expect(measure.score(school:, academic_year:).average).to be_nil
end
it 'affirms that the result does not meet the threshold' do
expect(measure.score(school:, academic_year:).meets_student_threshold?).to be false
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
create_list(:survey_item_response, 200, survey_item: student_survey_item_1, academic_year:,
school:, likert_score: rand)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_2, academic_year:, school:, likert_score: rand)
create_list(:survey_item_response, 191, survey_item: student_survey_item_3, academic_year:,
school:, likert_score: rand)
end
it 'returns nil' do
expect(measure.score(school:, academic_year:).average).to be_nil
end
it 'affirms that the result does not meet the threshold' do
expect(measure.score(school:, academic_year:).meets_student_threshold?).to be false
end
end
end
it 'returns the source as admin student and teacher survey items' do
expect(measure.sources).to eq [:admin_data, :student_surveys, :teacher_surveys]
context 'when the measure includes both teacher and student data' do
let(:teacher_survey_item_1) { create(:teacher_survey_item, scale: teacher_scale) }
let(:student_survey_item_1) { create(:student_survey_item, scale: student_scale) }
context 'and there is sufficient teacher data and sufficient student data' do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year:, school:, likert_score: 5)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_1, academic_year:, school:, likert_score: 5)
end
it 'returns the average of the likert scores of the survey items' do
expect(measure.score(school:, academic_year:).average).to eq 5
end
it 'affirms that the result does meet the thresholds' do
expect(measure.score(school:, academic_year:).meets_teacher_threshold?).to be true
end
context 'and a different measure in the same year exists' do
before :each do
different_measure = create(:measure)
different_scale = create(:teacher_scale, measure: different_measure)
different_teacher_survey_item = create(:teacher_survey_item, scale: different_scale)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: different_teacher_survey_item, academic_year:, school:, likert_score: 1)
end
it 'affirms the additional measures do not change the scores for the original measure' do
expect(measure.score(school:, academic_year:).average).to eq 5
end
end
context 'and data exists for the same measure but a different school' do
before do
different_school = create(:school)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year:, school: different_school, likert_score: 1)
end
it 'affirms the data for the different school does not affect the average for the original school' do
expect(measure.score(school:, academic_year:).average).to eq 5
end
end
context 'and data exists for the same measure but a different year' do
before do
different_year = create(:academic_year, range: '1111-12')
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year: different_year, school:, likert_score: 1)
end
it 'affirms the data for the different year does not affect the average for the original year' do
expect(measure.score(school:, academic_year:).average).to eq 5
end
end
end
context 'and there is sufficient teacher data and insufficient student data' do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year:, school:, likert_score: 5)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1,
survey_item: student_survey_item_1, academic_year:, school:, likert_score: 1)
end
it 'returns the average of the likert scores of the teacher survey items' do
expect(measure.score(school:, academic_year:).average).to eq 5
end
it 'affirms that the result meets the teacher threshold but not the student threshold' do
expect(measure.score(school:, academic_year:).meets_teacher_threshold?).to be true
expect(measure.score(school:, academic_year:).meets_student_threshold?).to be false
end
end
context 'and there is insufficient teacher data and sufficient student data' do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD - 1,
survey_item: teacher_survey_item_1, academic_year:, school:, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_1, academic_year:, school:, likert_score: 5)
end
it 'returns the average of the likert scores of the student survey items' do
expect(measure.score(school:, academic_year:).average).to eq 5
end
it 'affirms that the result meets the student threshold but not the teacher threshold' do
expect(measure.score(school:, academic_year:).meets_teacher_threshold?).to be false
expect(measure.score(school:, academic_year:).meets_student_threshold?).to be true
end
end
context 'and there is insufficient teacher data and insufficient student data' do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD - 1,
survey_item: teacher_survey_item_1, academic_year:, school:)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1,
survey_item: student_survey_item_1, academic_year:, school:)
end
it 'returns nil' do
expect(measure.score(school:, academic_year:).average).to be_nil
end
it 'affirms that the result does not meet either threshold' do
expect(measure.score(school:, academic_year:).meets_teacher_threshold?).to be false
expect(measure.score(school:, academic_year:).meets_student_threshold?).to be false
end
end
end
end
end

@ -11,13 +11,16 @@ describe ResponseRate, type: :model do
describe StudentResponseRate do
let(:subcategory) { create(:subcategory) }
let(:sufficient_measure_1) { create(:measure, subcategory: subcategory) }
let(:sufficient_scale_1) { create(:scale, measure: sufficient_measure_1) }
let(:sufficient_measure_2) { create(:measure, subcategory: subcategory) }
let(:sufficient_scale_2) { create(:scale, measure: sufficient_measure_2) }
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) }
let(:insufficient_scale) { create(:scale, measure: insufficient_measure) }
let(:sufficient_teacher_survey_item) { create(:teacher_survey_item, scale: sufficient_scale_1) }
let(:sufficient_student_survey_item_1) { create(:student_survey_item, scale: sufficient_scale_1) }
let(:insufficient_teacher_survey_item) { create(:teacher_survey_item, scale: insufficient_scale) }
let(:sufficient_student_survey_item_2) { create(:student_survey_item, scale: sufficient_scale_2) }
let(:insufficient_student_survey_item) { create(:student_survey_item, scale: insufficient_scale) }
before :each do
survey_respondents
@ -44,14 +47,17 @@ describe ResponseRate, type: :model do
describe TeacherResponseRate do
let(:subcategory) { create(:subcategory) }
let(:sufficient_measure_1) { create(:measure, subcategory: subcategory) }
let(:sufficient_scale_1) { create(:scale, measure: sufficient_measure_1) }
let(:sufficient_measure_2) { create(:measure, subcategory: subcategory) }
let(:sufficient_scale_2) { create(:scale, measure: sufficient_measure_2) }
let(:insufficient_measure) { create(:measure, subcategory: subcategory) }
let(:sufficient_teacher_survey_item_1) { create(:teacher_survey_item, measure: sufficient_measure_1) }
let(:sufficient_teacher_survey_item_2) { create(:teacher_survey_item, measure: sufficient_measure_1) }
let(:sufficient_teacher_survey_item_3) { 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(:insufficient_student_survey_item) { create(:student_survey_item, measure: insufficient_measure) }
let(:insufficient_scale) { create(:scale, measure: insufficient_measure) }
let(:sufficient_teacher_survey_item_1) { create(:teacher_survey_item, scale: sufficient_scale_1) }
let(:sufficient_teacher_survey_item_2) { create(:teacher_survey_item, scale: sufficient_scale_1) }
let(:sufficient_teacher_survey_item_3) { create(:teacher_survey_item, scale: sufficient_scale_1) }
let(:sufficient_student_survey_item_1) { create(:student_survey_item, scale: sufficient_scale_1) }
let(:insufficient_teacher_survey_item) { create(:teacher_survey_item, scale: insufficient_scale) }
let(:insufficient_student_survey_item) { create(:student_survey_item, scale: insufficient_scale) }
before :each do
survey_respondents

@ -0,0 +1,41 @@
require 'rails_helper'
RSpec.describe Scale, type: :model do
let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) }
let(:scale) { create(:scale) }
describe '.score' 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:) }
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
end

@ -0,0 +1,60 @@
require 'rails_helper'
RSpec.describe Subcategory, type: :model do
let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) }
let(:subcategory) { create(:subcategory) }
let(:measure_1) { create(:measure, subcategory: subcategory) }
let(:teacher_scale) { create(:teacher_scale, measure: measure_1) }
let(:measure_2) { create(:measure, subcategory: subcategory) }
let(:student_scale) { create(:student_scale, measure: measure_2) }
describe '.score' do
let(:teacher_survey_item_1) { create(:teacher_survey_item, scale: teacher_scale) }
let(:teacher_survey_item_2) { create(:teacher_survey_item, scale: teacher_scale) }
let(:teacher_survey_item_3) { create(:teacher_survey_item, scale: teacher_scale) }
let(:student_survey_item_1) { create(:student_survey_item, scale: student_scale) }
let(:student_survey_item_2) { create(:student_survey_item, scale: student_scale) }
let(:student_survey_item_3) { create(:student_survey_item, scale: student_scale) }
let(:student_survey_item_4) { create(:student_survey_item, scale: student_scale) }
let(:student_survey_item_5) { create(:student_survey_item, scale: student_scale) }
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year:, school:, likert_score: 2)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_2, academic_year:, school:, likert_score: 3)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_3, academic_year:, school:, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_1, academic_year:, school:, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_2, academic_year:, school:, likert_score: 2)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_3, academic_year:, school:, likert_score: 3)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_4, academic_year:, school:, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_5, academic_year:, school:, likert_score: 5)
end
it 'returns the average of the likert scores of the measures' do
expect(subcategory.score(school:, academic_year:)).to eq 3
end
context 'when other subcategories 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(subcategory.score(school:, academic_year:)).to eq 3
end
end
end
end

@ -1,337 +0,0 @@
require 'rails_helper'
describe SurveyItemResponse, type: :model do
let(:school) { create(:school) }
let(:ay) { create(:academic_year) }
describe '.score_for_measure' do
let(:measure) { create(:measure) }
context 'when the measure includes only teacher data' do
let(:teacher_survey_item_1) { create(:teacher_survey_item, measure: measure) }
let(:teacher_survey_item_2) { create(:teacher_survey_item, measure: measure) }
let(:teacher_survey_item_3) { create(:teacher_survey_item, measure: measure) }
context "and the number of responses for each of the measure's survey items meets the teacher threshold of 17" do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: 3)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_2, academic_year: ay, school: school, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_3, academic_year: ay, school: school, likert_score: 5)
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).average).to eq 4
end
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
before :each do
create_list(:survey_item_response, 19, survey_item: teacher_survey_item_1, academic_year: ay, school: school,
likert_score: 3)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_2, academic_year: ay, school: school,
likert_score: 4)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_3, academic_year: ay, school: school,
likert_score: 5)
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).average).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
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_1, academic_year: ay, school: school,
likert_score: rand)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_2, academic_year: ay, school: school,
likert_score: rand)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_3, academic_year: ay, school: school,
likert_score: rand)
end
it 'returns nil' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).average).to be_nil
end
it 'affirms that the result does not meet the 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 does not meet the teacher threshold of 17" do
before :each do
create_list(:survey_item_response, 18, survey_item: teacher_survey_item_1, academic_year: ay, school: school,
likert_score: rand)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_2, academic_year: ay, school: school,
likert_score: rand)
create_list(:survey_item_response, 16, survey_item: teacher_survey_item_3, academic_year: ay, school: school,
likert_score: rand)
end
it 'returns nil' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).average).to be_nil
end
it 'affirms that the result does not meet the threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_teacher_threshold?).to be false
end
end
end
context 'when the measure includes only student data' do
let(:student_survey_item_1) { create(:student_survey_item, measure: measure) }
let(:student_survey_item_2) { create(:student_survey_item, measure: measure) }
let(:student_survey_item_3) { create(:student_survey_item, measure: measure) }
context "and the number of responses for each of the measure's survey items meets the student threshold of 196" do
before :each do
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: 3)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_2, academic_year: ay, school: school, likert_score: 4)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_3, academic_year: ay, school: school, likert_score: 5)
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).average).to eq 4
end
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
before :each do
create_list(:survey_item_response, 200, survey_item: student_survey_item_1, academic_year: ay,
school: school, likert_score: 3)
create_list(:survey_item_response, 195, survey_item: student_survey_item_2, academic_year: ay,
school: school, likert_score: 4)
create_list(:survey_item_response, 193, survey_item: student_survey_item_3, academic_year: ay,
school: school, likert_score: 5)
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).average).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
create_list(:survey_item_response, 195, survey_item: student_survey_item_1, academic_year: ay,
school: school, likert_score: rand)
create_list(:survey_item_response, 195, survey_item: student_survey_item_2, academic_year: ay,
school: school, likert_score: rand)
create_list(:survey_item_response, 195, survey_item: student_survey_item_3, academic_year: ay,
school: school, likert_score: rand)
end
it 'returns nil' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).average).to be_nil
end
it 'affirms that the result does not meet the 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 does not meet the student threshold of 196" do
before :each do
create_list(:survey_item_response, 200, survey_item: student_survey_item_1, academic_year: ay,
school: school, likert_score: rand)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_2, academic_year: ay, school: school, likert_score: rand)
create_list(:survey_item_response, 191, survey_item: student_survey_item_3, academic_year: ay,
school: school, likert_score: rand)
end
it 'returns nil' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).average).to be_nil
end
it 'affirms that the result does not meet the threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_student_threshold?).to be false
end
end
end
context 'when the measure includes both teacher and student data' do
let(:teacher_survey_item_1) { create(:teacher_survey_item, measure: measure) }
let(:student_survey_item_1) { create(:student_survey_item, measure: measure) }
context 'and there is sufficient teacher data and sufficient student data' do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: 5)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: 5)
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).average).to eq 5
end
it 'affirms that the result does meet the thresholds' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_teacher_threshold?).to be true
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_student_threshold?).to be true
end
end
context 'and there is sufficient teacher data and insufficient student data' do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: 5)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1,
survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: 1)
end
it 'returns the average of the likert scores of the teacher survey items' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).average).to eq 5
end
it 'affirms that the result meets the teacher threshold but not the student threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_teacher_threshold?).to be true
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_student_threshold?).to be false
end
end
context 'and there is insufficient teacher data and sufficient student data' do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD - 1,
survey_item: teacher_survey_item_1, academic_year: ay, school: school, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item_1, academic_year: ay, school: school, likert_score: 5)
end
it 'returns the average of the likert scores of the student survey items' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).average).to eq 5
end
it 'affirms that the result meets the student threshold but not the teacher threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_teacher_threshold?).to be false
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_student_threshold?).to be true
end
end
context 'and there is insufficient teacher data and insufficient student data' do
before :each do
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD - 1,
survey_item: teacher_survey_item_1, academic_year: ay, school: school)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1,
survey_item: student_survey_item_1, academic_year: ay, school: school)
end
it 'returns nil' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).average).to be_nil
end
it 'affirms that the result does not meet either threshold' do
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_teacher_threshold?).to be false
expect(SurveyItemResponse.score_for_measure(measure: measure, school: school,
academic_year: ay).meets_student_threshold?).to be false
end
end
end
end
describe '.score_for_subcategory' 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
largest_threshold = [SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD].max
create_list(:survey_item_response, largest_threshold, survey_item: sufficient_teacher_survey_item,
academic_year: ay, school: school, likert_score: 1)
create_list(:survey_item_response, largest_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 the average score of all survey item responses for measures meeting their respective thresholds' do
expect(SurveyItemResponse.score_for_subcategory(subcategory: subcategory, school: school,
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
end

@ -0,0 +1,38 @@
require 'rails_helper'
RSpec.describe SurveyItem, type: :model do
let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) }
let(:scale) { create(:scale) }
describe '.score' do
let(:teacher_survey_item) { create(:teacher_survey_item, scale:) }
before :each do
create(:survey_item_response,
survey_item: teacher_survey_item, academic_year:, school:, likert_score: 3)
create(:survey_item_response,
survey_item: teacher_survey_item, academic_year:, school:, likert_score: 4)
create(:survey_item_response,
survey_item: teacher_survey_item, academic_year:, school:, likert_score: 5)
end
it 'returns the average of the likert scores of the survey items' do
expect(teacher_survey_item.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
end

@ -1,8 +1,8 @@
require 'rails_helper'
describe GaugePresenter do
let(:scale) do
Scale.new(
let(:zones) do
Zones.new(
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
@ -11,7 +11,7 @@ describe GaugePresenter do
end
let(:score) { 3 }
let(:gauge_presenter) { GaugePresenter.new(scale: scale, score: score) }
let(:gauge_presenter) { GaugePresenter.new(zones: zones, score: score) }
it 'returns the key benchmark percentage for the gauge' do
expect(gauge_presenter.key_benchmark_percentage).to eq 0.625
@ -98,8 +98,8 @@ describe GaugePresenter do
end
context 'when there are no benchmarks or score for the gauge' do
let(:scale) do
Scale.new(
let(:zones) do
Zones.new(
watch_low_benchmark: nil,
growth_low_benchmark: nil,
approval_low_benchmark: nil,

@ -4,7 +4,10 @@ describe MeasurePresenter do
let(:academic_year) { create(:academic_year, range: '1989-90') }
let(:school) { create(:school, name: 'Best School') }
let(:measure) { create(:measure, measure_id: 'measure-id') }
let(:measure_presenter) { MeasurePresenter.new(measure: measure, academic_year: academic_year, school: school) }
let(:teacher_scale) { create(:teacher_scale, measure:) }
let(:student_scale) { create(:student_scale, measure:) }
let(:admin_scale) { create(:scale, measure:) }
let(:measure_presenter) { MeasurePresenter.new(measure:, academic_year:, school:) }
it 'returns the id of the measure' do
expect(measure_presenter.id).to eq 'measure-id'
@ -16,13 +19,13 @@ describe MeasurePresenter do
context 'when the measure contains only teacher data' do
before :each do
survey_item1 = create(:teacher_survey_item, measure: measure, prompt: 'A teacher survey item prompt')
survey_item2 = create(:teacher_survey_item, measure: measure, prompt: 'Another teacher survey item prompt')
survey_item1 = create(:teacher_survey_item, scale: teacher_scale, prompt: 'A teacher survey item prompt')
survey_item2 = create(:teacher_survey_item, scale: teacher_scale, prompt: 'Another teacher survey item prompt')
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item1,
academic_year: academic_year, school: school, likert_score: 1)
academic_year:, school:, likert_score: 1)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item: survey_item2,
academic_year: academic_year, school: school, likert_score: 5)
academic_year:, school:, likert_score: 5)
end
it 'creates a gauge presenter that presents the average likert score' do
@ -41,10 +44,10 @@ describe MeasurePresenter do
context 'when the measure contains both teacher data and admin data' do
before :each do
create(:teacher_survey_item, measure: measure, prompt: 'A teacher survey item prompt')
create(:teacher_survey_item, measure: measure, prompt: 'Another teacher survey item prompt')
create(:admin_data_item, measure: measure, description: 'An admin data item description')
create(:admin_data_item, measure: measure, description: 'Another admin data item description')
create(:teacher_survey_item, scale: teacher_scale, prompt: 'A teacher survey item prompt')
create(:teacher_survey_item, scale: teacher_scale, prompt: 'Another teacher survey item prompt')
create(:admin_data_item, scale: admin_scale, description: 'An admin data item description')
create(:admin_data_item, scale: admin_scale, description: 'Another admin data item description')
end
it 'returns a list of data item presenters with two elements' do
@ -68,13 +71,13 @@ describe MeasurePresenter do
context 'when the measure has partial data for teachers and students' do
before :each do
teacher_survey_item = create(:teacher_survey_item, measure: measure)
student_survey_item = create(:student_survey_item, measure: measure)
teacher_survey_item = create(:teacher_survey_item, scale: teacher_scale)
student_survey_item = create(:student_survey_item, scale: student_scale)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item, academic_year: academic_year, school: school)
survey_item: teacher_survey_item, academic_year:, school:)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD - 1,
survey_item: student_survey_item, academic_year: academic_year, school: school)
survey_item: student_survey_item, academic_year:, school:)
end
it 'tracks which parts of the data are sufficient' do
@ -91,9 +94,9 @@ describe MeasurePresenter do
context 'when the measure has insufficient admin data and insufficient teacher/student data' do
before :each do
create(:admin_data_item, measure: measure)
create(:teacher_survey_item, measure: measure)
create(:student_survey_item, measure: measure)
create(:admin_data_item, scale: admin_scale)
create(:teacher_survey_item, scale: teacher_scale)
create(:student_survey_item, scale: student_scale)
end
it 'tracks the reason for their insufficiency' do

@ -1,30 +1,30 @@
require 'rails_helper'
describe Scale do
describe Zones do
describe '#zone_for_score' do
let(:scale) do
Scale.new watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5,
let(:zones) do
Zones.new watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5
end
context 'when the score is 1.0' do
it 'returns the warning zone' do
expect(scale.zone_for_score(1.0)).to eq scale.warning_zone
expect(zones.zone_for_score(1.0)).to eq zones.warning_zone
end
end
context 'when the score is 5.0' do
it 'returns the ideal zone' do
expect(scale.zone_for_score(5.0)).to eq scale.ideal_zone
expect(zones.zone_for_score(5.0)).to eq zones.ideal_zone
end
end
context 'when the score is right on the benchmark' do
it 'returns the higher zone' do
expect(scale.zone_for_score(1.5)).to eq scale.watch_zone
expect(scale.zone_for_score(2.5)).to eq scale.growth_zone
expect(scale.zone_for_score(3.5)).to eq scale.approval_zone
expect(scale.zone_for_score(4.5)).to eq scale.ideal_zone
expect(zones.zone_for_score(1.5)).to eq zones.watch_zone
expect(zones.zone_for_score(2.5)).to eq zones.growth_zone
expect(zones.zone_for_score(3.5)).to eq zones.approval_zone
expect(zones.zone_for_score(4.5)).to eq zones.ideal_zone
end
end
end

@ -1,8 +1,8 @@
require 'rails_helper'
describe SubcategoryCardPresenter do
let(:scale) do
Scale.new(
let(:zones) do
Zones.new(
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
@ -10,7 +10,7 @@ describe SubcategoryCardPresenter do
)
end
let(:subcategory_card_presenter) { SubcategoryCardPresenter.new(name: 'Card name', scale: scale, score: score) }
let(:subcategory_card_presenter) { SubcategoryCardPresenter.new(name: 'Card name', zones: zones, score: score) }
context 'when the given score is in the Warning zone for the given scale' do
let(:score) { 1 }

@ -13,9 +13,11 @@ describe SubcategoryPresenter do
let(:subcategory_presenter) do
survey_respondents
measure1 = create(:measure, subcategory:)
survey_item1 = create(:teacher_survey_item, measure: measure1, watch_low_benchmark: 4, growth_low_benchmark: 4.25,
teacher_scale_1 = create(:teacher_scale, measure: measure1)
student_scale_1 = create(:student_scale, measure: measure1)
survey_item1 = create(:teacher_survey_item, scale: teacher_scale_1, 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,
survey_item2 = create(:student_survey_item, scale: student_scale_1, 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:, school:, likert_score: 1)
@ -27,9 +29,12 @@ describe SubcategoryPresenter do
academic_year:, school:, likert_score: 3)
measure2 = create(:measure, subcategory:)
survey_item3 = create(:teacher_survey_item, measure: measure2, watch_low_benchmark: 1.25,
teacher_scale_2 = create(:teacher_scale, measure: measure2)
student_scale_2 = create(:student_scale, measure: measure2)
survey_item3 = create(:teacher_survey_item, scale: teacher_scale_2, 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,
survey_item4 = create(:student_survey_item, scale: student_scale_2, 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_item3,
academic_year:, school:, likert_score: 1)
@ -100,8 +105,9 @@ describe SubcategoryPresenter do
context 'and the measure does not include high-school-only admin data items' do
before do
measure_of_only_admin_data = create(:measure, subcategory:)
create(:admin_data_item, measure: measure_of_only_admin_data, hs_only_item: false)
create(:admin_data_item, measure: measure_of_only_admin_data, hs_only_item: false)
scale_of_only_admin_data = create(:scale, measure: measure_of_only_admin_data)
create(:admin_data_item, scale: scale_of_only_admin_data, hs_only_item: false)
create(:admin_data_item, scale: scale_of_only_admin_data, hs_only_item: false)
end
it 'returns the admin collection rate' do
expect(subcategory_presenter.admin_collection_rate).to eq [0, 2]
@ -111,8 +117,9 @@ describe SubcategoryPresenter do
context 'and the measure includes high-school-only items' do
before do
measure_of_only_admin_data = create(:measure, subcategory:)
create(:admin_data_item, measure: measure_of_only_admin_data, hs_only_item: true)
create(:admin_data_item, measure: measure_of_only_admin_data, hs_only_item: true)
scale_of_only_admin_data = create(:scale, measure: measure_of_only_admin_data)
create(:admin_data_item, scale: scale_of_only_admin_data, hs_only_item: true)
create(:admin_data_item, scale: scale_of_only_admin_data, hs_only_item: true)
end
it 'returns the admin collection rate' do
expect(subcategory_presenter.admin_collection_rate).to eq %w[N A]
@ -126,8 +133,9 @@ describe SubcategoryPresenter do
school.is_hs = true
school.save
measure_of_only_admin_data = create(:measure, subcategory:)
create(:admin_data_item, measure: measure_of_only_admin_data, hs_only_item: false)
create(:admin_data_item, measure: measure_of_only_admin_data, hs_only_item: false)
scale_of_only_admin_data = create(:scale, measure: measure_of_only_admin_data)
create(:admin_data_item, scale: scale_of_only_admin_data, hs_only_item: false)
create(:admin_data_item, scale: scale_of_only_admin_data, hs_only_item: false)
end
it 'returns the admin collection rate' do
expect(subcategory_presenter.admin_collection_rate).to eq [0, 2]
@ -139,8 +147,9 @@ describe SubcategoryPresenter do
school.is_hs = true
school.save
measure_of_only_admin_data = create(:measure, subcategory:)
create(:admin_data_item, measure: measure_of_only_admin_data, hs_only_item: true)
create(:admin_data_item, measure: measure_of_only_admin_data, hs_only_item: true)
scale_of_only_admin_data = create(:scale, measure: measure_of_only_admin_data)
create(:admin_data_item, scale: scale_of_only_admin_data, hs_only_item: true)
create(:admin_data_item, scale: scale_of_only_admin_data, hs_only_item: true)
end
it 'returns the admin collection rate' do
expect(subcategory_presenter.admin_collection_rate).to eq [0, 2]

@ -11,12 +11,13 @@ describe VarianceChartRowPresenter do
:measure,
name: 'Some Title'
)
scale = create(:scale, measure:)
create(:student_survey_item, measure: measure,
watch_low_benchmark: watch_low_benchmark,
growth_low_benchmark: growth_low_benchmark,
approval_low_benchmark: approval_low_benchmark,
ideal_low_benchmark: ideal_low_benchmark)
create(:student_survey_item, scale:,
watch_low_benchmark:,
growth_low_benchmark:,
approval_low_benchmark:,
ideal_low_benchmark:)
measure
end
@ -29,7 +30,7 @@ describe VarianceChartRowPresenter do
end
let(:presenter) do
VarianceChartRowPresenter.new measure: measure, score: score
VarianceChartRowPresenter.new measure:, score:
end
shared_examples_for 'measure_name' do
@ -155,8 +156,9 @@ describe VarianceChartRowPresenter do
:measure,
name: 'Some Title'
)
scale_with_admin_data = create(:scale, measure: measure_with_admin_data)
create :admin_data_item,
measure: measure_with_admin_data,
scale: scale_with_admin_data,
watch_low_benchmark: watch_low_benchmark,
growth_low_benchmark: growth_low_benchmark,
approval_low_benchmark: approval_low_benchmark,
@ -172,7 +174,8 @@ describe VarianceChartRowPresenter do
context 'when a measure contains teacher survey items' do
before :each do
create :teacher_survey_item, measure: measure
scale = create(:scale, measure:)
create :teacher_survey_item, scale:
end
context 'when there are insufficient teacher survey item responses' do
@ -193,7 +196,8 @@ describe VarianceChartRowPresenter do
context 'when a measure contains student survey items' do
before :each do
create :student_survey_item, measure: measure
scale = create(:scale, measure:)
create :student_survey_item, scale:
end
context 'when there are insufficient student survey item responses' do
@ -205,7 +209,8 @@ describe VarianceChartRowPresenter do
context 'where there are also admin data items' do
before :each do
create :admin_data_item, measure: measure
scale = create(:scale, measure:)
create :admin_data_item, scale:
end
it 'returns the sources for partial results of administrative data and student survey results' do
@ -224,12 +229,13 @@ describe VarianceChartRowPresenter do
context 'sorting scores' do
it 'selects a longer bar before a shorter bar for measures in the approval/ideal zones' do
scale_with_student_survey_items = create(:scale, measure:)
create(:student_survey_item,
measure: measure,
watch_low_benchmark: watch_low_benchmark,
growth_low_benchmark: growth_low_benchmark,
approval_low_benchmark: approval_low_benchmark,
ideal_low_benchmark: ideal_low_benchmark)
scale: scale_with_student_survey_items,
watch_low_benchmark:,
growth_low_benchmark:,
approval_low_benchmark:,
ideal_low_benchmark:)
approval_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(3.7, true, true)
ideal_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(4.4, true, true)
expect(ideal_presenter <=> approval_presenter).to be < 0

@ -9,19 +9,20 @@ describe 'District Admin', js: true do
let(:category) { Category.find_by_name('Teachers & Leadership') }
let(:subcategory) { Subcategory.find_by_name('Teachers & The Teaching Environment') }
let(:measures_for_subcategory) { Measure.where(subcategory:) }
let(:survey_items_for_subcategory) { SurveyItem.where(measure: measures_for_subcategory) }
let(:scales_for_subcategory) {Scale.where(measure: measures_for_subcategory)}
let(:survey_items_for_subcategory) { SurveyItem.where(scale: scales_for_subcategory) }
let(:measure_1A_i) { Measure.find_by_measure_id('1A-i') }
let(:measure_2A_i) { Measure.find_by_measure_id('2A-i') }
let(:measure_4C_i) { Measure.find_by_measure_id('4C-i') }
let(:measure_with_no_survey_responses) { Measure.find_by_measure_id('3A-i') }
let(:survey_item_1_for_measure_1A_i) { SurveyItem.create measure: measure_1A_i, survey_item_id: rand.to_s }
let(:survey_item_2_for_measure_1A_i) { SurveyItem.create measure: measure_1A_i, survey_item_id: rand.to_s }
let(:survey_item_1_for_measure_1A_i) { SurveyItem.create scale: measure_1A_i.scales.first, survey_item_id: rand.to_s }
let(:survey_item_2_for_measure_1A_i) { SurveyItem.create scale: measure_1A_i.scales.first, survey_item_id: rand.to_s }
let(:survey_items_for_measure_1A_i) { SurveyItem.where(measure: measure_1A_i) }
let(:survey_items_for_measure_2A_i) { SurveyItem.where(measure: measure_2A_i) }
let(:survey_items_for_measure_4C_i) { SurveyItem.where(measure: measure_4C_i) }
let(:survey_items_for_measure_1A_i) { measure_1A_i.survey_items }
let(:survey_items_for_measure_2A_i) { measure_2A_i.survey_items }
let(:survey_items_for_measure_4C_i) { measure_4C_i.survey_items }
let(:ay_2020_21) { AcademicYear.find_by_range '2020-21' }
let(:ay_2019_20) { AcademicYear.find_by_range '2019-20' }

@ -6,6 +6,7 @@ describe 'SQM Application' do
let(:academic_year) { create(:academic_year) }
let(:category) { create(:category) }
let(:measure) { create(:measure) }
let(:scale) {create(:scale, measure:)}
before :each do
driven_by :rack_test
@ -26,7 +27,7 @@ describe 'SQM Application' do
context 'at least one measure meets its threshold' do
before :each do
teacher_survey_item = create(:teacher_survey_item, measure: measure)
teacher_survey_item = create(:teacher_survey_item, scale:)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item, academic_year: academic_year, school: school)
end

@ -6,32 +6,36 @@ describe 'categories/show' do
school = create(:school, name: 'Best School')
category = create(:category, name: 'Some Category', description: 'Some description of the category')
category_presenter = CategoryPresenter.new(category: category)
category_presenter = CategoryPresenter.new(category:)
subcategory1 = create(:subcategory, category: category, name: 'A subcategory',
subcategory1 = create(:subcategory, category:, name: 'A subcategory',
description: 'Some description of the subcategory')
subcategory2 = create(:subcategory_with_measures, category: category, name: 'Another subcategory',
subcategory2 = create(:subcategory_with_measures, category:, name: 'Another subcategory',
description: 'Another description of the subcategory')
measure1 = create(:measure, subcategory: subcategory1)
survey_item1 = create(:student_survey_item, measure: measure1, watch_low_benchmark: 1.5, growth_low_benchmark: 2.5,
scale1 = create(:student_scale, measure: measure1)
survey_item1 = create(:student_survey_item, scale: scale1, watch_low_benchmark: 1.5, growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD, survey_item: survey_item1,
academic_year: academic_year, school: school, likert_score: 1)
academic_year:, school:, likert_score: 3)
measure2 = create(:measure, name: 'The second measure name', description: 'The second measure description',
subcategory: subcategory1)
student_survey_item = create(:student_survey_item, measure: measure2, prompt: 'Some prompt for student data',
subcategory: subcategory2)
scale2 = create(:student_scale, measure: measure2)
scale3 = create(:teacher_scale, measure: measure2)
student_survey_item = create(:student_survey_item, scale: scale2, prompt: 'Some prompt for student data',
watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5)
create_list(:survey_item_response, SurveyItemResponse::STUDENT_RESPONSE_THRESHOLD,
survey_item: student_survey_item, academic_year: academic_year, school: school, likert_score: 5)
survey_item: student_survey_item, academic_year:, school:, likert_score: 5)
teacher_survey_item = create(:teacher_survey_item, measure: measure2, prompt: 'Some prompt for teacher data')
teacher_survey_item = create(:teacher_survey_item, scale: scale3, prompt: 'Some prompt for teacher data')
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
survey_item: teacher_survey_item, academic_year: academic_year, school: school, likert_score: 3)
survey_item: teacher_survey_item, academic_year:, school:, likert_score: 3)
admin_data_item = create(:admin_data_item, measure: measure2, description: 'Some admin data item description',
watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5)
admin_data_scale = create(:scale, measure: measure2)
create(:admin_data_item, scale: admin_data_scale, description: 'Some admin data item description',
watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5)
assign :category, category_presenter
assign :categories, [category_presenter]
@ -44,45 +48,45 @@ describe 'categories/show' do
end
it 'renders the category name and description' do
expect(rendered).to match /Some Category/
expect(rendered).to match /Some description of the category/
expect(rendered).to match(/Some Category/)
expect(rendered).to match(/Some description of the category/)
end
context 'for each subcategory' do
it 'renders the subcategory name' do
expect(rendered).to match /A subcategory/
expect(rendered).to match /Another subcategory/
expect(rendered).to match(/A subcategory/)
expect(rendered).to match(/Another subcategory/)
end
it 'renders the subcategory description' do
expect(rendered).to match /Some description of the subcategory/
expect(rendered).to match /Another description of the subcategory/
expect(rendered).to match(/Some description of the subcategory/)
expect(rendered).to match(/Another description of the subcategory/)
end
it 'renders the zone title and fill color for the gauge graph' do
expect(rendered).to match /Growth/
expect(rendered).to match /fill-growth/
expect(rendered).to match(/Growth/)
expect(rendered).to match(/fill-growth/)
end
end
context 'for each measure' do
it 'renders the measure name' do
expect(rendered).to match /The second measure name/
expect(rendered).to match(/The second measure name/)
end
it 'renders the measure description' do
expect(rendered).to match /The second measure description/
expect(rendered).to match(/The second measure description/)
end
it 'renders a gauge graph and the zone title color' do
expect(rendered).to match /Ideal/
expect(rendered).to match /fill-ideal/
expect(rendered).to match(/Approval/)
expect(rendered).to match(/fill-approval/)
end
it 'renders the prompts for survey items and admin data that make up the measure' do
expect(rendered).to match /Some prompt for student data/
expect(rendered).to match /Some prompt for teacher data/
expect(rendered).to match /Some admin data item description/
expect(rendered).to match(/Some prompt for student data/)
expect(rendered).to match(/Some prompt for teacher data/)
expect(rendered).to match(/Some admin data item description/)
end
end
end

@ -5,8 +5,9 @@ describe 'overview/index' do
let(:support_for_teaching) do
measure = create(:measure, name: 'Support For Teaching Development & Growth', measure_id: '1')
scale = create(:scale, measure:)
create(:student_survey_item,
measure: measure,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
@ -16,8 +17,9 @@ describe 'overview/index' do
let(:effective_leadership) do
measure = create(:measure, name: 'Effective Leadership', measure_id: '2')
scale = create(:scale, measure:)
create(:teacher_survey_item,
measure: measure,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
@ -27,8 +29,9 @@ describe 'overview/index' do
let(:professional_qualifications) do
measure = create(:measure, name: 'Professional Qualifications', measure_id: '3')
scale = create(:scale, measure:)
create(:admin_data_item,
measure: measure,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
@ -71,14 +74,15 @@ describe 'overview/index' do
context 'when all the presenters have a non-nil score' do
let(:variance_chart_row_presenters) do
measure = create(:measure, name: 'Display Me', measure_id: 'display-me')
scale = create(:scale, measure:)
create(:student_survey_item,
measure: measure,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5)
[
VarianceChartRowPresenter.new(measure: measure,
VarianceChartRowPresenter.new(measure:,
score: Score.new(rand))
]
end

@ -6,8 +6,17 @@ describe 'overview/_variance_chart.html.erb' do
let(:survey_items1) { [create(:student_survey_item)] }
let(:survey_items2) { [create(:student_survey_item)] }
let(:higher_scoring_measure) { create(:measure, survey_items: survey_items1) }
let(:lower_scoring_measure) { create(:measure, survey_items: survey_items2) }
let(:higher_scoring_measure) do
measure = create(:measure)
create(:scale, measure:, survey_items: survey_items1)
measure
end
let(:lower_scoring_measure) do
measure = create(:measure)
create(:scale, measure:, survey_items: survey_items2)
measure
end
before :each do
presenters = [
@ -15,7 +24,7 @@ describe 'overview/_variance_chart.html.erb' do
VarianceChartRowPresenter.new(measure: higher_scoring_measure, score: Score.new(5))
]
render partial: 'variance_chart', locals: { presenters: presenters }
render partial: 'variance_chart', locals: { presenters: }
end
it 'displays higher scoring measures above lower scoring measures' do
@ -41,7 +50,7 @@ describe 'overview/_variance_chart.html.erb' do
VarianceChartRowPresenter.new(measure: another_measure_lacking_score, score: Score.new(nil))
]
render partial: 'variance_chart', locals: { presenters: presenters }
render partial: 'variance_chart', locals: { presenters: }
end
it "displays the text 'insufficient data' for an empty dataset" do

Loading…
Cancel
Save