mirror of
https://github.com/edcommonwealth/Dashboard.git
synced 2026-03-07 21:38:14 -08:00
dirty commit: can't get references to work correctly between any tables
This commit is contained in:
parent
e1f0b78236
commit
a4fddbeced
183 changed files with 5461 additions and 5 deletions
59
app/controllers/dashboard/home_controller.rb
Normal file
59
app/controllers/dashboard/home_controller.rb
Normal file
|
|
@ -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
|
||||
60
app/helpers/dashboard/header_helper.rb
Normal file
60
app/helpers/dashboard/header_helper.rb
Normal file
|
|
@ -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
|
||||
40
app/models/dashboard/academic_year.rb
Normal file
40
app/models/dashboard/academic_year.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
module Dashboard
|
||||
class AcademicYear < ApplicationRecord
|
||||
def self.find_by_date(date)
|
||||
year = parse_year_range(date:)
|
||||
range = "#{year.start}-#{year.end.to_s[2, 3]}"
|
||||
academic_years[range]
|
||||
end
|
||||
|
||||
def formatted_range
|
||||
years = range.split("-")
|
||||
"#{years.first} – 20#{years.second}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.parse_year_range(date:)
|
||||
year = date.year
|
||||
if date.month > 6
|
||||
AcademicYearRange.new(year, year + 1)
|
||||
else
|
||||
AcademicYearRange.new(year - 1, year)
|
||||
end
|
||||
end
|
||||
|
||||
# This may cause problems if academic years get loaded from csv instead of the current method that requires a code change to the seeder script. This is because a change in code will trigger a complete reload of the application whereas loading from csv does not. This means if we change academic year to load from csv, the set of academic years will be stale when new years are added.
|
||||
def self.academic_years
|
||||
@@academic_years ||= AcademicYear.all.map { |academic_year| [academic_year.range, academic_year] }.to_h
|
||||
end
|
||||
|
||||
# Needed to reset the academic years when testing with specs that create the same academic year in a before :each block
|
||||
def self.reset_academic_years
|
||||
@@academic_years = nil
|
||||
end
|
||||
|
||||
private_class_method :academic_years
|
||||
private_class_method :parse_year_range
|
||||
end
|
||||
end
|
||||
|
||||
AcademicYearRange = Struct.new(:start, :end)
|
||||
14
app/models/dashboard/admin_data_item.rb
Normal file
14
app/models/dashboard/admin_data_item.rb
Normal file
|
|
@ -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
|
||||
9
app/models/dashboard/admin_data_value.rb
Normal file
9
app/models/dashboard/admin_data_value.rb
Normal file
|
|
@ -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
|
||||
13
app/models/dashboard/category.rb
Normal file
13
app/models/dashboard/category.rb
Normal file
|
|
@ -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
|
||||
18
app/models/dashboard/district.rb
Normal file
18
app/models/dashboard/district.rb
Normal file
|
|
@ -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
|
||||
20
app/models/dashboard/ell.rb
Normal file
20
app/models/dashboard/ell.rb
Normal file
|
|
@ -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
|
||||
26
app/models/dashboard/gender.rb
Normal file
26
app/models/dashboard/gender.rb
Normal file
|
|
@ -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
|
||||
31
app/models/dashboard/income.rb
Normal file
31
app/models/dashboard/income.rb
Normal file
|
|
@ -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
|
||||
227
app/models/dashboard/measure.rb
Normal file
227
app/models/dashboard/measure.rb
Normal file
|
|
@ -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
|
||||
51
app/models/dashboard/race.rb
Normal file
51
app/models/dashboard/race.rb
Normal file
|
|
@ -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
|
||||
27
app/models/dashboard/respondent.rb
Normal file
27
app/models/dashboard/respondent.rb
Normal file
|
|
@ -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
|
||||
7
app/models/dashboard/response_rate.rb
Normal file
7
app/models/dashboard/response_rate.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module Dashboard
|
||||
class ResponseRate < ApplicationRecord
|
||||
belongs_to :dashboard_subcategory
|
||||
belongs_to :dashboard_school
|
||||
belongs_to :dashboard_academic_year
|
||||
end
|
||||
end
|
||||
51
app/models/dashboard/response_rate_calculator.rb
Normal file
51
app/models/dashboard/response_rate_calculator.rb
Normal file
|
|
@ -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
|
||||
44
app/models/dashboard/scale.rb
Normal file
44
app/models/dashboard/scale.rb
Normal file
|
|
@ -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
|
||||
27
app/models/dashboard/school.rb
Normal file
27
app/models/dashboard/school.rb
Normal file
|
|
@ -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
|
||||
28
app/models/dashboard/score.rb
Normal file
28
app/models/dashboard/score.rb
Normal file
|
|
@ -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
|
||||
20
app/models/dashboard/sped.rb
Normal file
20
app/models/dashboard/sped.rb
Normal file
|
|
@ -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
|
||||
9
app/models/dashboard/student.rb
Normal file
9
app/models/dashboard/student.rb
Normal file
|
|
@ -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
|
||||
6
app/models/dashboard/student_race.rb
Normal file
6
app/models/dashboard/student_race.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module Dashboard
|
||||
class StudentRace < ApplicationRecord
|
||||
belongs_to :dashboard_student
|
||||
belongs_to :dashboard_race
|
||||
end
|
||||
end
|
||||
67
app/models/dashboard/student_response_rate_calculator.rb
Normal file
67
app/models/dashboard/student_response_rate_calculator.rb
Normal file
|
|
@ -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
|
||||
102
app/models/dashboard/subcategory.rb
Normal file
102
app/models/dashboard/subcategory.rb
Normal file
|
|
@ -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
|
||||
6
app/models/dashboard/summary.rb
Normal file
6
app/models/dashboard/summary.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class Summary < Struct.new(:id, :description, :available?)
|
||||
end
|
||||
end
|
||||
78
app/models/dashboard/survey_item.rb
Normal file
78
app/models/dashboard/survey_item.rb
Normal file
|
|
@ -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
|
||||
85
app/models/dashboard/survey_item_response.rb
Normal file
85
app/models/dashboard/survey_item_response.rb
Normal file
|
|
@ -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
|
||||
31
app/models/dashboard/teacher_response_rate_calculator.rb
Normal file
31
app/models/dashboard/teacher_response_rate_calculator.rb
Normal file
|
|
@ -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
|
||||
29
app/presenters/dashboard/admin_data_presenter.rb
Normal file
29
app/presenters/dashboard/admin_data_presenter.rb
Normal file
|
|
@ -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
|
||||
85
app/presenters/dashboard/analyze/bar_presenter.rb
Normal file
85
app/presenters/dashboard/analyze/bar_presenter.rb
Normal file
|
|
@ -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
|
||||
20
app/presenters/dashboard/analyze/graph/all_data.rb
Normal file
20
app/presenters/dashboard/analyze/graph/all_data.rb
Normal file
|
|
@ -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
|
||||
39
app/presenters/dashboard/analyze/graph/column/all_admin.rb
Normal file
39
app/presenters/dashboard/analyze/graph/column/all_admin.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/all_student.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/all_student.rb
Normal file
|
|
@ -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
|
||||
42
app/presenters/dashboard/analyze/graph/column/all_teacher.rb
Normal file
42
app/presenters/dashboard/analyze/graph/column/all_teacher.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/eight.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/eight.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/five.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/five.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/four.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/four.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/nine.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/nine.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/one.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/one.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/seven.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/seven.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/six.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/six.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/ten.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/ten.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/three.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/three.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/two.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/two.rb
Normal file
|
|
@ -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
|
||||
33
app/presenters/dashboard/analyze/graph/column/grade/zero.rb
Normal file
33
app/presenters/dashboard/analyze/graph/column/grade/zero.rb
Normal file
|
|
@ -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
|
||||
44
app/presenters/dashboard/analyze/graph/students_by_ell.rb
Normal file
44
app/presenters/dashboard/analyze/graph/students_by_ell.rb
Normal file
|
|
@ -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
|
||||
45
app/presenters/dashboard/analyze/graph/students_by_gender.rb
Normal file
45
app/presenters/dashboard/analyze/graph/students_by_gender.rb
Normal file
|
|
@ -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
|
||||
53
app/presenters/dashboard/analyze/graph/students_by_grade.rb
Normal file
53
app/presenters/dashboard/analyze/graph/students_by_grade.rb
Normal file
|
|
@ -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
|
||||
42
app/presenters/dashboard/analyze/graph/students_by_income.rb
Normal file
42
app/presenters/dashboard/analyze/graph/students_by_income.rb
Normal file
|
|
@ -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
|
||||
47
app/presenters/dashboard/analyze/graph/students_by_race.rb
Normal file
47
app/presenters/dashboard/analyze/graph/students_by_race.rb
Normal file
|
|
@ -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
|
||||
43
app/presenters/dashboard/analyze/graph/students_by_sped.rb
Normal file
43
app/presenters/dashboard/analyze/graph/students_by_sped.rb
Normal file
|
|
@ -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
|
||||
13
app/presenters/dashboard/analyze/group/ell.rb
Normal file
13
app/presenters/dashboard/analyze/group/ell.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module Analyze
|
||||
module Group
|
||||
class Ell
|
||||
def name
|
||||
"ELL"
|
||||
end
|
||||
|
||||
def slug
|
||||
"ell"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/presenters/dashboard/analyze/group/gender.rb
Normal file
13
app/presenters/dashboard/analyze/group/gender.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module Analyze
|
||||
module Group
|
||||
class Gender
|
||||
def name
|
||||
'Gender'
|
||||
end
|
||||
|
||||
def slug
|
||||
'gender'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/presenters/dashboard/analyze/group/grade.rb
Normal file
13
app/presenters/dashboard/analyze/group/grade.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module Analyze
|
||||
module Group
|
||||
class Grade
|
||||
def name
|
||||
'Grade'
|
||||
end
|
||||
|
||||
def slug
|
||||
'grade'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/presenters/dashboard/analyze/group/income.rb
Normal file
13
app/presenters/dashboard/analyze/group/income.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module Analyze
|
||||
module Group
|
||||
class Income
|
||||
def name
|
||||
'Income'
|
||||
end
|
||||
|
||||
def slug
|
||||
'income'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/presenters/dashboard/analyze/group/race.rb
Normal file
13
app/presenters/dashboard/analyze/group/race.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module Analyze
|
||||
module Group
|
||||
class Race
|
||||
def name
|
||||
'Race'
|
||||
end
|
||||
|
||||
def slug
|
||||
'race'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/presenters/dashboard/analyze/group/sped.rb
Normal file
13
app/presenters/dashboard/analyze/group/sped.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module Analyze
|
||||
module Group
|
||||
class Sped
|
||||
def name
|
||||
"Special Education"
|
||||
end
|
||||
|
||||
def slug
|
||||
"sped"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
200
app/presenters/dashboard/analyze/presenter.rb
Normal file
200
app/presenters/dashboard/analyze/presenter.rb
Normal file
|
|
@ -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
|
||||
17
app/presenters/dashboard/analyze/slice/all_data.rb
Normal file
17
app/presenters/dashboard/analyze/slice/all_data.rb
Normal file
|
|
@ -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
|
||||
24
app/presenters/dashboard/analyze/slice/students_by_group.rb
Normal file
24
app/presenters/dashboard/analyze/slice/students_by_group.rb
Normal file
|
|
@ -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
|
||||
21
app/presenters/dashboard/analyze/source/all_data.rb
Normal file
21
app/presenters/dashboard/analyze/source/all_data.rb
Normal file
|
|
@ -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
|
||||
21
app/presenters/dashboard/analyze/source/survey_data.rb
Normal file
21
app/presenters/dashboard/analyze/source/survey_data.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
module Analyze
|
||||
module Source
|
||||
class SurveyData
|
||||
attr_reader :slices
|
||||
|
||||
include Analyze::Slice
|
||||
|
||||
def initialize(slices:)
|
||||
@slices = slices
|
||||
end
|
||||
|
||||
def to_s
|
||||
'Survey Data Only'
|
||||
end
|
||||
|
||||
def slug
|
||||
'survey-data-only'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue