parent
e1f0b78236
commit
a4fddbeced
@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class HomeController < ApplicationController
|
||||
helper HeaderHelper
|
||||
|
||||
def index
|
||||
@districts = districts
|
||||
@district = district
|
||||
|
||||
@schools = schools
|
||||
@school = school
|
||||
|
||||
@year = year
|
||||
@categories = Category.sorted.map { |category| CategoryPresenter.new(category:) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def districts
|
||||
District.all.order(:name).map do |district|
|
||||
[district.name, district.id]
|
||||
end
|
||||
end
|
||||
|
||||
def district
|
||||
return District.find(params[:district]) if params[:district].present?
|
||||
|
||||
District.first
|
||||
end
|
||||
|
||||
def schools
|
||||
if district.present?
|
||||
district.dashboard_schools.order(:name).map do |school|
|
||||
[school.name, school.id]
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def school
|
||||
School.find(params[:school]) if params[:school].present?
|
||||
end
|
||||
|
||||
def year
|
||||
return nil unless school.present?
|
||||
|
||||
academic_year = AcademicYear.all.order(range: :DESC).find do |ay|
|
||||
Subcategory.all.any? do |subcategory|
|
||||
rate = subcategory.response_rate(school:, academic_year: ay)
|
||||
rate.meets_student_threshold || rate.meets_teacher_threshold
|
||||
end
|
||||
end
|
||||
|
||||
academic_year&.range || AcademicYear.order("range DESC").first.range
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
module HeaderHelper
|
||||
def link_to_overview(district:, school:, academic_year:)
|
||||
"/districts/#{district.slug}/schools/#{school.slug}/overview?year=#{academic_year.range}"
|
||||
end
|
||||
|
||||
def link_to_browse(district:, school:, academic_year:)
|
||||
"/districts/#{district.slug}/schools/#{school.slug}/browse/teachers-and-leadership?year=#{academic_year.range}"
|
||||
end
|
||||
|
||||
def link_to_analyze(district:, school:, academic_year:)
|
||||
year = academic_year.range
|
||||
"/districts/#{district.slug}/schools/#{school.slug}/analyze?year=#{year}&category=1&academic_years=#{year}"
|
||||
end
|
||||
|
||||
def district_url_for(district:, academic_year:)
|
||||
pages = %w[overview browse analyze]
|
||||
pages.each do |page|
|
||||
if request.fullpath.include? page
|
||||
return send("#{page}_link", district_slug: district.slug, school_slug: district.schools.alphabetic.first.slug,
|
||||
academic_year_range: academic_year.range)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def school_url_for(school:, academic_year:)
|
||||
pages = %w[overview browse analyze]
|
||||
pages.each do |page|
|
||||
if request.fullpath.include? page
|
||||
return send("#{page}_link", district_slug: school.district.slug, school_slug: school.slug,
|
||||
academic_year_range: academic_year.range)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def link_weight(path:)
|
||||
active?(path:) ? "weight-700" : "weight-400"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def overview_link(district_slug:, school_slug:, academic_year_range:)
|
||||
"/districts/#{district_slug}/schools/#{school_slug}/overview?year=#{academic_year_range}"
|
||||
end
|
||||
|
||||
def analyze_link(district_slug:, school_slug:, academic_year_range:)
|
||||
"/districts/#{district_slug}/schools/#{school_slug}/analyze?year=#{academic_year_range}&academic_years=#{academic_year_range}"
|
||||
end
|
||||
|
||||
def browse_link(district_slug:, school_slug:, academic_year_range:)
|
||||
"/districts/#{district_slug}/schools/#{school_slug}/browse/teachers-and-leadership?year=#{academic_year_range}"
|
||||
end
|
||||
|
||||
def active?(path:)
|
||||
request.fullpath.include? path
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,14 @@
|
||||
module Dashboard
|
||||
class AdminDataItem < ApplicationRecord
|
||||
belongs_to :dashboard_scale
|
||||
has_many :dashboard_admin_data_values
|
||||
|
||||
scope :for_measures, lambda { |measures|
|
||||
joins(:scale).where('scale.measure': measures)
|
||||
}
|
||||
|
||||
scope :non_hs_items_for_measures, lambda { |measure|
|
||||
for_measures(measure).where(hs_only_item: false)
|
||||
}
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,9 @@
|
||||
module Dashboard
|
||||
class AdminDataValue < ApplicationRecord
|
||||
belongs_to :dashboard_school
|
||||
belongs_to :dashboard_admin_data_item
|
||||
belongs_to :dashboard_academic_year
|
||||
|
||||
validates :likert_score, numericality: { greater_than: 0, less_than_or_equal_to: 5 }
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,13 @@
|
||||
module Dashboard
|
||||
class Category < ApplicationRecord
|
||||
include FriendlyId
|
||||
friendly_id :name, use: [:slugged]
|
||||
|
||||
scope :sorted, -> { order(:category_id) }
|
||||
|
||||
has_many :subcategories
|
||||
has_many :measures, through: :subcategories
|
||||
has_many :admin_data_items, through: :measures
|
||||
has_many :scales, through: :subcategories
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,18 @@
|
||||
module Dashboard
|
||||
class District < ApplicationRecord
|
||||
has_many :schools, class_name: "Dashboard::School"
|
||||
has_many :dashboard_schools, class_name: "Dashboard::School"
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
scope :alphabetic, -> { order(name: :asc) }
|
||||
|
||||
include FriendlyId
|
||||
|
||||
friendly_id :name, use: [:slugged]
|
||||
|
||||
def short_name
|
||||
name.split(" ").first.downcase
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,20 @@
|
||||
module Dashboard
|
||||
class Ell < ApplicationRecord
|
||||
scope :by_designation, -> { all.map { |ell| [ell.designation, ell] }.to_h }
|
||||
|
||||
include FriendlyId
|
||||
|
||||
friendly_id :designation, use: [:slugged]
|
||||
|
||||
def self.to_designation(ell)
|
||||
case ell
|
||||
in /lep student 1st year|LEP student not 1st year|EL Student First Year|LEP\s*student/i
|
||||
"ELL"
|
||||
in /Does not apply/i
|
||||
"Not ELL"
|
||||
else
|
||||
"Unknown"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,26 @@
|
||||
module Dashboard
|
||||
class Gender < ApplicationRecord
|
||||
scope :by_qualtrics_code, lambda {
|
||||
all.map { |gender| [gender.qualtrics_code, gender] }.to_h
|
||||
}
|
||||
|
||||
def self.qualtrics_code_from(word)
|
||||
case word
|
||||
when /Female|^F|1/i
|
||||
1
|
||||
when /Male|^M|2/i
|
||||
2
|
||||
when /Another\s*Gender|Gender Identity not listed above|3|7/i
|
||||
4 # We categorize any self reported gender as non-binary
|
||||
when /Non-Binary|^N|4/i
|
||||
4
|
||||
when /Prefer not to disclose|6/i
|
||||
99
|
||||
when %r{^#*N/*A$}i
|
||||
nil
|
||||
else
|
||||
99
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,31 @@
|
||||
module Dashboard
|
||||
class Income < ApplicationRecord
|
||||
scope :by_designation, -> { all.map { |income| [income.designation, income] }.to_h }
|
||||
scope :by_slug, -> { all.map { |income| [income.slug, income] }.to_h }
|
||||
|
||||
include FriendlyId
|
||||
|
||||
friendly_id :designation, use: [:slugged]
|
||||
|
||||
def self.to_designation(income)
|
||||
case income
|
||||
in /Free\s*Lunch|Reduced\s*Lunch|Low\s*Income|Reduced\s*price\s*lunch/i
|
||||
"Economically Disadvantaged - Y"
|
||||
in /Not\s*Eligible/i
|
||||
"Economically Disadvantaged - N"
|
||||
else
|
||||
"Unknown"
|
||||
end
|
||||
end
|
||||
|
||||
LABELS = {
|
||||
"Economically Disadvantaged - Y" => "Economically Disadvantaged",
|
||||
"Economically Disadvantaged - N" => "Not Economically Disadvantaged",
|
||||
"Unknown" => "Unknown"
|
||||
}
|
||||
|
||||
def label
|
||||
LABELS[designation]
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,227 @@
|
||||
module Dashboard
|
||||
class Measure < ApplicationRecord
|
||||
belongs_to :dashboard_subcategory
|
||||
has_one :dashboard_category, through: :dashboard_subcategory
|
||||
has_many :dashboard_scales
|
||||
has_many :dashboard_admin_data_items, through: :scales
|
||||
has_many :dashboard_survey_items, through: :scales
|
||||
has_many :dashboard_survey_item_responses, through: :survey_items
|
||||
|
||||
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:)
|
||||
end
|
||||
|
||||
@none_meet_threshold[[school, academic_year]]
|
||||
end
|
||||
|
||||
def teacher_survey_items
|
||||
@teacher_survey_items ||= survey_items.teacher_survey_items
|
||||
end
|
||||
|
||||
def student_survey_items
|
||||
@student_survey_items ||= survey_items.student_survey_items
|
||||
end
|
||||
|
||||
def student_survey_items_with_sufficient_responses(school:, academic_year:)
|
||||
@student_survey_items_with_sufficient_responses ||= SurveyItem.where(id: SurveyItem.joins("inner join survey_item_responses on survey_item_responses.survey_item_id = survey_items.id")
|
||||
.student_survey_items
|
||||
.where("survey_item_responses.school": school,
|
||||
"survey_item_responses.academic_year": academic_year,
|
||||
"survey_item_responses.survey_item_id": survey_items.student_survey_items,
|
||||
"survey_item_responses.grade": school.grades(academic_year:))
|
||||
.group("survey_items.id")
|
||||
.having("count(*) >= 10")
|
||||
.count.keys)
|
||||
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?
|
||||
end
|
||||
|
||||
def includes_student_survey_items?
|
||||
@includes_student_survey_items ||= student_survey_items.any?
|
||||
end
|
||||
|
||||
def includes_admin_data_items?
|
||||
@includes_admin_data_items ||= admin_data_items.any?
|
||||
end
|
||||
|
||||
def score(school:, academic_year:)
|
||||
@score ||= Hash.new do |memo, (school, academic_year)|
|
||||
next Score::NIL_SCORE if incalculable_score(school:, academic_year:)
|
||||
|
||||
scores = collect_averages_for_teacher_student_and_admin_data(school:, academic_year:)
|
||||
average = scores.flatten.compact.remove_blanks.average.round(2)
|
||||
|
||||
next Score::NIL_SCORE if average.nan?
|
||||
|
||||
memo[[school, academic_year]] = scorify(average:, school:, academic_year:)
|
||||
end
|
||||
@score[[school, academic_year]]
|
||||
end
|
||||
|
||||
def student_score(school:, academic_year:)
|
||||
@student_score ||= Hash.new do |memo, (school, academic_year)|
|
||||
meets_student_threshold = sufficient_student_data?(school:, academic_year:)
|
||||
average = student_average(school:, academic_year:).round(2) if meets_student_threshold
|
||||
memo[[school, academic_year]] = scorify(average:, school:, academic_year:)
|
||||
end
|
||||
|
||||
@student_score[[school, academic_year]]
|
||||
end
|
||||
|
||||
def teacher_score(school:, academic_year:)
|
||||
@teacher_score ||= Hash.new do |memo, (school, academic_year)|
|
||||
meets_teacher_threshold = sufficient_teacher_data?(school:, academic_year:)
|
||||
average = teacher_average(school:, academic_year:).round(2) if meets_teacher_threshold
|
||||
memo[[school, academic_year]] = scorify(average:, school:, academic_year:)
|
||||
end
|
||||
|
||||
@teacher_score[[school, academic_year]]
|
||||
end
|
||||
|
||||
def admin_score(school:, academic_year:)
|
||||
@admin_score ||= Hash.new do |memo, (school, academic_year)|
|
||||
meets_admin_threshold = sufficient_admin_data?(school:, academic_year:)
|
||||
average = admin_data_averages(school:, academic_year:).average.round(2) if meets_admin_threshold
|
||||
memo[[school, academic_year]] = scorify(average:, school:, academic_year:)
|
||||
end
|
||||
|
||||
@admin_score[[school, academic_year]]
|
||||
end
|
||||
|
||||
def warning_low_benchmark
|
||||
1
|
||||
end
|
||||
|
||||
def watch_low_benchmark
|
||||
@watch_low_benchmark ||= benchmark(:watch_low_benchmark)
|
||||
end
|
||||
|
||||
def growth_low_benchmark
|
||||
@growth_low_benchmark ||= benchmark(:growth_low_benchmark)
|
||||
end
|
||||
|
||||
def approval_low_benchmark
|
||||
@approval_low_benchmark ||= benchmark(:approval_low_benchmark)
|
||||
end
|
||||
|
||||
def ideal_low_benchmark
|
||||
@ideal_low_benchmark ||= benchmark(:ideal_low_benchmark)
|
||||
end
|
||||
|
||||
def sufficient_admin_data?(school:, academic_year:)
|
||||
any_admin_data_collected?(school:, academic_year:)
|
||||
end
|
||||
|
||||
def benchmark(name)
|
||||
averages = []
|
||||
averages << student_survey_items.first.send(name) if includes_student_survey_items?
|
||||
averages << teacher_survey_items.first.send(name) if includes_teacher_survey_items?
|
||||
(averages << admin_data_items.map(&name)).flatten! if includes_admin_data_items?
|
||||
averages.average
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def any_admin_data_collected?(school:, academic_year:)
|
||||
@any_admin_data_collected ||= Hash.new do |memo, (school, academic_year)|
|
||||
total_collected_admin_data_items =
|
||||
admin_data_items.map do |admin_data_item|
|
||||
admin_data_item.admin_data_values.where(school:, academic_year:).count
|
||||
end.flatten.sum
|
||||
memo[[school, academic_year]] = total_collected_admin_data_items.positive?
|
||||
end
|
||||
@any_admin_data_collected[[school, academic_year]]
|
||||
end
|
||||
|
||||
def sufficient_survey_responses?(school:, academic_year:)
|
||||
@sufficient_survey_responses ||= Hash.new do |memo, (school, academic_year)|
|
||||
memo[[school, academic_year]] =
|
||||
sufficient_student_data?(school:, academic_year:) || sufficient_teacher_data?(school:, academic_year:)
|
||||
end
|
||||
@sufficient_survey_responses[[school, academic_year]]
|
||||
end
|
||||
|
||||
def scorify(average:, school:, academic_year:)
|
||||
meets_student_threshold = sufficient_student_data?(school:, academic_year:)
|
||||
meets_teacher_threshold = sufficient_teacher_data?(school:, academic_year:)
|
||||
meets_admin_data_threshold = any_admin_data_collected?(school:, academic_year:)
|
||||
Score.new(average:, meets_teacher_threshold:, meets_student_threshold:, meets_admin_data_threshold:)
|
||||
end
|
||||
|
||||
def collect_survey_item_average(survey_items:, school:, academic_year:)
|
||||
@collect_survey_item_average ||= Hash.new do |memo, (survey_items, school, academic_year)|
|
||||
averages = survey_items.map do |survey_item|
|
||||
SurveyItemResponse.grouped_responses(school:, academic_year:)[survey_item.id]
|
||||
end.remove_blanks
|
||||
memo[[survey_items, school, academic_year]] = averages.average || 0
|
||||
end
|
||||
@collect_survey_item_average[[survey_items, school, academic_year]]
|
||||
end
|
||||
|
||||
def sufficient_student_data?(school:, academic_year:)
|
||||
return false unless includes_student_survey_items?
|
||||
|
||||
subcategory.response_rate(school:, academic_year:).meets_student_threshold?
|
||||
end
|
||||
|
||||
def sufficient_teacher_data?(school:, academic_year:)
|
||||
return false unless includes_teacher_survey_items?
|
||||
|
||||
subcategory.response_rate(school:, academic_year:).meets_teacher_threshold?
|
||||
end
|
||||
|
||||
def incalculable_score(school:, academic_year:)
|
||||
@incalculable_score ||= Hash.new do |memo, (school, academic_year)|
|
||||
lacks_sufficient_survey_data = !sufficient_student_data?(school:, academic_year:) &&
|
||||
!sufficient_teacher_data?(school:, academic_year:)
|
||||
memo[[school, academic_year]] = lacks_sufficient_survey_data && !includes_admin_data_items?
|
||||
end
|
||||
|
||||
@incalculable_score[[school, academic_year]]
|
||||
end
|
||||
|
||||
def collect_averages_for_teacher_student_and_admin_data(school:, academic_year:)
|
||||
scores = []
|
||||
scores << teacher_average(school:, academic_year:) if sufficient_teacher_data?(school:, academic_year:)
|
||||
scores << student_average(school:, academic_year:) if sufficient_student_data?(school:, academic_year:)
|
||||
scores << admin_data_averages(school:, academic_year:) if includes_admin_data_items?
|
||||
scores
|
||||
end
|
||||
|
||||
def teacher_average(school:, academic_year:)
|
||||
@teacher_average ||= Hash.new do |memo, (school, academic_year)|
|
||||
memo[[school, academic_year]] =
|
||||
collect_survey_item_average(survey_items: teacher_survey_items, school:, academic_year:)
|
||||
end
|
||||
|
||||
@teacher_average[[school, academic_year]]
|
||||
end
|
||||
|
||||
def student_average(school:, academic_year:)
|
||||
@student_average ||= Hash.new do |memo, (school, academic_year)|
|
||||
survey_items = student_survey_items_with_sufficient_responses(school:, academic_year:)
|
||||
memo[[school, academic_year]] = collect_survey_item_average(survey_items:, school:, academic_year:)
|
||||
end
|
||||
@student_average[[school, academic_year]]
|
||||
end
|
||||
|
||||
def admin_data_averages(school:, academic_year:)
|
||||
@admin_data_averages ||= Hash.new do |memo, (school, academic_year)|
|
||||
memo[[school, academic_year]] =
|
||||
AdminDataValue.where(school:, academic_year:, admin_data_item: admin_data_items).pluck(:likert_score)
|
||||
end
|
||||
@admin_data_averages[[school, academic_year]]
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,51 @@
|
||||
module Dashboard
|
||||
class Race < ApplicationRecord
|
||||
include FriendlyId
|
||||
has_many :dashboard_student_races
|
||||
has_many :dashboard_students, through: :student_races
|
||||
|
||||
friendly_id :designation, use: [:slugged]
|
||||
|
||||
scope :by_qualtrics_code, lambda {
|
||||
all.map { |race| [race.qualtrics_code, race] }.to_h
|
||||
}
|
||||
|
||||
def self.qualtrics_code_from(word)
|
||||
case word
|
||||
when /Native\s*American|American\s*Indian|Alaskan\s*Native|1/i
|
||||
1
|
||||
when /\bAsian|Pacific\s*Island|Hawaiian|2/i
|
||||
2
|
||||
when /Black|African\s*American|3/i
|
||||
3
|
||||
when /Hispanic|Latinx|4/i
|
||||
4
|
||||
when /White|Caucasian|5/i
|
||||
5
|
||||
when /Prefer not to disclose|6/i
|
||||
6
|
||||
when /Prefer to self-describe|7/i
|
||||
7
|
||||
when /Middle\s*Eastern|North\s*African|8/i
|
||||
8
|
||||
when %r{^#*N/*A$}i
|
||||
nil
|
||||
else
|
||||
99
|
||||
end
|
||||
end
|
||||
|
||||
def self.normalize_race_list(codes)
|
||||
# if anyone selects not to disclose their race or prefers to self-describe, categorize that as unknown race
|
||||
races = codes.map do |code|
|
||||
code = 99 if [6, 7].include?(code) || code.nil? || code.zero?
|
||||
code
|
||||
end.uniq
|
||||
|
||||
races.delete(99) if races.length > 1 # remove unkown race if other races present
|
||||
races << 100 if races.length > 1 # add multiracial designation if multiple races present
|
||||
races << 99 if races.length == 0 # add unknown race if other races missing
|
||||
races
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,27 @@
|
||||
module Dashboard
|
||||
class Respondent < ApplicationRecord
|
||||
belongs_to :dashboard_school
|
||||
belongs_to :dashboard_academic_year
|
||||
|
||||
validates :school, uniqueness: { scope: :academic_year }
|
||||
|
||||
def enrollment_by_grade
|
||||
@enrollment_by_grade ||= {}.tap do |row|
|
||||
attributes = %i[pk k one two three four five six seven eight nine ten eleven twelve]
|
||||
grades = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
attributes.zip(grades).each do |attribute, grade|
|
||||
count = send(attribute) if send(attribute).present?
|
||||
row[grade] = count unless count.nil? || count.zero?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.by_school_and_year(school:, academic_year:)
|
||||
@by_school_and_year ||= Hash.new do |memo, (school, academic_year)|
|
||||
memo[[school, academic_year]] = Respondent.find_by(school:, academic_year:)
|
||||
end
|
||||
|
||||
@by_school_and_year[[school, academic_year]]
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,7 @@
|
||||
module Dashboard
|
||||
class ResponseRate < ApplicationRecord
|
||||
belongs_to :dashboard_subcategory
|
||||
belongs_to :dashboard_school
|
||||
belongs_to :dashboard_academic_year
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class ResponseRateCalculator
|
||||
TEACHER_RATE_THRESHOLD = 25
|
||||
STUDENT_RATE_THRESHOLD = 25
|
||||
attr_reader :subcategory, :school, :academic_year
|
||||
|
||||
def initialize(subcategory:, school:, academic_year:)
|
||||
@subcategory = subcategory
|
||||
@school = school
|
||||
@academic_year = academic_year
|
||||
end
|
||||
|
||||
def rate
|
||||
return 100 if population_data_unavailable?
|
||||
|
||||
return 0 unless survey_items_have_sufficient_responses?
|
||||
|
||||
return 0 unless total_possible_responses.positive?
|
||||
|
||||
cap_at_one_hundred(raw_response_rate).round
|
||||
end
|
||||
|
||||
def meets_student_threshold?
|
||||
rate >= STUDENT_RATE_THRESHOLD
|
||||
end
|
||||
|
||||
def meets_teacher_threshold?
|
||||
rate >= TEACHER_RATE_THRESHOLD
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cap_at_one_hundred(response_rate)
|
||||
response_rate > 100 ? 100 : response_rate
|
||||
end
|
||||
|
||||
def average_responses_per_survey_item
|
||||
response_count / survey_item_count.to_f
|
||||
end
|
||||
|
||||
def respondents
|
||||
@respondents ||= Respondent.by_school_and_year(school:, academic_year:)
|
||||
end
|
||||
|
||||
def population_data_unavailable?
|
||||
@population_data_unavailable ||= respondents.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,44 @@
|
||||
module Dashboard
|
||||
class Scale < ApplicationRecord
|
||||
belongs_to :dashboard_measure
|
||||
has_many :dashboard_survey_items
|
||||
has_many :dashboard_survey_item_responses, through: :dashboard_survey_items
|
||||
has_many :dashboard_admin_data_items
|
||||
|
||||
def score(school:, academic_year:)
|
||||
@score ||= Hash.new do |memo, (school, academic_year)|
|
||||
memo[[school, academic_year]] = begin
|
||||
items = []
|
||||
items << collect_survey_item_average(student_survey_items, school, academic_year)
|
||||
items << collect_survey_item_average(teacher_survey_items, school, academic_year)
|
||||
|
||||
items.remove_blanks.average
|
||||
end
|
||||
end
|
||||
@score[[school, academic_year]]
|
||||
end
|
||||
|
||||
scope :teacher_scales, lambda {
|
||||
where("scale_id LIKE 't-%'")
|
||||
}
|
||||
scope :student_scales, lambda {
|
||||
where("scale_id LIKE 's-%'")
|
||||
}
|
||||
|
||||
private
|
||||
|
||||
def collect_survey_item_average(survey_items, school, academic_year)
|
||||
survey_items.map do |survey_item|
|
||||
survey_item.score(school:, academic_year:)
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def teacher_survey_items
|
||||
survey_items.teacher_survey_items
|
||||
end
|
||||
|
||||
def student_survey_items
|
||||
survey_items.student_survey_items
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,27 @@
|
||||
module Dashboard
|
||||
class School < ApplicationRecord
|
||||
belongs_to :district, class_name: "Dashboard::District"
|
||||
|
||||
has_many :dashboard_survey_item_responses, dependent: :delete_all
|
||||
|
||||
# validates :name, presence: true
|
||||
|
||||
scope :alphabetic, -> { order(name: :asc) }
|
||||
scope :school_hash, -> { all.map { |school| [school.dese_id, school] }.to_h }
|
||||
|
||||
include FriendlyId
|
||||
friendly_id :name, use: [:slugged]
|
||||
|
||||
def self.find_by_district_code_and_school_code(district_code, school_code)
|
||||
School
|
||||
.joins(:district)
|
||||
.where(districts: { qualtrics_code: district_code })
|
||||
.find_by_qualtrics_code(school_code)
|
||||
end
|
||||
|
||||
def grades(academic_year:)
|
||||
@grades ||= Respondent.by_school_and_year(school: self,
|
||||
academic_year:)&.enrollment_by_grade&.keys || (-1..12).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,28 @@
|
||||
module Dashboard
|
||||
class Score < ApplicationRecord
|
||||
belongs_to :dashboard_measure
|
||||
belongs_to :dashboard_school
|
||||
belongs_to :dashboard_academic_year
|
||||
belongs_to :dashboard_race
|
||||
|
||||
NIL_SCORE = Score.new(average: nil, meets_teacher_threshold: false, meets_student_threshold: false,
|
||||
meets_admin_data_threshold: false)
|
||||
|
||||
enum group: {
|
||||
all_students: 0,
|
||||
race: 1,
|
||||
grade: 2,
|
||||
gender: 3
|
||||
}
|
||||
|
||||
def in_zone?(zone:)
|
||||
return false if average.nil? || average.is_a?(Float) && average.nan?
|
||||
|
||||
average.between?(zone.low_benchmark, zone.high_benchmark)
|
||||
end
|
||||
|
||||
def blank?
|
||||
average.nil? || average.zero? || average.nan?
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,20 @@
|
||||
module Dashboard
|
||||
class Sped < ApplicationRecord
|
||||
scope :by_designation, -> { all.map { |sped| [sped.designation, sped] }.to_h }
|
||||
|
||||
include FriendlyId
|
||||
|
||||
friendly_id :designation, use: [:slugged]
|
||||
|
||||
def self.to_designation(sped)
|
||||
case sped
|
||||
in /active/i
|
||||
"Special Education"
|
||||
in /^NA$|^#NA$/i
|
||||
"Unknown"
|
||||
else
|
||||
"Not Special Education"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,9 @@
|
||||
module Dashboard
|
||||
class Student < ApplicationRecord
|
||||
# has_many :dashboard_survey_item_responses
|
||||
has_many :dashboard_student_races
|
||||
has_and_belongs_to_many :races, join_table: :student_races
|
||||
|
||||
encrypts :lasid, deterministic: true
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,6 @@
|
||||
module Dashboard
|
||||
class StudentRace < ApplicationRecord
|
||||
belongs_to :dashboard_student
|
||||
belongs_to :dashboard_race
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class StudentResponseRateCalculator < ResponseRateCalculator
|
||||
def raw_response_rate
|
||||
rates_by_grade.values.length.positive? ? weighted_average : 0
|
||||
end
|
||||
|
||||
def weighted_average
|
||||
num_possible_responses = 0.0
|
||||
rates_by_grade.keys.map do |grade|
|
||||
num_possible_responses += enrollment_by_grade[grade]
|
||||
end
|
||||
rates_by_grade.map do |grade, rate|
|
||||
rate * (enrollment_by_grade[grade] / num_possible_responses)
|
||||
end.sum
|
||||
end
|
||||
|
||||
def rates_by_grade
|
||||
@rates_by_grade ||= enrollment_by_grade.map do |grade, num_of_students_in_grade|
|
||||
responses = survey_items_with_sufficient_responses(grade:)
|
||||
actual_response_count_for_grade = responses.values.sum.to_f
|
||||
count_of_survey_items_with_sufficient_responses = responses.count
|
||||
if actual_response_count_for_grade.nil? || count_of_survey_items_with_sufficient_responses.nil? || count_of_survey_items_with_sufficient_responses.zero? || num_of_students_in_grade.nil? || num_of_students_in_grade.zero?
|
||||
next nil
|
||||
end
|
||||
|
||||
rate = actual_response_count_for_grade / count_of_survey_items_with_sufficient_responses / num_of_students_in_grade * 100
|
||||
[grade, rate]
|
||||
end.compact.to_h
|
||||
end
|
||||
|
||||
def enrollment_by_grade
|
||||
@enrollment_by_grade ||= respondents.enrollment_by_grade
|
||||
end
|
||||
|
||||
def survey_items_have_sufficient_responses?
|
||||
rates_by_grade.values.length.positive?
|
||||
end
|
||||
|
||||
def survey_items_with_sufficient_responses(grade:)
|
||||
@survey_items_with_sufficient_responses ||= Hash.new do |memo, grade|
|
||||
threshold = 10
|
||||
quarter_of_grade = enrollment_by_grade[grade] / 4
|
||||
threshold = threshold > quarter_of_grade ? quarter_of_grade : threshold
|
||||
|
||||
si = SurveyItemResponse.student_survey_items_with_sufficient_responses_by_grade(school:,
|
||||
academic_year:)
|
||||
si = si.reject do |_key, value|
|
||||
value < threshold
|
||||
end
|
||||
|
||||
ssi = @subcategory.student_survey_items.map(&:id)
|
||||
grade_array = Array.new(ssi.length, grade)
|
||||
|
||||
memo[grade] = si.slice(*grade_array.zip(ssi))
|
||||
end
|
||||
@survey_items_with_sufficient_responses[grade]
|
||||
end
|
||||
|
||||
def total_possible_responses
|
||||
@total_possible_responses ||= begin
|
||||
return 0 unless respondents.present?
|
||||
|
||||
respondents.total_students
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,102 @@
|
||||
module Dashboard
|
||||
class Subcategory < ApplicationRecord
|
||||
belongs_to :dashboard_categories, class_name: "Dashboard::Category"
|
||||
|
||||
has_many :dashboard_measures
|
||||
has_many :dashboard_survey_items, through: :dashboard_measures
|
||||
has_many :dashboard_admin_data_items, through: :dashboard_measures
|
||||
has_many :dashboard_survey_items, through: :dashboard_measures
|
||||
has_many :dashboard_scales, through: :dashboard_measures
|
||||
|
||||
def score(school:, academic_year:)
|
||||
measures.map do |measure|
|
||||
measure.score(school:, academic_year:).average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def student_score(school:, academic_year:)
|
||||
measures.map do |measure|
|
||||
measure.student_score(school:, academic_year:).average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def teacher_score(school:, academic_year:)
|
||||
measures.map do |measure|
|
||||
measure.teacher_score(school:, academic_year:).average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def admin_score(school:, academic_year:)
|
||||
measures.map do |measure|
|
||||
measure.admin_score(school:, academic_year:).average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def response_rate(school:, academic_year:)
|
||||
@response_rate ||= Hash.new do |memo, (school, academic_year)|
|
||||
student = StudentResponseRateCalculator.new(subcategory: self, school:, academic_year:)
|
||||
teacher = TeacherResponseRateCalculator.new(subcategory: self, school:, academic_year:)
|
||||
memo[[school, academic_year]] = ResponseRate.new(school:, academic_year:, subcategory: self, student_response_rate: student.rate,
|
||||
teacher_response_rate: teacher.rate, meets_student_threshold: student.meets_student_threshold?,
|
||||
meets_teacher_threshold: teacher.meets_teacher_threshold?)
|
||||
end
|
||||
|
||||
@response_rate[[school, academic_year]]
|
||||
end
|
||||
|
||||
def warning_low_benchmark
|
||||
1
|
||||
end
|
||||
|
||||
def watch_low_benchmark
|
||||
@watch_low_benchmark ||= benchmark(:watch_low_benchmark)
|
||||
end
|
||||
|
||||
def growth_low_benchmark
|
||||
@growth_low_benchmark ||= benchmark(:growth_low_benchmark)
|
||||
end
|
||||
|
||||
def approval_low_benchmark
|
||||
@approval_low_benchmark ||= benchmark(:approval_low_benchmark)
|
||||
end
|
||||
|
||||
def ideal_low_benchmark
|
||||
@ideal_low_benchmark ||= benchmark(:ideal_low_benchmark)
|
||||
end
|
||||
|
||||
def benchmark(name)
|
||||
measures.map do |measure|
|
||||
measure.benchmark(name)
|
||||
end.average
|
||||
end
|
||||
|
||||
def zone(school:, academic_year:)
|
||||
zone_for_score(score: score(school:, academic_year:))
|
||||
end
|
||||
|
||||
def student_zone(school:, academic_year:)
|
||||
zone_for_score(score: student_score(school:, academic_year:))
|
||||
end
|
||||
|
||||
def teacher_zone(school:, academic_year:)
|
||||
zone_for_score(score: teacher_score(school:, academic_year:))
|
||||
end
|
||||
|
||||
def admin_zone(school:, academic_year:)
|
||||
zone_for_score(score: admin_score(school:, academic_year:))
|
||||
end
|
||||
|
||||
def zone_for_score(score:)
|
||||
Zones.new(watch_low_benchmark:, growth_low_benchmark:,
|
||||
approval_low_benchmark:, ideal_low_benchmark:).zone_for_score(score)
|
||||
end
|
||||
|
||||
def student_survey_items
|
||||
@student_survey_items ||= survey_items.student_survey_items
|
||||
end
|
||||
|
||||
def teacher_survey_items
|
||||
@teacher_survey_items ||= survey_items.teacher_survey_items
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class Summary < Struct.new(:id, :description, :available?)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,78 @@
|
||||
module Dashboard
|
||||
class SurveyItem < ApplicationRecord
|
||||
belongs_to :dashboard_scale
|
||||
|
||||
has_one :dashboard_measure, through: dashboard_scale
|
||||
has_one :dashboard_subcategory, through: dashboard_measure
|
||||
|
||||
has_many :dashboard_survey_item_responses
|
||||
|
||||
def score(school:, academic_year:)
|
||||
@score ||= Hash.new do |memo, (school, academic_year)|
|
||||
memo[[school, academic_year]] =
|
||||
survey_item_responses.exclude_boston.where(school:, academic_year:).average(:likert_score).to_f
|
||||
end
|
||||
@score[[school, academic_year]]
|
||||
end
|
||||
|
||||
scope :student_survey_items, lambda {
|
||||
where("survey_items.survey_item_id LIKE 's-%'")
|
||||
}
|
||||
scope :standard_survey_items, lambda {
|
||||
where("survey_items.survey_item_id LIKE 's-%-q%'")
|
||||
}
|
||||
scope :teacher_survey_items, lambda {
|
||||
where("survey_items.survey_item_id LIKE 't-%'")
|
||||
}
|
||||
scope :short_form_survey_items, lambda {
|
||||
where(on_short_form: true)
|
||||
}
|
||||
scope :early_education_survey_items, lambda {
|
||||
where("survey_items.survey_item_id LIKE '%-%-es%'")
|
||||
}
|
||||
|
||||
scope :survey_items_for_grade, lambda { |school, academic_year, grade|
|
||||
includes(:survey_item_responses)
|
||||
.where("survey_item_responses.grade": grade,
|
||||
"survey_item_responses.school": school,
|
||||
"survey_item_responses.academic_year": academic_year).distinct
|
||||
}
|
||||
|
||||
scope :survey_item_ids_for_grade, lambda { |school, academic_year, grade|
|
||||
survey_items_for_grade(school, academic_year, grade).pluck(:survey_item_id)
|
||||
}
|
||||
|
||||
scope :survey_items_for_grade_and_subcategory, lambda { |school, academic_year, grade, subcategory|
|
||||
includes(:survey_item_responses)
|
||||
.where(
|
||||
survey_item_id: subcategory.survey_items.pluck(:survey_item_id),
|
||||
"survey_item_responses.school": school,
|
||||
"survey_item_responses.academic_year": academic_year,
|
||||
"survey_item_responses.grade": grade
|
||||
)
|
||||
}
|
||||
|
||||
scope :survey_type_for_grade, lambda { |school, academic_year, grade|
|
||||
survey_items_set_by_grade = survey_items_for_grade(school, academic_year, grade).pluck(:survey_item_id).to_set
|
||||
if survey_items_set_by_grade.size > 0 && survey_items_set_by_grade.subset?(early_education_survey_items.pluck(:survey_item_id).to_set)
|
||||
return :early_education
|
||||
end
|
||||
|
||||
:standard
|
||||
}
|
||||
|
||||
def description
|
||||
Summary.new(survey_item_id, prompt, true)
|
||||
end
|
||||
|
||||
def self.survey_type(survey_item_ids:)
|
||||
survey_item_ids = survey_item_ids.reject { |id| id.ends_with?("-1") }.to_set
|
||||
return :short_form if survey_item_ids.subset? short_form_survey_items.map(&:survey_item_id).to_set
|
||||
return :early_education if survey_item_ids.subset? early_education_survey_items.map(&:survey_item_id).to_set
|
||||
return :teacher if survey_item_ids.subset? teacher_survey_items.map(&:survey_item_id).to_set
|
||||
return :standard if survey_item_ids.subset? standard_survey_items.map(&:survey_item_id).to_set
|
||||
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,85 @@
|
||||
module Dashboard
|
||||
class SurveyItemResponse < ApplicationRecord
|
||||
TEACHER_RESPONSE_THRESHOLD = 2
|
||||
STUDENT_RESPONSE_THRESHOLD = 10
|
||||
|
||||
belongs_to :dashboard_school
|
||||
belongs_to :dashboard_survey_item
|
||||
belongs_to :dashboard_academic_year
|
||||
belongs_to :dashboard_student, optional: true
|
||||
belongs_to :dashboard_gender, optional: true
|
||||
belongs_to :dashboard_income, optional: true
|
||||
belongs_to :dashboard_ell, optional: true
|
||||
belongs_to :dashboard_sped, optional: true
|
||||
|
||||
has_one :dashboard_measure, through: :dashboard_survey_item
|
||||
|
||||
scope :exclude_boston, lambda {
|
||||
includes(school: :district).where.not("district.name": "Boston")
|
||||
}
|
||||
|
||||
scope :averages_for_grade, lambda { |survey_items, school, academic_year, grade|
|
||||
SurveyItemResponse.where(survey_item: survey_items, school:,
|
||||
academic_year:, grade:).group(:survey_item).having("count(*) >= 10").average(:likert_score)
|
||||
}
|
||||
|
||||
scope :averages_for_gender, lambda { |survey_items, school, academic_year, gender|
|
||||
SurveyItemResponse.where(survey_item: survey_items, school:,
|
||||
academic_year:, gender:, grade: school.grades(academic_year:)).group(:survey_item).having("count(*) >= 10").average(:likert_score)
|
||||
}
|
||||
|
||||
scope :averages_for_income, lambda { |survey_items, school, academic_year, income|
|
||||
SurveyItemResponse.where(survey_item: survey_items, school:,
|
||||
academic_year:, income:, grade: school.grades(academic_year:)).group(:survey_item).having("count(*) >= 10").average(:likert_score)
|
||||
}
|
||||
|
||||
scope :averages_for_ell, lambda { |survey_items, school, academic_year, ell|
|
||||
SurveyItemResponse.where(survey_item: survey_items, school:,
|
||||
academic_year:, ell:, grade: school.grades(academic_year:)).group(:survey_item).having("count(*) >= 10").average(:likert_score)
|
||||
}
|
||||
|
||||
scope :averages_for_sped, lambda { |survey_items, school, academic_year, sped|
|
||||
SurveyItemResponse.where(survey_item: survey_items, school:,
|
||||
academic_year:, sped:, grade: school.grades(academic_year:)).group(:survey_item).having("count(*) >= 10").average(:likert_score)
|
||||
}
|
||||
|
||||
scope :averages_for_race, lambda { |school, academic_year, race|
|
||||
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:, grade: school.grades(academic_year:)
|
||||
).where("student_races.race_id": race.id).group(:survey_item_id).having("count(*) >= 10").average(:likert_score)
|
||||
}
|
||||
|
||||
def self.grouped_responses(school:, academic_year:)
|
||||
@grouped_responses ||= Hash.new do |memo, (school, academic_year)|
|
||||
memo[[school, academic_year]] =
|
||||
SurveyItemResponse.where(school:, academic_year:).group(:survey_item_id).average(:likert_score)
|
||||
end
|
||||
@grouped_responses[[school, academic_year]]
|
||||
end
|
||||
|
||||
def self.teacher_survey_items_with_sufficient_responses(school:, academic_year:)
|
||||
@teacher_survey_items_with_sufficient_responses ||= Hash.new do |memo, (school, academic_year)|
|
||||
hash = SurveyItem.joins("inner join survey_item_responses on survey_item_responses.survey_item_id = survey_items.id")
|
||||
.teacher_survey_items
|
||||
.where("survey_item_responses.school": school, "survey_item_responses.academic_year": academic_year)
|
||||
.group("survey_items.id")
|
||||
.having("count(*) > 0").count
|
||||
memo[[school, academic_year]] = hash
|
||||
end
|
||||
@teacher_survey_items_with_sufficient_responses[[school, academic_year]]
|
||||
end
|
||||
|
||||
def self.student_survey_items_with_sufficient_responses_by_grade(school:, academic_year:)
|
||||
@student_survey_items_with_sufficient_responses_by_grade ||= Hash.new do |memo, (school, academic_year)|
|
||||
hash = SurveyItem.joins("inner join survey_item_responses on survey_item_responses.survey_item_id = survey_items.id")
|
||||
.student_survey_items
|
||||
.where("survey_item_responses.school": school, "survey_item_responses.academic_year": academic_year)
|
||||
.group(:grade, :id)
|
||||
.count
|
||||
memo[[school, academic_year]] = hash
|
||||
end
|
||||
|
||||
@student_survey_items_with_sufficient_responses_by_grade[[school, academic_year]]
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class TeacherResponseRateCalculator < ResponseRateCalculator
|
||||
def survey_item_count
|
||||
@survey_item_count ||= survey_items_with_sufficient_responses.length
|
||||
end
|
||||
|
||||
def survey_items_have_sufficient_responses?
|
||||
survey_item_count.positive?
|
||||
end
|
||||
|
||||
def survey_items_with_sufficient_responses
|
||||
@survey_items_with_sufficient_responses ||= SurveyItemResponse.teacher_survey_items_with_sufficient_responses(
|
||||
school:, academic_year:
|
||||
).slice(*@subcategory.teacher_survey_items.map(&:id))
|
||||
end
|
||||
|
||||
def response_count
|
||||
@response_count ||= survey_items_with_sufficient_responses&.values&.sum
|
||||
end
|
||||
|
||||
def total_possible_responses
|
||||
@total_possible_responses ||= respondents.total_teachers
|
||||
end
|
||||
|
||||
def raw_response_rate
|
||||
(average_responses_per_survey_item / total_possible_responses.to_f * 100).round
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class AdminDataPresenter < DataItemPresenter
|
||||
def initialize(measure_id:, admin_data_items:, has_sufficient_data:, school:, academic_year:)
|
||||
super(measure_id:, has_sufficient_data:, school:, academic_year:)
|
||||
@admin_data_items = admin_data_items
|
||||
end
|
||||
|
||||
def title
|
||||
"School admin data"
|
||||
end
|
||||
|
||||
def id
|
||||
"admin-data-items-#{@measure_id}"
|
||||
end
|
||||
|
||||
def reason_for_insufficiency
|
||||
"limited availability"
|
||||
end
|
||||
|
||||
def descriptions_and_availability
|
||||
@admin_data_items.map do |admin_data_item|
|
||||
Summary.new(admin_data_item.admin_data_item_id, admin_data_item.description,
|
||||
admin_data_item.admin_data_values.where(school:, academic_year:).count > 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
class BarPresenter
|
||||
include AnalyzeHelper
|
||||
attr_reader :score, :x_position, :academic_year, :measure_id, :measure, :color
|
||||
|
||||
MINIMUM_BAR_HEIGHT = 2
|
||||
|
||||
def initialize(measure:, academic_year:, score:, x_position:, color:)
|
||||
@score = score
|
||||
@x_position = x_position
|
||||
@academic_year = academic_year
|
||||
@measure = measure
|
||||
@measure_id = measure.measure_id
|
||||
@color = color
|
||||
end
|
||||
|
||||
def y_offset
|
||||
benchmark_height = analyze_zone_height * 2
|
||||
case zone.type
|
||||
when :ideal, :approval
|
||||
benchmark_height - bar_height_percentage
|
||||
else
|
||||
benchmark_height
|
||||
end
|
||||
end
|
||||
|
||||
def bar_color
|
||||
"fill-#{zone.type}"
|
||||
end
|
||||
|
||||
def bar_height_percentage
|
||||
bar_height = send("#{zone.type}_bar_height_percentage") || 0
|
||||
enforce_minimum_height(bar_height:)
|
||||
end
|
||||
|
||||
def percentage
|
||||
low_benchmark = zone.low_benchmark
|
||||
(score.average - low_benchmark) / (zone.high_benchmark - low_benchmark)
|
||||
end
|
||||
|
||||
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
|
||||
)
|
||||
zones.zone_for_score(score.average)
|
||||
end
|
||||
|
||||
def average
|
||||
average = score.average || 0
|
||||
|
||||
average.round(6)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enforce_minimum_height(bar_height:)
|
||||
bar_height < MINIMUM_BAR_HEIGHT ? MINIMUM_BAR_HEIGHT : bar_height
|
||||
end
|
||||
|
||||
def ideal_bar_height_percentage
|
||||
(percentage * zone_height_percentage + zone_height_percentage) * 100
|
||||
end
|
||||
|
||||
def approval_bar_height_percentage
|
||||
(percentage * zone_height_percentage) * 100
|
||||
end
|
||||
|
||||
def growth_bar_height_percentage
|
||||
((1 - percentage) * zone_height_percentage) * 100
|
||||
end
|
||||
|
||||
def watch_bar_height_percentage
|
||||
((1 - percentage) * zone_height_percentage + zone_height_percentage) * 100
|
||||
end
|
||||
|
||||
def warning_bar_height_percentage
|
||||
((1 - percentage) * zone_height_percentage + zone_height_percentage + zone_height_percentage) * 100
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class AllData
|
||||
include Analyze::Graph::Column
|
||||
def to_s
|
||||
"All Data"
|
||||
end
|
||||
|
||||
def slug
|
||||
"all-data"
|
||||
end
|
||||
|
||||
def columns
|
||||
[AllStudent, AllTeacher, AllAdmin, GroupedBarColumnPresenter]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
class AllAdmin < GroupedBarColumnPresenter
|
||||
def label
|
||||
%w[All Admin]
|
||||
end
|
||||
|
||||
def basis
|
||||
"admin data"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
!measure.includes_admin_data_items?
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
!academic_years.any? do |year|
|
||||
measure.sufficient_admin_data?(school:, academic_year: year)
|
||||
end
|
||||
end
|
||||
|
||||
def insufficiency_message
|
||||
["data not", "available"]
|
||||
end
|
||||
|
||||
def score(year_index)
|
||||
measure.admin_score(school:, academic_year: academic_years[year_index])
|
||||
end
|
||||
|
||||
def type
|
||||
:admin
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
class AllStudent < GroupedBarColumnPresenter
|
||||
def label
|
||||
%w[All Students]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
!measure.includes_student_survey_items?
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
scores = academic_years.map do |year|
|
||||
measure.score(school:, academic_year: year)
|
||||
end
|
||||
|
||||
scores.all? { |score| !score.meets_student_threshold? }
|
||||
end
|
||||
|
||||
def score(year_index)
|
||||
measure.student_score(school:, academic_year: academic_years[year_index])
|
||||
end
|
||||
|
||||
def type
|
||||
:student
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
class AllSurveyData < GroupedBarColumnPresenter
|
||||
def label
|
||||
%w[Survey Data]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
scores = academic_years.map do |year|
|
||||
combined_score(school:, academic_year: year)
|
||||
end
|
||||
|
||||
scores.all? { |score| !score.meets_student_threshold? && !score.meets_teacher_threshold? }
|
||||
end
|
||||
|
||||
def score(year_index)
|
||||
combined_score(school:, academic_year: academic_years[year_index])
|
||||
end
|
||||
|
||||
def type
|
||||
:all_survey_data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def combined_score(school:, academic_year:)
|
||||
teacher_score = measure.teacher_score(school:, academic_year:)
|
||||
student_score = measure.student_score(school:, academic_year:)
|
||||
|
||||
averages = []
|
||||
averages << student_score.average unless student_score.average.nil?
|
||||
averages << teacher_score.average unless teacher_score.average.nil?
|
||||
average = averages.average if averages.length > 0
|
||||
combined_score = Score.new(average:, meets_student_threshold: student_score.meets_student_threshold,
|
||||
meets_teacher_threshold: teacher_score.meets_teacher_threshold)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
class AllTeacher < GroupedBarColumnPresenter
|
||||
def label
|
||||
%w[All Teachers]
|
||||
end
|
||||
|
||||
def basis
|
||||
"teacher surveys"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
!measure.includes_teacher_survey_items?
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
scores = academic_years.map do |year|
|
||||
measure.score(school:, academic_year: year)
|
||||
end
|
||||
|
||||
scores.all? { |score| !score.meets_teacher_threshold? }
|
||||
end
|
||||
|
||||
def score(year_index)
|
||||
measure.teacher_score(school:, academic_year: academic_years[year_index])
|
||||
end
|
||||
|
||||
def type
|
||||
:teacher
|
||||
end
|
||||
|
||||
def n_size(year_index)
|
||||
SurveyItemResponse.where(survey_item: measure.teacher_survey_items, school:,
|
||||
academic_year: academic_years[year_index]).select(:response_id).distinct.count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module EllColumn
|
||||
class Ell < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::EllColumn::ScoreForEll
|
||||
include Analyze::Graph::Column::EllColumn::EllCount
|
||||
def label
|
||||
%w[ELL]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def ell
|
||||
::Ell.find_by_slug "ell"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,18 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module EllColumn
|
||||
module EllCount
|
||||
def type
|
||||
:student
|
||||
end
|
||||
|
||||
def n_size(year_index)
|
||||
SurveyItemResponse.where(ell:, survey_item: measure.student_survey_items, school:, grade: grades(year_index),
|
||||
academic_year: academic_years[year_index]).select(:response_id).distinct.count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module EllColumn
|
||||
class NotEll < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::EllColumn::ScoreForEll
|
||||
include Analyze::Graph::Column::EllColumn::EllCount
|
||||
def label
|
||||
["Not ELL"]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def ell
|
||||
::Ell.find_by_slug "not-ell"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,42 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module EllColumn
|
||||
module ScoreForEll
|
||||
def score(year_index)
|
||||
academic_year = academic_years[year_index]
|
||||
meets_student_threshold = sufficient_student_responses?(academic_year:)
|
||||
return Score::NIL_SCORE unless meets_student_threshold
|
||||
|
||||
averages = SurveyItemResponse.averages_for_ell(measure.student_survey_items, school, academic_year,
|
||||
ell)
|
||||
average = bubble_up_averages(averages:).round(2)
|
||||
|
||||
Score.new(average:,
|
||||
meets_teacher_threshold: false,
|
||||
meets_student_threshold:,
|
||||
meets_admin_data_threshold: false)
|
||||
end
|
||||
|
||||
def bubble_up_averages(averages:)
|
||||
measure.student_scales.map do |scale|
|
||||
scale.survey_items.map do |survey_item|
|
||||
averages[survey_item]
|
||||
end.remove_blanks.average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def sufficient_student_responses?(academic_year:)
|
||||
return false unless measure.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
|
||||
yearly_counts.any? do |count|
|
||||
count[1] >= 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module EllColumn
|
||||
class Unknown < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::EllColumn::ScoreForEll
|
||||
include Analyze::Graph::Column::EllColumn::EllCount
|
||||
def label
|
||||
%w[Unknown]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def ell
|
||||
::Ell.find_by_slug "unknown"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module GenderColumn
|
||||
class Female < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::GenderColumn::ScoreForGender
|
||||
include Analyze::Graph::Column::GenderColumn::GenderCount
|
||||
def label
|
||||
%w[Female]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def gender
|
||||
::Gender.find_by_qualtrics_code 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,18 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module GenderColumn
|
||||
module GenderCount
|
||||
def type
|
||||
:student
|
||||
end
|
||||
|
||||
def n_size(year_index)
|
||||
SurveyItemResponse.where(gender:, survey_item: measure.student_survey_items, school:, grade: grades(year_index),
|
||||
academic_year: academic_years[year_index]).select(:response_id).distinct.count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module GenderColumn
|
||||
class Male < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::GenderColumn::ScoreForGender
|
||||
include Analyze::Graph::Column::GenderColumn::GenderCount
|
||||
def label
|
||||
%w[Male]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def gender
|
||||
::Gender.find_by_qualtrics_code 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module GenderColumn
|
||||
class NonBinary < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::GenderColumn::ScoreForGender
|
||||
include Analyze::Graph::Column::GenderColumn::GenderCount
|
||||
def label
|
||||
%w[Non-Binary]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def gender
|
||||
::Gender.find_by_qualtrics_code 4
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,42 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module GenderColumn
|
||||
module ScoreForGender
|
||||
def score(year_index)
|
||||
academic_year = academic_years[year_index]
|
||||
meets_student_threshold = sufficient_student_responses?(academic_year:)
|
||||
return Score::NIL_SCORE unless meets_student_threshold
|
||||
|
||||
averages = SurveyItemResponse.averages_for_gender(measure.student_survey_items, school, academic_year,
|
||||
gender)
|
||||
average = bubble_up_averages(averages:).round(2)
|
||||
|
||||
Score.new(average:,
|
||||
meets_teacher_threshold: false,
|
||||
meets_student_threshold:,
|
||||
meets_admin_data_threshold: false)
|
||||
end
|
||||
|
||||
def bubble_up_averages(averages:)
|
||||
measure.student_scales.map do |scale|
|
||||
scale.survey_items.map do |survey_item|
|
||||
averages[survey_item]
|
||||
end.remove_blanks.average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def sufficient_student_responses?(academic_year:)
|
||||
return false unless measure.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
|
||||
yearly_counts.any? do |count|
|
||||
count[1] >= 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module GenderColumn
|
||||
class Unknown < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::GenderColumn::ScoreForGender
|
||||
include Analyze::Graph::Column::GenderColumn::GenderCount
|
||||
def label
|
||||
%w[Unknown]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def gender
|
||||
::Gender.find_by_qualtrics_code 99
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Eight < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 8]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
8
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Eleven < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 11]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
11
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Five < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 5]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
5
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Four < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 4]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
4
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,18 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
module GradeCount
|
||||
def type
|
||||
:student
|
||||
end
|
||||
|
||||
def n_size(year_index)
|
||||
SurveyItemResponse.where(grade:, survey_item: measure.student_survey_items, school:,
|
||||
academic_year: academic_years[year_index]).select(:response_id).distinct.count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Nine < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 9]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
9
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class One < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 1]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,42 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
module ScoreForGrade
|
||||
def score(year_index)
|
||||
academic_year = academic_years[year_index]
|
||||
meets_student_threshold = sufficient_student_responses?(academic_year:)
|
||||
return Score::NIL_SCORE unless meets_student_threshold
|
||||
|
||||
averages = SurveyItemResponse.averages_for_grade(measure.student_survey_items, school,
|
||||
academic_year, grade)
|
||||
average = bubble_up_averages(averages:).round(2)
|
||||
|
||||
Score.new(average:,
|
||||
meets_teacher_threshold: false,
|
||||
meets_student_threshold:,
|
||||
meets_admin_data_threshold: false)
|
||||
end
|
||||
|
||||
def bubble_up_averages(averages:)
|
||||
measure.student_scales.map do |scale|
|
||||
scale.survey_items.map do |survey_item|
|
||||
averages[survey_item]
|
||||
end.remove_blanks.average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def sufficient_student_responses?(academic_year:)
|
||||
return false unless measure.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
|
||||
yearly_counts.any? do |count|
|
||||
count[1] >= 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Seven < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 7]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
7
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Six < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 6]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
6
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Ten < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 10]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Three < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 3]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
3
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Twelve < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 12]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
12
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Two < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Grade 2]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Grade
|
||||
class Zero < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::Grade::ScoreForGrade
|
||||
include Analyze::Graph::Column::Grade::GradeCount
|
||||
def label
|
||||
%w[Kindergarten]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def grade
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,172 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
class GroupedBarColumnPresenter
|
||||
include AnalyzeHelper
|
||||
|
||||
attr_reader :measure_name, :measure_id, :category, :position, :measure, :school, :academic_years,
|
||||
:number_of_columns
|
||||
|
||||
def initialize(measure:, school:, academic_years:, position:, number_of_columns:)
|
||||
@measure = measure
|
||||
@measure_name = @measure.name
|
||||
@measure_id = @measure.measure_id
|
||||
@category = @measure.subcategory.category
|
||||
@school = school
|
||||
@academic_years = academic_years
|
||||
@position = position
|
||||
@number_of_columns = number_of_columns
|
||||
end
|
||||
|
||||
def academic_year_for_year_index(year_index)
|
||||
academic_years[year_index]
|
||||
end
|
||||
|
||||
def score(year_index)
|
||||
measure.score(school:, academic_year: academic_years[year_index]) || 0
|
||||
end
|
||||
|
||||
def bars
|
||||
@bars ||= yearly_scores.map.each_with_index do |yearly_score, index|
|
||||
year = yearly_score.year
|
||||
Analyze::BarPresenter.new(measure:, academic_year: year,
|
||||
score: yearly_score.score,
|
||||
x_position: bar_x(index),
|
||||
color: bar_color(year))
|
||||
end
|
||||
end
|
||||
|
||||
def label
|
||||
%w[All Data]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
scores = academic_years.map do |year|
|
||||
measure.score(school:, academic_year: year)
|
||||
end
|
||||
|
||||
scores.all? do |score|
|
||||
!score.meets_teacher_threshold? && !score.meets_student_threshold? && !score.meets_admin_data_threshold?
|
||||
end
|
||||
end
|
||||
|
||||
def column_midpoint
|
||||
zone_label_width + (grouped_chart_column_width * (position + 1)) - (grouped_chart_column_width / 2)
|
||||
end
|
||||
|
||||
def bar_width
|
||||
min_bar_width(10.5 / data_sources)
|
||||
end
|
||||
|
||||
def min_bar_width(number)
|
||||
min_width = 2
|
||||
number < min_width ? min_width : number
|
||||
end
|
||||
|
||||
def message_x
|
||||
column_midpoint - message_width / 2
|
||||
end
|
||||
|
||||
def message_y
|
||||
17
|
||||
end
|
||||
|
||||
def message_width
|
||||
20
|
||||
end
|
||||
|
||||
def message_height
|
||||
34
|
||||
end
|
||||
|
||||
def column_end_x
|
||||
zone_label_width + (grouped_chart_column_width * (position + 1))
|
||||
end
|
||||
|
||||
def column_start_x
|
||||
zone_label_width + (grouped_chart_column_width * position)
|
||||
end
|
||||
|
||||
def grouped_chart_column_width
|
||||
graph_width / data_sources
|
||||
end
|
||||
|
||||
def bar_label_height
|
||||
(100 - ((100 - analyze_graph_height) / 2))
|
||||
end
|
||||
|
||||
def data_sources
|
||||
number_of_columns
|
||||
end
|
||||
|
||||
def basis
|
||||
"student surveys"
|
||||
end
|
||||
|
||||
def type
|
||||
:all_data
|
||||
end
|
||||
|
||||
def show_popover?
|
||||
%i[student teacher].include? type
|
||||
end
|
||||
|
||||
def n_size(year_index)
|
||||
SurveyItemResponse.where(survey_item: measure.student_survey_items, school:, grade: grades(year_index),
|
||||
academic_year: academic_years[year_index]).select(:response_id).distinct.count
|
||||
end
|
||||
|
||||
def popover_content(year_index)
|
||||
"#{n_size(year_index)} #{type.to_s.capitalize}s"
|
||||
end
|
||||
|
||||
def insufficiency_message
|
||||
["survey response", "rate below 25%"]
|
||||
end
|
||||
|
||||
def sufficient_data?(year_index)
|
||||
case basis
|
||||
when "student"
|
||||
score(year_index).meets_student_threshold
|
||||
when "teacher"
|
||||
score(year_index).meets_teacher_threshold
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def grades(year_index)
|
||||
Respondent.by_school_and_year(school:, academic_year: academic_years[year_index]).enrollment_by_grade.keys
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
YearlyScore = Struct.new(:year, :score)
|
||||
def yearly_scores
|
||||
yearly_scores = academic_years.each_with_index.map do |year, index|
|
||||
YearlyScore.new(year, score(index))
|
||||
end
|
||||
yearly_scores.reject do |yearly_score|
|
||||
yearly_score.score.blank?
|
||||
end
|
||||
end
|
||||
|
||||
def bar_color(year)
|
||||
@available_academic_years ||= AcademicYear.order(:range).all
|
||||
colors[@available_academic_years.find_index(year)]
|
||||
end
|
||||
|
||||
def bar_x(index)
|
||||
column_start_x + (index * bar_width * 1.2) +
|
||||
((column_end_x - column_start_x) - (yearly_scores.size * bar_width * 1.2)) / 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module IncomeColumn
|
||||
class Disadvantaged < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
|
||||
include Analyze::Graph::Column::IncomeColumn::IncomeCount
|
||||
def label
|
||||
%w[Economically Disadvantaged]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def income
|
||||
Income.find_by_designation "Economically Disadvantaged - Y"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,18 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module IncomeColumn
|
||||
module IncomeCount
|
||||
def type
|
||||
:student
|
||||
end
|
||||
|
||||
def n_size(year_index)
|
||||
SurveyItemResponse.where(income:, survey_item: measure.student_survey_items, school:, grade: grades(year_index),
|
||||
academic_year: academic_years[year_index]).select(:response_id).distinct.count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module IncomeColumn
|
||||
class NotDisadvantaged < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
|
||||
include Analyze::Graph::Column::IncomeColumn::IncomeCount
|
||||
def label
|
||||
["Not Economically", "Disadvantaged"]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def income
|
||||
Income.find_by_designation "Economically Disadvantaged - N"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,42 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module IncomeColumn
|
||||
module ScoreForIncome
|
||||
def score(year_index)
|
||||
academic_year = academic_years[year_index]
|
||||
meets_student_threshold = sufficient_student_responses?(academic_year:)
|
||||
return Score::NIL_SCORE unless meets_student_threshold
|
||||
|
||||
averages = SurveyItemResponse.averages_for_income(measure.student_survey_items, school, academic_year,
|
||||
income)
|
||||
average = bubble_up_averages(averages:).round(2)
|
||||
|
||||
Score.new(average:,
|
||||
meets_teacher_threshold: false,
|
||||
meets_student_threshold:,
|
||||
meets_admin_data_threshold: false)
|
||||
end
|
||||
|
||||
def bubble_up_averages(averages:)
|
||||
measure.student_scales.map do |scale|
|
||||
scale.survey_items.map do |survey_item|
|
||||
averages[survey_item]
|
||||
end.remove_blanks.average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def sufficient_student_responses?(academic_year:)
|
||||
return false unless measure.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
|
||||
yearly_counts.any? do |count|
|
||||
count[1] >= 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module IncomeColumn
|
||||
class Unknown < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
|
||||
include Analyze::Graph::Column::IncomeColumn::IncomeCount
|
||||
def label
|
||||
["Unknown"]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def income
|
||||
Income.find_by_designation "Unknown"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
class AmericanIndian < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::ScoreForRace
|
||||
include Analyze::Graph::Column::RaceColumn::RaceCount
|
||||
def label
|
||||
%w[American Indian]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def race
|
||||
Race.find_by_qualtrics_code 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
class Asian < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::ScoreForRace
|
||||
include Analyze::Graph::Column::RaceColumn::RaceCount
|
||||
def label
|
||||
%w[Asian]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def race
|
||||
Race.find_by_qualtrics_code 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
class Black < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::ScoreForRace
|
||||
include Analyze::Graph::Column::RaceColumn::RaceCount
|
||||
def label
|
||||
%w[Black]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def race
|
||||
Race.find_by_qualtrics_code 3
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
class Hispanic < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::ScoreForRace
|
||||
include Analyze::Graph::Column::RaceColumn::RaceCount
|
||||
def label
|
||||
%w[Hispanic]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def race
|
||||
Race.find_by_qualtrics_code 4
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
class MiddleEastern < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::ScoreForRace
|
||||
include Analyze::Graph::Column::RaceColumn::RaceCount
|
||||
|
||||
def label
|
||||
%w[Middle Eastern]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def race
|
||||
Race.find_by_qualtrics_code 8
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
class Multiracial < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::ScoreForRace
|
||||
include Analyze::Graph::Column::RaceColumn::RaceCount
|
||||
def label
|
||||
%w[Multiracial]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def race
|
||||
Race.find_by_qualtrics_code 100
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,20 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
module RaceCount
|
||||
def type
|
||||
:student
|
||||
end
|
||||
|
||||
def n_size(year_index)
|
||||
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: academic_years[year_index],
|
||||
survey_item: measure.student_survey_items
|
||||
).where("student_races.race_id": race.id).select(:response_id).distinct.count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
class Unknown < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::ScoreForRace
|
||||
include Analyze::Graph::Column::RaceColumn::RaceCount
|
||||
def label
|
||||
%w[Not Listed]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def race
|
||||
Race.find_by_qualtrics_code 99
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module RaceColumn
|
||||
class White < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::ScoreForRace
|
||||
include Analyze::Graph::Column::RaceColumn::RaceCount
|
||||
def label
|
||||
%w[White]
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def race
|
||||
Race.find_by_qualtrics_code 5
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,40 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module ScoreForRace
|
||||
def score(year_index)
|
||||
academic_year = academic_years[year_index]
|
||||
meets_student_threshold = sufficient_student_responses?(academic_year:)
|
||||
return Score::NIL_SCORE unless meets_student_threshold
|
||||
|
||||
survey_items = measure.student_survey_items
|
||||
|
||||
averages = SurveyItemResponse.averages_for_race(school, academic_year, race)
|
||||
average = bubble_up_averages(averages:).round(2)
|
||||
|
||||
Score.new(average:,
|
||||
meets_teacher_threshold: false,
|
||||
meets_student_threshold:,
|
||||
meets_admin_data_threshold: false)
|
||||
end
|
||||
|
||||
def bubble_up_averages(averages:)
|
||||
measure.student_scales.map do |scale|
|
||||
scale.survey_items.map do |survey_item|
|
||||
averages[survey_item.id]
|
||||
end.remove_blanks.average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def sufficient_student_responses?(academic_year:)
|
||||
return false unless measure.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:
|
||||
).where("student_races.race_id": race.id).distinct.pluck(:student_id).count
|
||||
number_of_students_for_a_racial_group >= 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module SpedColumn
|
||||
class NotSped < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::SpedColumn::ScoreForSped
|
||||
include Analyze::Graph::Column::SpedColumn::SpedCount
|
||||
|
||||
def label
|
||||
["Not Special", "Education"]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def sped
|
||||
::Sped.find_by_slug "not-special-education"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,42 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module SpedColumn
|
||||
module ScoreForSped
|
||||
def score(year_index)
|
||||
academic_year = academic_years[year_index]
|
||||
meets_student_threshold = sufficient_student_responses?(academic_year:)
|
||||
return Score::NIL_SCORE unless meets_student_threshold
|
||||
|
||||
averages = SurveyItemResponse.averages_for_sped(measure.student_survey_items, school, academic_year,
|
||||
sped)
|
||||
average = bubble_up_averages(averages:).round(2)
|
||||
|
||||
Score.new(average:,
|
||||
meets_teacher_threshold: false,
|
||||
meets_student_threshold:,
|
||||
meets_admin_data_threshold: false)
|
||||
end
|
||||
|
||||
def bubble_up_averages(averages:)
|
||||
measure.student_scales.map do |scale|
|
||||
scale.survey_items.map do |survey_item|
|
||||
averages[survey_item]
|
||||
end.remove_blanks.average
|
||||
end.remove_blanks.average
|
||||
end
|
||||
|
||||
def sufficient_student_responses?(academic_year:)
|
||||
return false unless measure.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
|
||||
yearly_counts.any? do |count|
|
||||
count[1] >= 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module SpedColumn
|
||||
class Sped < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::SpedColumn::ScoreForSped
|
||||
include Analyze::Graph::Column::SpedColumn::SpedCount
|
||||
|
||||
def label
|
||||
%w[Special Education]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def sped
|
||||
::Sped.find_by_slug "special-education"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,18 @@
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module SpedColumn
|
||||
module SpedCount
|
||||
def type
|
||||
:student
|
||||
end
|
||||
|
||||
def n_size(year_index)
|
||||
SurveyItemResponse.where(sped:, survey_item: measure.student_survey_items, school:, grade: grades(year_index),
|
||||
academic_year: academic_years[year_index]).select(:response_id).distinct.count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module SpedColumn
|
||||
class Unknown < GroupedBarColumnPresenter
|
||||
include Analyze::Graph::Column::SpedColumn::ScoreForSped
|
||||
include Analyze::Graph::Column::SpedColumn::SpedCount
|
||||
|
||||
def label
|
||||
%w[Unknown]
|
||||
end
|
||||
|
||||
def basis
|
||||
"student"
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?
|
||||
false
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?
|
||||
false
|
||||
end
|
||||
|
||||
def sped
|
||||
::Sped.find_by_slug "unknown"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class StudentsAndTeachers
|
||||
include Analyze::Graph::Column
|
||||
def to_s
|
||||
'Students & Teachers'
|
||||
end
|
||||
|
||||
def slug
|
||||
'students-and-teachers'
|
||||
end
|
||||
|
||||
def columns
|
||||
[AllStudent, AllTeacher, AllSurveyData]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class StudentsByEll
|
||||
include Analyze::Graph::Column::EllColumn
|
||||
attr_reader :ells
|
||||
|
||||
def initialize(ells:)
|
||||
@ells = ells
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Students by Ell"
|
||||
end
|
||||
|
||||
def slug
|
||||
"students-by-ell"
|
||||
end
|
||||
|
||||
def columns
|
||||
[].tap do |array|
|
||||
ells.each do |ell|
|
||||
array << column_for_ell_code(code: ell.slug)
|
||||
end
|
||||
array.sort_by!(&:to_s)
|
||||
array << Analyze::Graph::Column::AllStudent
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_for_ell_code(code:)
|
||||
CFR[code]
|
||||
end
|
||||
|
||||
CFR = {
|
||||
"ell" => Analyze::Graph::Column::EllColumn::Ell,
|
||||
"not-ell" => Analyze::Graph::Column::EllColumn::NotEll,
|
||||
"unknown" => Analyze::Graph::Column::EllColumn::Unknown
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class StudentsByGender
|
||||
include Analyze::Graph::Column::GenderColumn
|
||||
attr_reader :genders
|
||||
|
||||
def initialize(genders:)
|
||||
@genders = genders
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Students by Gender"
|
||||
end
|
||||
|
||||
def slug
|
||||
"students-by-gender"
|
||||
end
|
||||
|
||||
def columns
|
||||
[].tap do |array|
|
||||
genders.each do |gender|
|
||||
array << column_for_gender_code(code: gender.qualtrics_code)
|
||||
end
|
||||
array.sort_by!(&:to_s)
|
||||
array << Analyze::Graph::Column::AllStudent
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_for_gender_code(code:)
|
||||
CFR[code]
|
||||
end
|
||||
|
||||
CFR = {
|
||||
1 => Analyze::Graph::Column::GenderColumn::Female,
|
||||
2 => Analyze::Graph::Column::GenderColumn::Male,
|
||||
4 => Analyze::Graph::Column::GenderColumn::NonBinary,
|
||||
99 => Analyze::Graph::Column::GenderColumn::Unknown
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class StudentsByGrade
|
||||
include Analyze::Graph::Column::Grade
|
||||
attr_reader :grades
|
||||
|
||||
def initialize(grades:)
|
||||
@grades = grades
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Students by Grade"
|
||||
end
|
||||
|
||||
def slug
|
||||
"students-by-grade"
|
||||
end
|
||||
|
||||
def columns
|
||||
[].tap do |array|
|
||||
grades.each do |grade|
|
||||
array << column_for_grade_code(code: grade)
|
||||
end
|
||||
array << Analyze::Graph::Column::AllStudent
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_for_grade_code(code:)
|
||||
CFR[code]
|
||||
end
|
||||
|
||||
CFR = {
|
||||
0 => Zero,
|
||||
1 => One,
|
||||
2 => Two,
|
||||
3 => Three,
|
||||
4 => Four,
|
||||
5 => Five,
|
||||
6 => Six,
|
||||
7 => Seven,
|
||||
8 => Eight,
|
||||
9 => Nine,
|
||||
10 => Ten,
|
||||
11 => Eleven,
|
||||
12 => Twelve
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class StudentsByIncome
|
||||
attr_reader :incomes
|
||||
|
||||
def initialize(incomes:)
|
||||
@incomes = incomes
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Students by income"
|
||||
end
|
||||
|
||||
def slug
|
||||
"students-by-income"
|
||||
end
|
||||
|
||||
def columns
|
||||
[].tap do |array|
|
||||
incomes.each do |income|
|
||||
array << column_for_income_code(code: income.slug)
|
||||
end
|
||||
array << Analyze::Graph::Column::AllStudent
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_for_income_code(code:)
|
||||
CFR[code.to_s]
|
||||
end
|
||||
|
||||
CFR = {
|
||||
"economically-disadvantaged-y" => Analyze::Graph::Column::IncomeColumn::Disadvantaged,
|
||||
"economically-disadvantaged-n" => Analyze::Graph::Column::IncomeColumn::NotDisadvantaged,
|
||||
"unknown" => Analyze::Graph::Column::IncomeColumn::Unknown
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class StudentsByRace
|
||||
attr_reader :races
|
||||
|
||||
def initialize(races:)
|
||||
@races = races
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Students by Race"
|
||||
end
|
||||
|
||||
def slug
|
||||
"students-by-race"
|
||||
end
|
||||
|
||||
def columns
|
||||
[].tap do |array|
|
||||
races.each do |race|
|
||||
array << column_for_race_code(code: race.qualtrics_code)
|
||||
end
|
||||
array << Analyze::Graph::Column::AllStudent
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_for_race_code(code:)
|
||||
CFR[code.to_s]
|
||||
end
|
||||
|
||||
CFR = {
|
||||
"1" => Analyze::Graph::Column::RaceColumn::AmericanIndian,
|
||||
"2" => Analyze::Graph::Column::RaceColumn::Asian,
|
||||
"3" => Analyze::Graph::Column::RaceColumn::Black,
|
||||
"4" => Analyze::Graph::Column::RaceColumn::Hispanic,
|
||||
"5" => Analyze::Graph::Column::RaceColumn::White,
|
||||
"8" => Analyze::Graph::Column::RaceColumn::MiddleEastern,
|
||||
"99" => Analyze::Graph::Column::RaceColumn::Unknown,
|
||||
"100" => Analyze::Graph::Column::RaceColumn::Multiracial
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class StudentsBySped
|
||||
include Analyze::Graph::Column::SpedColumn
|
||||
attr_reader :speds
|
||||
|
||||
def initialize(speds:)
|
||||
@speds = speds
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Students by SpEd"
|
||||
end
|
||||
|
||||
def slug
|
||||
"students-by-sped"
|
||||
end
|
||||
|
||||
def columns
|
||||
[].tap do |array|
|
||||
speds.each do |sped|
|
||||
array << column_for_sped_code(code: sped.slug)
|
||||
end
|
||||
array << Analyze::Graph::Column::AllStudent
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_for_sped_code(code:)
|
||||
CFR[code]
|
||||
end
|
||||
|
||||
CFR = {
|
||||
"special-education" => Analyze::Graph::Column::SpedColumn::Sped,
|
||||
"not-special-education" => Analyze::Graph::Column::SpedColumn::NotSped,
|
||||
"unknown" => Analyze::Graph::Column::SpedColumn::Unknown
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,13 @@
|
||||
module Analyze
|
||||
module Group
|
||||
class Ell
|
||||
def name
|
||||
"ELL"
|
||||
end
|
||||
|
||||
def slug
|
||||
"ell"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,13 @@
|
||||
module Analyze
|
||||
module Group
|
||||
class Gender
|
||||
def name
|
||||
'Gender'
|
||||
end
|
||||
|
||||
def slug
|
||||
'gender'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,13 @@
|
||||
module Analyze
|
||||
module Group
|
||||
class Grade
|
||||
def name
|
||||
'Grade'
|
||||
end
|
||||
|
||||
def slug
|
||||
'grade'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,13 @@
|
||||
module Analyze
|
||||
module Group
|
||||
class Income
|
||||
def name
|
||||
'Income'
|
||||
end
|
||||
|
||||
def slug
|
||||
'income'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,13 @@
|
||||
module Analyze
|
||||
module Group
|
||||
class Race
|
||||
def name
|
||||
'Race'
|
||||
end
|
||||
|
||||
def slug
|
||||
'race'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,13 @@
|
||||
module Analyze
|
||||
module Group
|
||||
class Sped
|
||||
def name
|
||||
"Special Education"
|
||||
end
|
||||
|
||||
def slug
|
||||
"sped"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,200 @@
|
||||
module Analyze
|
||||
class Presenter
|
||||
attr_reader :params, :school, :academic_year
|
||||
|
||||
def initialize(params:, school:, academic_year:)
|
||||
@params = params
|
||||
@school = school
|
||||
@academic_year = academic_year
|
||||
end
|
||||
|
||||
def category
|
||||
@category ||= Category.find_by_category_id(params[:category]) || Category.order(:category_id).first
|
||||
end
|
||||
|
||||
def categories
|
||||
@categories = Category.all.order(:category_id)
|
||||
end
|
||||
|
||||
def subcategory
|
||||
@subcategory ||= Subcategory.find_by_subcategory_id(params[:subcategory]) || subcategories.first
|
||||
end
|
||||
|
||||
def subcategories
|
||||
@subcategories = category.subcategories.order(:subcategory_id)
|
||||
end
|
||||
|
||||
def measures
|
||||
@measures = subcategory.measures.order(:measure_id).includes(%i[admin_data_items subcategory])
|
||||
end
|
||||
|
||||
def academic_years
|
||||
@academic_years = AcademicYear.order(:range).all
|
||||
end
|
||||
|
||||
def selected_academic_years
|
||||
@selected_academic_years ||= begin
|
||||
year_params = params[:academic_years]
|
||||
return [] unless year_params
|
||||
|
||||
year_params.split(",").map { |year| AcademicYear.find_by_range(year) }.compact
|
||||
end
|
||||
end
|
||||
|
||||
def races
|
||||
@races ||= Race.all.order(designation: :ASC)
|
||||
end
|
||||
|
||||
def selected_races
|
||||
@selected_races ||= begin
|
||||
race_params = params[:races]
|
||||
return races unless race_params
|
||||
|
||||
race_params.split(",").map { |race| Race.find_by_slug race }.compact
|
||||
end
|
||||
end
|
||||
|
||||
def ells
|
||||
@ells ||= Ell.all.order(slug: :ASC)
|
||||
end
|
||||
|
||||
def selected_ells
|
||||
@selected_ells ||= begin
|
||||
ell_params = params[:ells]
|
||||
return ells unless ell_params
|
||||
|
||||
ell_params.split(",").map { |ell| Ell.find_by_slug ell }.compact
|
||||
end
|
||||
end
|
||||
|
||||
def speds
|
||||
@speds ||= Sped.all.order(id: :ASC)
|
||||
end
|
||||
|
||||
def selected_speds
|
||||
@selected_speds ||= begin
|
||||
sped_params = params[:speds]
|
||||
return speds unless sped_params
|
||||
|
||||
sped_params.split(",").map { |sped| Sped.find_by_slug sped }.compact
|
||||
end
|
||||
end
|
||||
|
||||
def graphs
|
||||
@graphs ||= [Analyze::Graph::AllData.new,
|
||||
Analyze::Graph::StudentsAndTeachers.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::StudentsByEll.new(ells: selected_ells),
|
||||
Analyze::Graph::StudentsBySped.new(speds: selected_speds)]
|
||||
end
|
||||
|
||||
def graph
|
||||
@graph ||= graphs.reduce(graphs.first) do |acc, graph|
|
||||
graph.slug == params[:graph] ? graph : acc
|
||||
end
|
||||
end
|
||||
|
||||
def selected_grades
|
||||
@selected_grades ||= begin
|
||||
grade_params = params[:grades]
|
||||
return grades unless grade_params
|
||||
|
||||
grade_params.split(",").map(&:to_i)
|
||||
end
|
||||
end
|
||||
|
||||
def selected_genders
|
||||
@selected_genders ||= begin
|
||||
gender_params = params[:genders]
|
||||
return genders unless gender_params
|
||||
|
||||
gender_params.split(",").sort.map { |gender| Gender.find_by_designation(gender) }.compact
|
||||
end
|
||||
end
|
||||
|
||||
def genders
|
||||
@genders ||= Gender.all.order(designation: :ASC)
|
||||
end
|
||||
|
||||
def groups
|
||||
@groups = [Analyze::Group::Ell.new, Analyze::Group::Gender.new, Analyze::Group::Grade.new, Analyze::Group::Income.new,
|
||||
Analyze::Group::Race.new, Analyze::Group::Sped.new]
|
||||
end
|
||||
|
||||
def group
|
||||
@group ||= groups.reduce(groups.first) do |acc, group|
|
||||
group.slug == params[:group] ? group : acc
|
||||
end
|
||||
end
|
||||
|
||||
def slice
|
||||
@slice ||= slices.reduce(slices.first) do |acc, slice|
|
||||
slice.slug == params[:slice] ? slice : acc
|
||||
end
|
||||
end
|
||||
|
||||
def slices
|
||||
source.slices
|
||||
end
|
||||
|
||||
def source
|
||||
@source ||= sources.reduce(sources.first) do |acc, source|
|
||||
source.slug == params[:source] ? source : acc
|
||||
end
|
||||
end
|
||||
|
||||
def sources
|
||||
all_data_slices = [Analyze::Slice::AllData.new]
|
||||
all_data_source = Analyze::Source::AllData.new(slices: all_data_slices)
|
||||
|
||||
students_and_teachers = Analyze::Slice::StudentsAndTeachers.new
|
||||
students_by_group = Analyze::Slice::StudentsByGroup.new(races:, grades:)
|
||||
survey_data_slices = [students_and_teachers, students_by_group]
|
||||
survey_data_source = Analyze::Source::SurveyData.new(slices: survey_data_slices)
|
||||
|
||||
@sources = [all_data_source, survey_data_source]
|
||||
end
|
||||
|
||||
def grades
|
||||
@grades ||= SurveyItemResponse.where(school:, academic_year:)
|
||||
.where.not(grade: nil)
|
||||
.group(:grade)
|
||||
.select(:response_id)
|
||||
.distinct(:response_id)
|
||||
.count.reject do |_key, value|
|
||||
value < 10
|
||||
end.keys
|
||||
end
|
||||
|
||||
def incomes
|
||||
@incomes ||= Income.all
|
||||
end
|
||||
|
||||
def selected_incomes
|
||||
@selected_incomes ||= begin
|
||||
income_params = params[:incomes]
|
||||
return incomes unless income_params
|
||||
|
||||
income_params.split(",").map { |income| Income.find_by_slug(income) }.compact
|
||||
end
|
||||
end
|
||||
|
||||
def cache_objects
|
||||
[subcategory,
|
||||
selected_academic_years,
|
||||
graph,
|
||||
selected_races,
|
||||
selected_grades,
|
||||
grades,
|
||||
selected_genders,
|
||||
genders,
|
||||
selected_ells,
|
||||
ells,
|
||||
selected_speds,
|
||||
speds]
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,17 @@
|
||||
module Analyze
|
||||
module Slice
|
||||
class AllData
|
||||
def to_s
|
||||
'All Data'
|
||||
end
|
||||
|
||||
def slug
|
||||
'all-data'
|
||||
end
|
||||
|
||||
def graphs
|
||||
[Analyze::Graph::AllData.new]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,17 @@
|
||||
module Analyze
|
||||
module Slice
|
||||
class StudentsAndTeachers
|
||||
def to_s
|
||||
'Students & Teachers'
|
||||
end
|
||||
|
||||
def slug
|
||||
'students-and-teachers'
|
||||
end
|
||||
|
||||
def graphs
|
||||
[Analyze::Graph::StudentsAndTeachers.new]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,24 @@
|
||||
module Analyze
|
||||
module Slice
|
||||
class StudentsByGroup
|
||||
attr_reader :races, :grades
|
||||
|
||||
def initialize(races:, grades:)
|
||||
@races = races
|
||||
@grades = grades
|
||||
end
|
||||
|
||||
def to_s
|
||||
'Students by Group'
|
||||
end
|
||||
|
||||
def slug
|
||||
'students-by-group'
|
||||
end
|
||||
|
||||
def graphs
|
||||
[Analyze::Graph::StudentsByRace.new(races:), Analyze::Graph::StudentsByGrade.new(grades:)]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,21 @@
|
||||
module Analyze
|
||||
module Source
|
||||
class AllData
|
||||
attr_reader :slices
|
||||
|
||||
include Analyze::Slice
|
||||
|
||||
def initialize(slices:)
|
||||
@slices = slices
|
||||
end
|
||||
|
||||
def to_s
|
||||
'All Data'
|
||||
end
|
||||
|
||||
def slug
|
||||
'all-data'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue