diff --git a/app/models/measure.rb b/app/models/measure.rb index 67c7e9fa..04d9ed8f 100644 --- a/app/models/measure.rb +++ b/app/models/measure.rb @@ -8,6 +8,10 @@ class Measure < ActiveRecord::Base has_many :survey_items, through: :scales has_many :survey_item_responses, through: :survey_items + def construct_id + measure_id + end + def none_meet_threshold?(school:, academic_year:) @none_meet_threshold ||= Hash.new do |memo, (school, academic_year)| memo[[school, academic_year]] = !sufficient_survey_responses?(school:, academic_year:) diff --git a/app/models/scale.rb b/app/models/scale.rb index b53a68cc..03f30155 100644 --- a/app/models/scale.rb +++ b/app/models/scale.rb @@ -3,10 +3,19 @@ class Scale < ApplicationRecord belongs_to :measure, counter_cache: true has_one :category, through: :measure + has_one :subcategory, through: :measure has_many :survey_items has_many :survey_item_responses, through: :survey_items has_many :admin_data_items + def construct_id + scale_id + end + + def parent_survey_items + @parent_survey_items ||= survey_items.parent_survey_items + end + def score(school:, academic_year:) @score ||= Hash.new do |memo, (school, academic_year)| memo[[school, academic_year]] = begin diff --git a/app/presenters/analyze/bar_presenter.rb b/app/presenters/analyze/bar_presenter.rb index 7dc9bdc1..39c266f7 100644 --- a/app/presenters/analyze/bar_presenter.rb +++ b/app/presenters/analyze/bar_presenter.rb @@ -3,16 +3,16 @@ module Analyze class BarPresenter include AnalyzeHelper - attr_reader :score, :x_position, :academic_year, :measure_id, :measure, :color + attr_reader :score, :x_position, :academic_year, :construct_id, :construct, :color MINIMUM_BAR_HEIGHT = 2 - def initialize(measure:, academic_year:, score:, x_position:, color:) + def initialize(construct:, construct_id:, academic_year:, score:, x_position:, color:) @score = score @x_position = x_position @academic_year = academic_year - @measure = measure - @measure_id = measure.measure_id + @construct = construct + @construct_id = construct_id @color = color end @@ -40,12 +40,13 @@ module Analyze (score.average - low_benchmark) / (zone.high_benchmark - low_benchmark) end + Zone = Struct.new(:low_benchmark, :high_benchmark, :type) def zone 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 + watch_low_benchmark: construct.watch_low_benchmark, + growth_low_benchmark: construct.growth_low_benchmark, + approval_low_benchmark: construct.approval_low_benchmark, + ideal_low_benchmark: construct.ideal_low_benchmark ) zones.zone_for_score(score.average) end diff --git a/app/presenters/analyze/graph/all_data.rb b/app/presenters/analyze/graph/all_data.rb index 80d9652b..1c493be7 100644 --- a/app/presenters/analyze/graph/all_data.rb +++ b/app/presenters/analyze/graph/all_data.rb @@ -4,7 +4,6 @@ module Analyze module Graph class AllData include Analyze::Graph::Column - def label ["All", "Data"] end @@ -37,13 +36,13 @@ module Analyze Analyze::Group::Base.new(name: nil, slug: nil, graph: nil) end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) scores = academic_years.map do |academic_year| - measure.score(school:, academic_year:) + construct.score(school:, academic_year:) end scores.none? { |score| score.meets_student_threshold? || score.meets_teacher_threshold? || score.meets_admin_data_threshold? } @@ -53,8 +52,8 @@ module Analyze ["survey response", "rate below 25%"] end - def score(measure:, school:, academic_year:) - measure.score(school:, academic_year:) + def score(construct:, school:, academic_year:) + construct.score(school:, academic_year:) end end end diff --git a/app/presenters/analyze/graph/all_parent.rb b/app/presenters/analyze/graph/all_parent.rb index c051f624..d140e6e2 100644 --- a/app/presenters/analyze/graph/all_parent.rb +++ b/app/presenters/analyze/graph/all_parent.rb @@ -3,16 +3,18 @@ module Analyze module Graph class AllParent - def initialize(scales:) - @scales = scales - end - def to_s - "All Data" + "All Parent" end def slug - "all-data" + "all-parent" + end + + def columns(construct:) + construct.scales.parent_scales.map do |scale| + Analyze::Graph::Column::Parent::Scale.new(scale:) + end end def source diff --git a/app/presenters/analyze/graph/column/all_admin.rb b/app/presenters/analyze/graph/column/all_admin.rb index 4cb84fda..7662ceb9 100644 --- a/app/presenters/analyze/graph/column/all_admin.rb +++ b/app/presenters/analyze/graph/column/all_admin.rb @@ -12,13 +12,13 @@ module Analyze "school data" end - def show_irrelevancy_message?(measure:) - !measure.includes_admin_data_items? + def show_irrelevancy_message?(construct:) + !construct.includes_admin_data_items? end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) !academic_years.any? do |year| - measure.sufficient_admin_data?(school:, academic_year: year) + construct.sufficient_admin_data?(school:, academic_year: year) end end @@ -26,8 +26,8 @@ module Analyze ["data not", "available"] end - def score(measure:, school:, academic_year:) - measure.admin_score(school:, academic_year:) + def score(construct:, school:, academic_year:) + construct.admin_score(school:, academic_year:) end def type diff --git a/app/presenters/analyze/graph/column/all_data.rb b/app/presenters/analyze/graph/column/all_data.rb index 718bae3f..854aa3f7 100644 --- a/app/presenters/analyze/graph/column/all_data.rb +++ b/app/presenters/analyze/graph/column/all_data.rb @@ -6,13 +6,13 @@ module Analyze %w[All Data] end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) scores = academic_years.map do |year| - measure.score(school:, academic_year: year) + construct.score(school:, academic_year: year) end scores.none? do |score| @@ -20,8 +20,8 @@ module Analyze end end - def score(measure:, school:, academic_year:) - measure.score(school:, academic_year:) || 0 + def score(construct:, school:, academic_year:) + construct.score(school:, academic_year:) || 0 end def type @@ -32,8 +32,8 @@ module Analyze "student surveys" end - def n_size(measure:, school:, academic_year:) - SurveyItemResponse.where(survey_item: measure.student_survey_items, school:, grade: grades, + def n_size(construct:, school:, academic_year:) + SurveyItemResponse.where(survey_item: construct.student_survey_items, school:, grade: grades, academic_year:).select(:response_id).distinct.count end end diff --git a/app/presenters/analyze/graph/column/all_student.rb b/app/presenters/analyze/graph/column/all_student.rb index 011dc38c..364cad41 100644 --- a/app/presenters/analyze/graph/column/all_student.rb +++ b/app/presenters/analyze/graph/column/all_student.rb @@ -16,29 +16,29 @@ module Analyze ["survey response", "rate below 25%"] end - def show_irrelevancy_message?(measure:) - !measure.includes_student_survey_items? + def show_irrelevancy_message?(construct:) + !construct.includes_student_survey_items? end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) scores = academic_years.map do |academic_year| - measure.student_score(school:, academic_year:) + construct.student_score(school:, academic_year:) end scores.none? { |score| score.meets_student_threshold? } end - def score(measure:, school:, academic_year:) - measure.student_score(school:, academic_year:) + def score(construct:, school:, academic_year:) + construct.student_score(school:, academic_year:) end def type :student end - def n_size(measure:, school:, academic_year:) + def n_size(construct:, school:, academic_year:) grades = Respondent.by_school_and_year(school:, academic_year:)&.enrollment_by_grade&.keys - SurveyItemResponse.where(survey_item: measure.student_survey_items, school:, grade: grades, + SurveyItemResponse.where(survey_item: construct.student_survey_items, school:, grade: grades, academic_year:).select(:response_id).distinct.count end end diff --git a/app/presenters/analyze/graph/column/all_survey_data.rb b/app/presenters/analyze/graph/column/all_survey_data.rb index b3ef1698..a2834b53 100644 --- a/app/presenters/analyze/graph/column/all_survey_data.rb +++ b/app/presenters/analyze/graph/column/all_survey_data.rb @@ -12,13 +12,13 @@ module Analyze "survey data" end - def show_irrelevancy_message?(measure:) - !measure.includes_teacher_survey_items? || !measure.includes_student_survey_items? || !measure.includes_parent_survey_items? + def show_irrelevancy_message?(construct:) + !construct.includes_teacher_survey_items? || !construct.includes_student_survey_items? || !construct.includes_parent_survey_items? end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) scores = academic_years.map do |academic_year| - score(measure:, school:, academic_year:) + score(construct:, school:, academic_year:) end scores.all? { |score| !score.meets_student_threshold? && !score.meets_teacher_threshold? } @@ -28,9 +28,9 @@ module Analyze ["survey response", "rate below 25%"] end - def score(measure:, school:, academic_year:) - teacher_score = measure.teacher_score(school:, academic_year:) - student_score = measure.student_score(school:, academic_year:) + def score(construct:, school:, academic_year:) + teacher_score = construct.teacher_score(school:, academic_year:) + student_score = construct.student_score(school:, academic_year:) averages = [] averages << student_score.average unless student_score.average.nil? diff --git a/app/presenters/analyze/graph/column/all_teacher.rb b/app/presenters/analyze/graph/column/all_teacher.rb index 66ff7c7c..972791ec 100644 --- a/app/presenters/analyze/graph/column/all_teacher.rb +++ b/app/presenters/analyze/graph/column/all_teacher.rb @@ -16,28 +16,28 @@ module Analyze ["survey response", "rate below 25%"] end - def show_irrelevancy_message?(measure:) - !measure.includes_teacher_survey_items? + def show_irrelevancy_message?(construct:) + !construct.includes_teacher_survey_items? end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) scores = academic_years.map do |year| - measure.score(school:, academic_year: year) + construct.score(school:, academic_year: year) end scores.all? { |score| !score.meets_teacher_threshold? } end - def score(measure:, school:, academic_year:) - measure.teacher_score(school:, academic_year:) + def score(construct:, school:, academic_year:) + construct.teacher_score(school:, academic_year:) end def type :teacher end - def n_size(measure:, school:, academic_year:) - SurveyItemResponse.where(survey_item: measure.teacher_survey_items, school:, + def n_size(construct:, school:, academic_year:) + SurveyItemResponse.where(survey_item: construct.teacher_survey_items, school:, academic_year:).pluck(:response_id).uniq.count end end diff --git a/app/presenters/analyze/graph/column/column_base.rb b/app/presenters/analyze/graph/column/column_base.rb index f15611ae..40647317 100644 --- a/app/presenters/analyze/graph/column/column_base.rb +++ b/app/presenters/analyze/graph/column/column_base.rb @@ -12,11 +12,11 @@ module Analyze raise NotImplementedError end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) raise NotImplementedError end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) raise NotImplementedError end @@ -24,7 +24,7 @@ module Analyze raise NotImplementedError end - def score(measure:, school:, academic_year:) + def score(construct:, school:, academic_year:) raise NotImplementedError end @@ -32,12 +32,12 @@ module Analyze raise NotImplementedError end - def n_size(measure:, school:, academic_year:) + def n_size(construct:, school:, academic_year:) raise NotImplementedError end - def bubble_up_averages(measure:, averages:) - measure.student_scales.map do |scale| + def bubble_up_averages(construct:, averages:) + construct.student_scales.map do |scale| scale.survey_items.map do |survey_item| averages[survey_item] end.remove_blanks.average diff --git a/app/presenters/analyze/graph/column/ell.rb b/app/presenters/analyze/graph/column/ell.rb index 9fa549ef..22de434f 100644 --- a/app/presenters/analyze/graph/column/ell.rb +++ b/app/presenters/analyze/graph/column/ell.rb @@ -18,11 +18,11 @@ module Analyze "student" end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) false end @@ -30,18 +30,18 @@ module Analyze :student end - def n_size(measure:, school:, academic_year:) - SurveyItemResponse.where(ell:, survey_item: measure.student_survey_items, school:, grade: grades(school:, academic_year:), + def n_size(construct:, school:, academic_year:) + SurveyItemResponse.where(ell:, survey_item: construct.student_survey_items, school:, grade: grades(school:, academic_year:), academic_year:).select(:response_id).distinct.count end - def score(measure:, school:, academic_year:) - meets_student_threshold = sufficient_student_responses?(measure:, school:, academic_year:) + def score(construct:, school:, academic_year:) + meets_student_threshold = sufficient_student_responses?(construct:, school:, academic_year:) return Score::NIL_SCORE unless meets_student_threshold - averages = SurveyItemResponse.averages_for_ell(measure.student_survey_items, school, academic_year, + averages = SurveyItemResponse.averages_for_ell(construct.student_survey_items, school, academic_year, ell) - average = bubble_up_averages(measure:, averages:).round(2) + average = bubble_up_averages(construct:, averages:).round(2) Score.new(average:, meets_teacher_threshold: false, @@ -49,11 +49,11 @@ module Analyze meets_admin_data_threshold: false) end - def sufficient_student_responses?(measure:, school:, academic_year:) - return false unless measure.subcategory.response_rate(school:, academic_year:).meets_student_threshold? + def sufficient_student_responses?(construct:, school:, academic_year:) + return false unless construct.subcategory.response_rate(school:, academic_year:).meets_student_threshold? yearly_counts = SurveyItemResponse.where(school:, academic_year:, - ell:, survey_item: measure.student_survey_items).group(:ell).select(:response_id).distinct(:response_id).count + ell:, survey_item: construct.student_survey_items).group(:ell).select(:response_id).distinct(:response_id).count yearly_counts.any? do |count| count[1] >= 10 end diff --git a/app/presenters/analyze/graph/column/gender.rb b/app/presenters/analyze/graph/column/gender.rb index cdf974f1..57b0673b 100644 --- a/app/presenters/analyze/graph/column/gender.rb +++ b/app/presenters/analyze/graph/column/gender.rb @@ -18,11 +18,11 @@ module Analyze "student surveys" end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) false end @@ -30,18 +30,18 @@ module Analyze :student end - def n_size(measure:, school:, academic_year:) - SurveyItemResponse.where(gender:, survey_item: measure.student_survey_items, school:, grade: grades(school:, academic_year:), + def n_size(construct:, school:, academic_year:) + SurveyItemResponse.where(gender:, survey_item: construct.student_survey_items, school:, grade: grades(school:, academic_year:), academic_year:).select(:response_id).distinct.count end - def score(measure:, school:, academic_year:) - meets_student_threshold = sufficient_student_responses?(measure:, school:, academic_year:) + def score(construct:, school:, academic_year:) + meets_student_threshold = sufficient_student_responses?(construct:, school:, academic_year:) return Score::NIL_SCORE unless meets_student_threshold - averages = SurveyItemResponse.averages_for_gender(measure.student_survey_items, school, academic_year, + averages = SurveyItemResponse.averages_for_gender(construct.student_survey_items, school, academic_year, gender) - average = bubble_up_averages(measure:, averages:).round(2) + average = bubble_up_averages(construct:, averages:).round(2) Score.new(average:, meets_teacher_threshold: false, @@ -49,11 +49,11 @@ module Analyze meets_admin_data_threshold: false) end - def sufficient_student_responses?(measure:, school:, academic_year:) - return false unless measure.subcategory.response_rate(school:, academic_year:).meets_student_threshold? + def sufficient_student_responses?(construct:, school:, academic_year:) + return false unless construct.subcategory.response_rate(school:, academic_year:).meets_student_threshold? yearly_counts = SurveyItemResponse.where(school:, academic_year:, - gender:, survey_item: measure.student_survey_items).group(:gender).select(:response_id).distinct(:response_id).count + gender:, survey_item: construct.student_survey_items).group(:gender).select(:response_id).distinct(:response_id).count yearly_counts.any? do |count| count[1] >= 10 end diff --git a/app/presenters/analyze/graph/column/grade.rb b/app/presenters/analyze/graph/column/grade.rb index 31c03ea5..ccabd6a9 100644 --- a/app/presenters/analyze/graph/column/grade.rb +++ b/app/presenters/analyze/graph/column/grade.rb @@ -18,11 +18,11 @@ module Analyze "student surveys" end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) false end @@ -30,18 +30,18 @@ module Analyze :student end - def n_size(measure:, school:, academic_year:) - SurveyItemResponse.where(grade:, survey_item: measure.student_survey_items, school:, + def n_size(construct:, school:, academic_year:) + SurveyItemResponse.where(grade:, survey_item: construct.student_survey_items, school:, academic_year:).select(:response_id).distinct.count end - def score(measure:, school:, academic_year:) - meets_student_threshold = sufficient_student_responses?(measure:, school:, academic_year:) + def score(construct:, school:, academic_year:) + meets_student_threshold = sufficient_student_responses?(construct:, school:, academic_year:) return Score::NIL_SCORE unless meets_student_threshold - averages = SurveyItemResponse.averages_for_grade(measure.student_survey_items, school, + averages = SurveyItemResponse.averages_for_grade(construct.student_survey_items, school, academic_year, grade) - average = bubble_up_averages(measure:, averages:).round(2) + average = bubble_up_averages(construct:, averages:).round(2) Score.new(average:, meets_teacher_threshold: false, @@ -49,11 +49,11 @@ module Analyze meets_admin_data_threshold: false) end - def sufficient_student_responses?(measure:, school:, academic_year:) - return false unless measure.subcategory.response_rate(school:, academic_year:).meets_student_threshold? + def sufficient_student_responses?(construct:, school:, academic_year:) + return false unless construct.subcategory.response_rate(school:, academic_year:).meets_student_threshold? yearly_counts = SurveyItemResponse.where(school:, academic_year:, - survey_item: measure.student_survey_items).group(:grade).select(:response_id).distinct(:response_id).count + survey_item: construct.student_survey_items).group(:grade).select(:response_id).distinct(:response_id).count yearly_counts.any? do |count| count[1] >= 10 end diff --git a/app/presenters/analyze/graph/column/grouped_bar_column_presenter.rb b/app/presenters/analyze/graph/column/grouped_bar_column_presenter.rb index 123b603f..437e2b21 100644 --- a/app/presenters/analyze/graph/column/grouped_bar_column_presenter.rb +++ b/app/presenters/analyze/graph/column/grouped_bar_column_presenter.rb @@ -6,14 +6,14 @@ module Analyze class GroupedBarColumnPresenter include AnalyzeHelper - attr_reader :measure_name, :measure_id, :category, :position, :measure, :school, :academic_years, + attr_reader :construct_name, :construct_id, :category, :position, :construct, :school, :academic_years, :number_of_columns, :config - def initialize(measure:, school:, academic_years:, position:, number_of_columns:, config:) - @measure = measure - @measure_name = @measure.name - @measure_id = @measure.measure_id - @category = @measure.subcategory.category + def initialize(school:, academic_years:, position:, number_of_columns:, config:, construct:) + @construct = construct + @construct_name = @construct&.name + @construct_id = @construct&.construct_id + @category = @construct&.subcategory&.category @school = school @academic_years = academic_years @position = position @@ -23,7 +23,7 @@ module Analyze def bars @bars ||= academic_years.map.with_index do |academic_year, index| - Analyze::BarPresenter.new(measure:, academic_year:, + Analyze::BarPresenter.new(construct: construct, construct_id:, academic_year:, score: score(academic_year:), x_position: bar_x(index), color: bar_color(academic_year)) @@ -36,19 +36,19 @@ module Analyze end def show_irrelevancy_message? - config.show_irrelevancy_message?(measure:) + config.show_irrelevancy_message?(construct:) unless construct.nil? end def show_insufficient_data_message? - config.show_insufficient_data_message?(measure:, school:, academic_years:) + config.show_insufficient_data_message?(construct:, school:, academic_years:) unless construct.nil? end def score(academic_year:) - config.score(measure:, school:, academic_year:) + config.score(construct:, school:, academic_year:) unless construct.nil? end def n_size(academic_year:) - config.n_size(measure:, school:, academic_year:) + config.n_size(construct:, school:, academic_year:) unless construct.nil? end def basis diff --git a/app/presenters/analyze/graph/column/income.rb b/app/presenters/analyze/graph/column/income.rb index 18c6e470..6823a524 100644 --- a/app/presenters/analyze/graph/column/income.rb +++ b/app/presenters/analyze/graph/column/income.rb @@ -18,11 +18,11 @@ module Analyze "student surveys" end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) false end @@ -30,18 +30,18 @@ module Analyze :student end - def n_size(measure:, school:, academic_year:) - SurveyItemResponse.where(income:, survey_item: measure.student_survey_items, school:, grade: grades(school:, academic_year:), + def n_size(construct:, school:, academic_year:) + SurveyItemResponse.where(income:, survey_item: construct.student_survey_items, school:, grade: grades(school:, academic_year:), academic_year:).select(:response_id).distinct.count end - def score(measure:, school:, academic_year:) - meets_student_threshold = sufficient_student_responses?(measure:, school:, academic_year:) + def score(construct:, school:, academic_year:) + meets_student_threshold = sufficient_student_responses?(construct:, school:, academic_year:) return Score::NIL_SCORE unless meets_student_threshold - averages = SurveyItemResponse.averages_for_income(measure.student_survey_items, school, academic_year, + averages = SurveyItemResponse.averages_for_income(construct.student_survey_items, school, academic_year, income) - average = bubble_up_averages(measure:, averages:).round(2) + average = bubble_up_averages(construct:, averages:).round(2) Score.new(average:, meets_teacher_threshold: false, @@ -49,11 +49,11 @@ module Analyze meets_admin_data_threshold: false) end - def sufficient_student_responses?(measure:, school:, academic_year:) - return false unless measure.subcategory.response_rate(school:, academic_year:).meets_student_threshold? + def sufficient_student_responses?(construct:, school:, academic_year:) + return false unless construct.subcategory.response_rate(school:, academic_year:).meets_student_threshold? yearly_counts = SurveyItemResponse.where(school:, academic_year:, - income:, survey_item: measure.student_survey_items).group(:income).select(:response_id).distinct(:response_id).count + income:, survey_item: construct.student_survey_items).group(:income).select(:response_id).distinct(:response_id).count yearly_counts.any? do |count| count[1] >= 10 end diff --git a/app/presenters/analyze/graph/column/language.rb b/app/presenters/analyze/graph/column/language.rb deleted file mode 100644 index 68d6915d..00000000 --- a/app/presenters/analyze/graph/column/language.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -module Analyze - module Graph - module Column - class Language < ColumnBase - attr_reader :language, :label - - def initialize(languages:, label:) - @language = languages - @label = label - end - - def basis - "parent surveys" - end - - def show_irrelevancy_message?(measure:) - false - end - - def show_insufficient_data_message?(measure:, school:, academic_years:) - false - end - - def type - :parent - end - - def n_size(measure:, school:, academic_year:) - SurveyItemResponse.joins([parent: :languages]).where(languages: { designation: designations }, survey_item: measure.parent_survey_items, school:, academic_year:).select(:parent_id).distinct.count - end - - def score(measure:, school:, academic_year:) - return Score::NIL_SCORE if n_size(measure:, school:, academic_year:) < 10 - - averages = SurveyItemResponse.averages_for_language(measure.parent_survey_items, school, academic_year, - designations) - average = bubble_up_averages(measure:, averages:).round(2) - Score.new(average:, - meets_teacher_threshold: false, - meets_student_threshold: true, - meets_admin_data_threshold: false) - end - - def designations - language.map(&:designation) - end - - def bubble_up_averages(measure:, averages:) - measure.parent_scales.map do |scale| - scale.survey_items.map do |survey_item| - averages[survey_item] - end.remove_blanks.average - end.remove_blanks.average - end - end - end - end -end diff --git a/app/presenters/analyze/graph/column/parent/language.rb b/app/presenters/analyze/graph/column/parent/language.rb new file mode 100644 index 00000000..90bbe071 --- /dev/null +++ b/app/presenters/analyze/graph/column/parent/language.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Analyze + module Graph + module Column + module Parent + class Language < ColumnBase + attr_reader :language, :label + + def initialize(languages:, label:) + @language = languages + @label = label + end + + def basis + "parent surveys" + end + + def show_irrelevancy_message?(construct:) + false + end + + def show_insufficient_data_message?(construct:, school:, academic_years:) + false + end + + def type + :parent + end + + def n_size(construct:, school:, academic_year:) + SurveyItemResponse.joins([parent: :languages]).where(languages: { designation: designations }, survey_item: construct.parent_survey_items, school:, academic_year:).select(:parent_id).distinct.count + end + + def score(construct:, school:, academic_year:) + return Score::NIL_SCORE if n_size(construct:, school:, academic_year:) < 10 + + averages = SurveyItemResponse.averages_for_language(construct.parent_survey_items, school, academic_year, + designations) + average = bubble_up_averages(construct:, averages:).round(2) + Score.new(average:, + meets_teacher_threshold: false, + meets_student_threshold: true, + meets_admin_data_threshold: false) + end + + def designations + language.map(&:designation) + end + + def bubble_up_averages(construct:, averages:) + name = construct.class.name.downcase + send("#{name}_bubble_up_averages", construct:, averages:) + end + + def measure_bubble_up_averages(construct:, averages:) + construct.parent_scales.map do |scale| + scale_bubble_up_averages(construct: scale, averages:) + end.remove_blanks.average + end + + def scale_bubble_up_averages(construct:, averages:) + construct.survey_items.map do |survey_item| + averages[survey_item] + end.remove_blanks.average + end + end + end + end + end +end diff --git a/app/presenters/analyze/graph/column/parent/scale.rb b/app/presenters/analyze/graph/column/parent/scale.rb index f18f7cb0..4f0a746e 100644 --- a/app/presenters/analyze/graph/column/parent/scale.rb +++ b/app/presenters/analyze/graph/column/parent/scale.rb @@ -17,11 +17,11 @@ module Analyze "parent data" end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) false end @@ -29,7 +29,7 @@ module Analyze ["data not", "available"] end - def score(measure:, school:, academic_year:) + def score(construct:, school:, academic_year:) average = scale.parent_score(school:, academic_year:) Score.new(average:, meets_teacher_threshold: true, meets_student_threshold: true, meets_admin_data_threshold: true) end @@ -38,7 +38,7 @@ module Analyze :parent end - def n_size(measure:, school:, academic_year:) + def n_size(construct:, school:, academic_year:) SurveyItemResponse.where(survey_item: scale.survey_items.parent_survey_items, school:, academic_year:).select(:response_id).distinct.count end end diff --git a/app/presenters/analyze/graph/column/race.rb b/app/presenters/analyze/graph/column/race.rb index 23ec9fe8..31def5ec 100644 --- a/app/presenters/analyze/graph/column/race.rb +++ b/app/presenters/analyze/graph/column/race.rb @@ -19,11 +19,11 @@ module Analyze "student surveys" end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) false end @@ -31,21 +31,21 @@ module Analyze :student end - def n_size(measure:, school:, academic_year:) + def n_size(construct:, school:, academic_year:) SurveyItemResponse.joins("JOIN student_races on survey_item_responses.student_id = student_races.student_id JOIN students on students.id = student_races.student_id").where( school:, academic_year:, - survey_item: measure.student_survey_items + survey_item: construct.student_survey_items ).where("student_races.race_id": race.id).select(:response_id).distinct.count end - def score(measure:, school:, academic_year:) - meets_student_threshold = sufficient_student_responses?(measure:, school:, academic_year:) + def score(construct:, school:, academic_year:) + meets_student_threshold = sufficient_student_responses?(construct:, school:, academic_year:) return Score::NIL_SCORE unless meets_student_threshold - measure.student_survey_items + construct.student_survey_items averages = SurveyItemResponse.averages_for_race(school, academic_year, race) - average = bubble_up_averages(measure:, averages:).round(2) + average = bubble_up_averages(construct:, averages:).round(2) Score.new(average:, meets_teacher_threshold: false, @@ -53,8 +53,8 @@ module Analyze meets_admin_data_threshold: false) end - def sufficient_student_responses?(measure:, school:, academic_year:) - return false unless measure.subcategory.response_rate(school:, academic_year:).meets_student_threshold? + def sufficient_student_responses?(construct:, school:, academic_year:) + return false unless construct.subcategory.response_rate(school:, academic_year:).meets_student_threshold? number_of_students_for_a_racial_group = SurveyItemResponse.joins("JOIN student_races on survey_item_responses.student_id = student_races.student_id JOIN students on students.id = student_races.student_id").where( school:, academic_year: diff --git a/app/presenters/analyze/graph/column/sped.rb b/app/presenters/analyze/graph/column/sped.rb index 4fe1d3ae..32b47eab 100644 --- a/app/presenters/analyze/graph/column/sped.rb +++ b/app/presenters/analyze/graph/column/sped.rb @@ -18,11 +18,11 @@ module Analyze "student surveys" end - def show_irrelevancy_message?(measure:) + def show_irrelevancy_message?(construct:) false end - def show_insufficient_data_message?(measure:, school:, academic_years:) + def show_insufficient_data_message?(construct:, school:, academic_years:) false end @@ -30,18 +30,18 @@ module Analyze :student end - def n_size(measure:, school:, academic_year:) - SurveyItemResponse.where(sped:, survey_item: measure.student_survey_items, school:, grade: grades(school:, academic_year:), + def n_size(construct:, school:, academic_year:) + SurveyItemResponse.where(sped:, survey_item: construct.student_survey_items, school:, grade: grades(school:, academic_year:), academic_year:).select(:response_id).distinct.count end - def score(measure:, school:, academic_year:) - meets_student_threshold = sufficient_student_responses?(measure:, school:, academic_year:) + def score(construct:, school:, academic_year:) + meets_student_threshold = sufficient_student_responses?(construct:, school:, academic_year:) return Score::NIL_SCORE unless meets_student_threshold - averages = SurveyItemResponse.averages_for_sped(measure.student_survey_items, school, academic_year, + averages = SurveyItemResponse.averages_for_sped(construct.student_survey_items, school, academic_year, sped) - average = bubble_up_averages(measure:, averages:).round(2) + average = bubble_up_averages(construct:, averages:).round(2) Score.new(average:, meets_teacher_threshold: false, @@ -49,11 +49,11 @@ module Analyze meets_admin_data_threshold: false) end - def sufficient_student_responses?(measure:, school:, academic_year:) - return false unless measure.subcategory.response_rate(school:, academic_year:).meets_student_threshold? + def sufficient_student_responses?(construct:, school:, academic_year:) + return false unless construct.subcategory.response_rate(school:, academic_year:).meets_student_threshold? yearly_counts = SurveyItemResponse.where(school:, academic_year:, - sped:, survey_item: measure.student_survey_items).group(:sped).select(:response_id).distinct(:response_id).count + sped:, survey_item: construct.student_survey_items).group(:sped).select(:response_id).distinct(:response_id).count yearly_counts.any? do |count| count[1] >= 10 end diff --git a/app/presenters/analyze/graph/parents_by_language.rb b/app/presenters/analyze/graph/parents_by_language.rb index 06079db8..f08a463b 100644 --- a/app/presenters/analyze/graph/parents_by_language.rb +++ b/app/presenters/analyze/graph/parents_by_language.rb @@ -3,8 +3,6 @@ module Analyze module Graph class ParentsByLanguage - attr_reader :speds - ALL_LANGUAGES = Language.all ENGLISH_LANGUAGES = ALL_LANGUAGES.select { |language| language.designation == "English" } UNKNOWN_LANGUAGES = ALL_LANGUAGES.select { |language| language.designation == "Prefer not to disclose" } @@ -20,10 +18,10 @@ module Analyze def columns [].tap do |array| - array << Analyze::Graph::Column::Language.new(languages: ENGLISH_LANGUAGES, label: ["English", "Speaking"]) - array << Analyze::Graph::Column::Language.new(languages: NON_ENGLISH_LANGUAGES, label: ["Non English", "Speaking"]) - array << Analyze::Graph::Column::Language.new(languages: UNKNOWN_LANGUAGES, label: ["Unknown"]) - array << Analyze::Graph::Column::Language.new(languages: ALL_LANGUAGES, label: ["All", "Parents"]) + array << Analyze::Graph::Column::Parent::Language.new(languages: ENGLISH_LANGUAGES, label: ["English", "Speaking"]) + array << Analyze::Graph::Column::Parent::Language.new(languages: NON_ENGLISH_LANGUAGES, label: ["Non English", "Speaking"]) + array << Analyze::Graph::Column::Parent::Language.new(languages: UNKNOWN_LANGUAGES, label: ["Unknown"]) + array << Analyze::Graph::Column::Parent::Language.new(languages: ALL_LANGUAGES, label: ["All", "Parents"]) end end diff --git a/app/presenters/analyze/graph/students_and_teachers.rb b/app/presenters/analyze/graph/students_and_teachers.rb index d5902a77..2e797052 100644 --- a/app/presenters/analyze/graph/students_and_teachers.rb +++ b/app/presenters/analyze/graph/students_and_teachers.rb @@ -4,6 +4,7 @@ module Analyze module Graph class StudentsAndTeachers include Analyze::Graph::Column + def to_s "Students & Teachers" end diff --git a/app/presenters/analyze/graph/students_and_teachers_and_parents.rb b/app/presenters/analyze/graph/students_and_teachers_and_parents.rb index 7cfd5e86..7917f697 100644 --- a/app/presenters/analyze/graph/students_and_teachers_and_parents.rb +++ b/app/presenters/analyze/graph/students_and_teachers_and_parents.rb @@ -4,6 +4,7 @@ module Analyze module Graph class StudentsAndTeachersAndParents include Analyze::Graph::Column + def to_s "Students, Teachers & Parents" end diff --git a/app/presenters/analyze/presenter.rb b/app/presenters/analyze/presenter.rb index 50e27b92..81c039ac 100644 --- a/app/presenters/analyze/presenter.rb +++ b/app/presenters/analyze/presenter.rb @@ -102,7 +102,7 @@ module Analyze graphs.select { |graph| graph.class.name.demodulize.first == first_char_of_class_name }.map(&:group) .reject { |group| group.name.nil? } .sort_by { |group| group.name } - .uniq + .uniq { |group| group.slug } end end @@ -133,15 +133,13 @@ module Analyze def show_secondary_graph?(measure:) return false unless measure.includes_parent_survey_items? - ["all-data", "students-and-teachers-and-parents"].include?(graph.slug) + ["all-data", "students-and-teachers-and-parents"].include?(requested_graphs) end - def columns_for_measure(measure:) - return unless measure.includes_parent_survey_items? + def show_scale_level_graphs?(measure:) + return false unless measure.includes_parent_survey_items? - measure.scales.parent_scales.map do |scale| - Analyze::Graph::Column::Parent::Scale.new(scale:) - end + ["parents-by-language"].include?(requested_graphs) end def sources @@ -154,23 +152,54 @@ module Analyze end end + def measure_level_graphs + @measure_level_graphs ||= { "all-data" => Analyze::Graph::AllData.new, + "students-and-teachers" => Analyze::Graph::StudentsAndTeachers.new, + "students-and-teachers-and-parents" => Analyze::Graph::StudentsAndTeachersAndParents.new, + "students-by-race" => Analyze::Graph::StudentsByRace.new(races: selected_races), + "students-by-grade" => Analyze::Graph::StudentsByGrade.new(grades: selected_grades), + "students-by-gender" => Analyze::Graph::StudentsByGender.new(genders: selected_genders), + "students-by-income" => Analyze::Graph::StudentsByIncome.new(incomes: selected_incomes), + "students-by-sped" => Analyze::Graph::StudentsBySped.new(speds: selected_speds), + "students-by-ell" => Analyze::Graph::StudentsByEll.new(ells: selected_ells), + "parents-by-language" => Analyze::Graph::ParentsByLanguage.new } + end + + def scale_level_graphs + @scale_level_graphs ||= { "all-data" => nil, + "students-and-teachers" => nil, + "students-and-teachers-and-parents" => nil, + "students-by-race" => nil, + "students-by-grade" => nil, + "students-by-gender" => nil, + "students-by-income" => nil, + "students-by-sped" => nil, + "students-by-ell" => nil, + "parents-by-language" => Analyze::Graph::ParentsByLanguage.new } + end + def graphs - @graphs ||= [Analyze::Graph::AllData.new, - Analyze::Graph::StudentsAndTeachers.new, - Analyze::Graph::StudentsAndTeachersAndParents.new, - Analyze::Graph::StudentsByRace.new(races: selected_races), - Analyze::Graph::StudentsByGrade.new(grades: selected_grades), - Analyze::Graph::StudentsByGender.new(genders: selected_genders), - Analyze::Graph::StudentsByIncome.new(incomes: selected_incomes), - Analyze::Graph::StudentsBySped.new(speds: selected_speds), - Analyze::Graph::StudentsByEll.new(ells: selected_ells), - Analyze::Graph::ParentsByLanguage.new] + @graphs ||= measure_level_graphs.values end def graph - @graph ||= graphs.find do |graph| - graph.slug == params[:graph] - end || graphs.first + measure_level_graph + end + + def requested_graphs + @requested_graphs ||= params[:graph] || "all-data" + end + + def secondary_graph + @secondary_graph ||= Analyze::Graph::AllParent.new + end + + def scale_level_graph + @scale_level_graph ||= scale_level_graphs[requested_graphs] || Analyze::Graph::ParentsByLanguageByScale.new + end + + def measure_level_graph + @measure_level_graph ||= measure_level_graphs[requested_graphs] || Analyze::Graph::AllData.new end def grades diff --git a/app/views/analyze/_grouped_bar_chart.html.erb b/app/views/analyze/_grouped_bar_chart.html.erb index ec5c4792..6447f332 100644 --- a/app/views/analyze/_grouped_bar_chart.html.erb +++ b/app/views/analyze/_grouped_bar_chart.html.erb @@ -3,7 +3,7 @@ <% number_of_columns = columns.length %> <% columns.each_with_index do |config, index| %> - <% p = Analyze::Graph::Column::GroupedBarColumnPresenter.new(measure: measure, school: @school, academic_years: @presenter.selected_academic_years, position: index , number_of_columns:, config: config) %> + <% p = Analyze::Graph::Column::GroupedBarColumnPresenter.new(construct:, school: @school, academic_years: @presenter.selected_academic_years, position: index , number_of_columns:, config: config) %> <%= render partial: "grouped_bar_column", locals: {column: p} %> <% end %> diff --git a/app/views/analyze/_grouped_bar_column.html.erb b/app/views/analyze/_grouped_bar_column.html.erb index f487eef0..8e75d7ee 100644 --- a/app/views/analyze/_grouped_bar_column.html.erb +++ b/app/views/analyze/_grouped_bar_column.html.erb @@ -1,4 +1,4 @@ - + <% score_label_y = [5, 10, 15, 5, 10, 15 ] %> <% column.bars.each_with_index do |bar, index| %> <% cache [@school, @presenter.cache_objects] do %>
- <% @presenter.measures.each do |measure| %> -
- <%= link_to("MEASURE " + measure.measure_id.upcase, district_school_category_path( @district, @school, @presenter.category, {year: @presenter.selected_academic_years&.last&.range , anchor: "#{measure.measure_id}"}), class: "construct-id", data: {turbo_frame: "_top"}) %> -

<%= measure.name %>

- <%= render partial: "grouped_bar_chart" , locals: { measure: measure, columns: @presenter.graph.columns} %> -
+
+ <%= link_to("MEASURE " + measure.measure_id.upcase, district_school_category_path( @district, @school, @presenter.category, {year: @presenter.selected_academic_years&.last&.range , anchor: "#{measure.measure_id}"}), class: "construct-id", data: {turbo_frame: "_top"}) %> +

<%= measure.name %>

+ <%= render partial: "grouped_bar_chart" , locals: { construct: measure, columns: @presenter.measure_level_graph.columns} %> +
+ + <% if @presenter.show_secondary_graph?(measure:) %> +
+ <%= link_to("Parent Survey", district_school_category_path( @district, @school, @presenter.category, {year: @presenter.selected_academic_years&.last&.range , anchor: "#{measure.scales.parent_scales.first.scale_id}"}), class: "construct-id", data: {turbo_frame: "_top"}) %> +

<%= measure.subcategory.name %>

+ <% measure.scales.parent_scales.each do |scale| %> + + <% end %> + <%= render partial: "grouped_bar_chart" , locals: { construct: measure, columns: @presenter.secondary_graph.columns(construct: measure)} %> +
+ <% end %> - <% if @presenter.show_secondary_graph?(measure:) %> -
- <%= link_to("Parent Survey", district_school_category_path( @district, @school, @presenter.category, {year: @presenter.selected_academic_years&.last&.range , anchor: "#{measure.scales.parent_scales.first.scale_id}"}), class: "construct-id", data: {turbo_frame: "_top"}) %> -

<%= measure.subcategory.name %>

- <% measure.scales.parent_scales.each do |scale| %> - + <% if @presenter.show_scale_level_graphs?(measure:) %> + <% measure.scales.parent_scales.each do |scale| %> +
+ <%= link_to("scale " + scale.scale_id.upcase, district_school_category_path( @district, @school, @presenter.category, {year: @presenter.selected_academic_years&.last&.range , anchor: "#{scale.scale_id}"}), class: "construct-id", data: {turbo_frame: "_top"}) %> +

<%= scale.name %>

+ <%= render partial: "grouped_bar_chart" , locals: { construct: scale, columns: @presenter.scale_level_graph.columns} %> +
<% end %> - <%= render partial: "grouped_bar_chart" , locals: { measure: measure, columns: @presenter.columns_for_measure(measure:)} %> -
- <% end %> + <% end %> <% end %>
diff --git a/spec/presenters/analyze/presenter_spec.rb b/spec/presenters/analyze/presenter_spec.rb index 675dfb98..f315805a 100644 --- a/spec/presenters/analyze/presenter_spec.rb +++ b/spec/presenters/analyze/presenter_spec.rb @@ -15,7 +15,9 @@ describe Analyze::Presenter do create(:wrong_measure, measure_id: "99A", name: "wrong measure", subcategory: wrong_subcategory) end let(:scale) { create(:student_scale, measure:) } + let(:parent_scale) { create(:parent_scale, measure:) } let(:survey_item) { create(:student_survey_item, scale:) } + let(:parent_survey_item) { create(:parent_survey_item, scale: parent_scale) } let(:school) { create(:school) } let(:academic_year) { create(:academic_year) } let(:ay_2021_22) { create(:academic_year, range: "2021-22") } @@ -426,6 +428,24 @@ describe Analyze::Presenter do end end + context "when the graph is 'parents-by-language'" do + before :each do + parent_survey_item + parent_scale + measure + end + it "returns the slice with the given slug" do + params = { graph: "parents-by-language" } + presenter = Analyze::Presenter.new(params:, school:, academic_year:) + expect(presenter.slice.slug).to eq "parents-by-group" + expect(presenter.requested_graphs).to eq "parents-by-language" + expect(presenter.show_secondary_graph?(measure:)).to eq false + expect(presenter.show_scale_level_graphs?(measure:)).to eq true + expect(presenter.secondary_graph.class.to_s).to eq "Analyze::Graph::AllParent" + expect(presenter.secondary_graph.slug).to eq "all-parent" + end + end + context "when the graph is of a disaggregation group" do it "returns the slice with the given slug" do params = { graph: "students-by-ell" } diff --git a/spec/presenters/grouped_bar_column_presenter_spec.rb b/spec/presenters/grouped_bar_column_presenter_spec.rb index 6b8818a8..258f4f8b 100644 --- a/spec/presenters/grouped_bar_column_presenter_spec.rb +++ b/spec/presenters/grouped_bar_column_presenter_spec.rb @@ -63,17 +63,17 @@ describe GroupedBarColumnPresenter do end let(:student_presenter) do - GroupedBarColumnPresenter.new measure: measure_with_student_survey_items, school:, academic_years:, + GroupedBarColumnPresenter.new construct: measure_with_student_survey_items, school:, academic_years:, position: 0, number_of_columns: 3, config: Analyze::Graph::Column::AllStudent.new end let(:teacher_presenter) do - GroupedBarColumnPresenter.new measure: measure_with_teacher_survey_items, school:, academic_years:, + GroupedBarColumnPresenter.new construct: measure_with_teacher_survey_items, school:, academic_years:, position: 0, number_of_columns: 3, config: Analyze::Graph::Column::AllTeacher.new end let(:all_data_presenter) do - GroupedBarColumnPresenter.new measure: measure_composed_of_student_and_teacher_items, school:, academic_years:, + GroupedBarColumnPresenter.new construct: measure_composed_of_student_and_teacher_items, school:, academic_years:, position: 0, number_of_columns: 3, config: Analyze::Graph::Column::AllData.new end @@ -83,7 +83,7 @@ describe GroupedBarColumnPresenter do shared_examples_for "measure_name" do it "returns the measure name" do - expect(student_presenter.measure_name).to eq "Student measure" + expect(student_presenter.construct_name).to eq "Student measure" end end