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.
This commit is contained in:
rebuilt 2022-02-16 20:40:32 +01:00
parent 1ca88bf6d1
commit d4df7cbc06
44 changed files with 1053 additions and 856 deletions

View file

@ -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

View file

@ -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
@ideal_low_benchmark = benchmark(:ideal_low_benchmark)
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
def sufficient_data?(school:, academic_year:)
sufficient_student_data?(school:, academic_year:) || sufficient_teacher_data?(school:, academic_year:)
end
private

View file

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

21
app/models/scale.rb Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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