dirty commit: can't get references to work correctly between any tables

This commit is contained in:
Nelson Jovel 2024-01-04 19:36:10 -08:00
parent e1f0b78236
commit a4fddbeced
183 changed files with 5461 additions and 5 deletions

View 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

View 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

View 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

View 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

View 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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View 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

View 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

View file

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

View 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

View 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

View file

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

View 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

View 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

View 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

View 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

View file

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

View 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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -0,0 +1,13 @@
module Analyze
module Group
class Ell
def name
"ELL"
end
def slug
"ell"
end
end
end
end

View file

@ -0,0 +1,13 @@
module Analyze
module Group
class Gender
def name
'Gender'
end
def slug
'gender'
end
end
end
end

View file

@ -0,0 +1,13 @@
module Analyze
module Group
class Grade
def name
'Grade'
end
def slug
'grade'
end
end
end
end

View file

@ -0,0 +1,13 @@
module Analyze
module Group
class Income
def name
'Income'
end
def slug
'income'
end
end
end
end

View file

@ -0,0 +1,13 @@
module Analyze
module Group
class Race
def name
'Race'
end
def slug
'race'
end
end
end
end

View file

@ -0,0 +1,13 @@
module Analyze
module Group
class Sped
def name
"Special Education"
end
def slug
"sped"
end
end
end
end

View 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

View 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

View file

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

View 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

View 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

View 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

View file

@ -0,0 +1,46 @@
module Dashboard
class BackgroundPresenter
include AnalyzeHelper
attr_reader :num_of_columns
def initialize(num_of_columns:)
@num_of_columns = num_of_columns
end
def zone_label_x
2
end
def benchmark_y
(analyze_zone_height * 2) - (benchmark_height / 2.0)
end
def benchmark_height
1
end
def grouped_chart_column_width
graph_width / data_sources
end
def column_end_x(position)
zone_label_width + (grouped_chart_column_width * position)
end
def column_start_x(position)
column_end_x(position - 1)
end
def bar_label_height
(100 - ((100 - analyze_graph_height) / 2))
end
def zone_label_y(position)
8.5 * (position + position - 1)
end
def data_sources
num_of_columns
end
end
end

View file

@ -0,0 +1,71 @@
# frozen_string_literal: true
module Dashboard
class CategoryPresenter
def initialize(category:)
@category = category
end
def id
@category.category_id
end
def name
@category.name
end
def description
@category.description
end
def short_description
@category.short_description
end
def slug
@category.slug
end
def icon_class
icon_suffix = classes[id.to_sym]
"fas fa-#{icon_suffix}"
end
def icon_color_class
color_suffix = colors[id.to_sym]
"color-#{color_suffix}"
end
def subcategories(academic_year:, school:)
@category.subcategories.includes([:measures]).sort_by(&:subcategory_id).map do |subcategory|
SubcategoryPresenter.new(
subcategory:,
academic_year:,
school:
)
end
end
def to_model
@category
end
private
def colors
{ '1': "blue",
'2': "red",
'3': "black",
'4': "lime",
'5': "teal" }
end
def classes
{ '1': "apple-alt",
'2': "school",
'3': "users-cog",
'4': "graduation-cap",
'5': "heart" }
end
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Dashboard
class DataItemPresenter
attr_reader :measure_id, :has_sufficient_data, :school, :academic_year
def initialize(measure_id:, has_sufficient_data:, school:, academic_year:)
@measure_id = measure_id
@has_sufficient_data = has_sufficient_data
@school = school
@academic_year = academic_year
end
def data_item_accordion_id
"data-item-accordion-#{@measure_id}"
end
def sufficient_data?
@has_sufficient_data
end
end
end

View file

@ -0,0 +1,70 @@
# frozen_string_literal: true
module Dashboard
class GaugePresenter
def initialize(zones:, score:)
@zones = zones
@score = score&.round(2)
end
def title
zone.type.to_s.titleize
end
def color_class
"fill-#{zone.type}"
end
def score_percentage
percentage_for @score
end
def key_benchmark_percentage
percentage_for @zones.approval_zone.low_benchmark
end
def boundary_percentage_for(zone)
case zone
when :watch_low
watch_low_boundary
when :growth_low
growth_low_boundary
when :ideal_low
ideal_low_boundary
end
end
attr_reader :score
private
def watch_low_boundary
percentage_for @zones.watch_zone.low_benchmark
end
def growth_low_boundary
percentage_for @zones.growth_zone.low_benchmark
end
def approval_low_boundary
percentage_for @zones.approval_zone.low_benchmark
end
def ideal_low_boundary
percentage_for @zones.ideal_zone.low_benchmark
end
def zone
@zones.zone_for_score(@score)
end
def percentage_for(number)
return nil if number.nil?
zones_minimum = @zones.warning_zone.low_benchmark
zones_maximum = @zones.ideal_zone.high_benchmark
(number - zones_minimum) / (zones_maximum - zones_minimum)
end
end
end

View file

@ -0,0 +1,78 @@
# frozen_string_literal: true
module Dashboard
class MeasurePresenter
attr_reader :measure, :academic_year, :school, :id, :name, :description
def initialize(measure:, academic_year:, school:)
@measure = measure
@academic_year = academic_year
@school = school
@id = measure_id
@name = measure.name
@description = measure.description
end
def gauge_presenter
GaugePresenter.new(zones:, score: score_for_measure.average)
end
def data_item_accordion_id
"data-item-accordion-#{@measure.measure_id}"
end
def data_item_presenters
[].tap do |array|
array << student_survey_presenter if student_survey_items.any?
array << teacher_survey_presenter if teacher_survey_items.any?
array << admin_data_presenter if admin_data_items.any?
end
end
private
def admin_data_items
measure.admin_data_items
end
def score_for_measure
@score ||= @measure.score(school: @school, academic_year: @academic_year)
end
def student_survey_items
measure.student_survey_items
end
def teacher_survey_items
measure.teacher_survey_items
end
def measure_id
measure.measure_id
end
def student_survey_presenter
StudentSurveyPresenter.new(measure_id:, survey_items: student_survey_items,
has_sufficient_data: score_for_measure.meets_student_threshold?, school:, academic_year:)
end
def teacher_survey_presenter
TeacherSurveyPresenter.new(measure_id:, survey_items: teacher_survey_items,
has_sufficient_data: score_for_measure.meets_teacher_threshold?, school:, academic_year:)
end
def admin_data_presenter
AdminDataPresenter.new(measure_id:,
admin_data_items:, has_sufficient_data: score_for_measure.meets_admin_data_threshold?, school:, academic_year:)
end
def zones
Zones.new(
watch_low_benchmark: @measure.watch_low_benchmark,
growth_low_benchmark: @measure.growth_low_benchmark,
approval_low_benchmark: @measure.approval_low_benchmark,
ideal_low_benchmark: @measure.ideal_low_benchmark
)
end
end
end

View file

@ -0,0 +1,110 @@
module Dashboard
class ResponseRatePresenter
attr_reader :focus, :academic_year, :school, :survey_items
def initialize(focus:, academic_year:, school:)
@focus = focus
@academic_year = academic_year
@school = school
if focus == :student
@survey_items = Measure.all.flat_map do |measure|
measure.student_survey_items_with_sufficient_responses(school:, academic_year:)
end
end
@survey_items = SurveyItem.teacher_survey_items if focus == :teacher
end
def date
SurveyItemResponse.where(survey_item: survey_items, school:,
academic_year:).order(recorded_date: :DESC).first&.recorded_date
end
def percentage
return 0 if respondents_count.zero?
cap_at_100(actual_count.to_f / respondents_count.to_f * 100).round
end
def color
percentage > 75 ? "purple" : "gold"
end
def date_message
return "" if date.nil?
"Response rate as of #{date.to_date.strftime('%m/%d/%y')}"
end
def hover_message
return "" if date.nil?
"Percentages based on #{actual_count} out of #{respondents_count.round} #{focus}s completing at least 25% of the survey."
end
private
def cap_at_100(value)
value > 100 ? 100 : value
end
def actual_count
if focus == :teacher
response_count_for_survey_items(survey_items:)
else
non_early_ed_items = survey_items - SurveyItem.early_education_survey_items
non_early_ed_count = response_count_for_survey_items(survey_items: non_early_ed_items)
early_ed_items = survey_items & SurveyItem.early_education_survey_items
early_ed_count = SurveyItemResponse.where(school:, academic_year:,
survey_item: early_ed_items)
.group(:survey_item)
.select(:response_id)
.distinct
.count
.reduce(0) do |largest, row|
count = row[1]
if count > largest
count
else
largest
end
end
non_early_ed_count + early_ed_count
end
end
def response_count_for_survey_items(survey_items:)
SurveyItemResponse.where(school:, academic_year:,
survey_item: survey_items).select(:response_id).distinct.count || 0
end
def respondents_count
return 0 if respondents.nil?
count = enrollment if focus == :student
count = respondents.total_teachers if focus == :teacher
count
end
def enrollment
SurveyItemResponse.where(school:, academic_year:, grade: grades,
survey_item: SurveyItem.student_survey_items)
.select(:grade)
.distinct
.pluck(:grade)
.reject(&:nil?)
.map do |grade|
respondents.enrollment_by_grade[grade]
end.sum.to_f
end
def respondents
@respondents ||= Respondent.by_school_and_year(school:, academic_year:)
end
def grades
respondents.enrollment_by_grade.keys
end
end
end

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Dashboard
class StudentSurveyPresenter < DataItemPresenter
attr_reader :survey_items
def initialize(measure_id:, survey_items:, has_sufficient_data:, school:, academic_year:)
super(measure_id:, has_sufficient_data:, school:, academic_year:)
@survey_items = survey_items
end
def title
"Student survey"
end
def id
"student-survey-items-#{@measure_id}"
end
def reason_for_insufficiency
"low response rate"
end
def descriptions_and_availability
survey_items.reject do |survey_item|
question_id = survey_item.survey_item_id.split("-")[2]
if question_id.present?
question_id.starts_with?("es")
else
false
end
end.map(&:description)
end
end
end

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
module Dashboard
class SubcategoryCardPresenter
attr_reader :name, :subcategory, :category, :subcategory_id
def initialize(subcategory:, zones:, score:)
@name = subcategory.name
@subcategory = subcategory
@category = subcategory.category
@subcategory_id = subcategory.subcategory_id
@zones = zones
@score = score
end
def harvey_ball_icon
"#{zone.type}-harvey-ball"
end
def color
zone.type.to_s
end
def insufficient_data?
zone.type == :insufficient_data
end
def to_model
subcategory
end
private
def zone
@zones.zone_for_score(@score)
end
end
end

View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
module Dashboard
class SubcategoryPresenter
attr_reader :subcategory, :academic_year, :school, :id, :name, :description
def initialize(subcategory:, academic_year:, school:)
@subcategory = subcategory
@academic_year = academic_year
@school = school
@id = @subcategory.subcategory_id
@name = @subcategory.name
@description = @subcategory.description
end
def gauge_presenter
GaugePresenter.new(zones:, score: average_score)
end
def subcategory_card_presenter
SubcategoryCardPresenter.new(subcategory: @subcategory, zones:, score: average_score)
end
def average_score
@average_score ||= @subcategory.score(school: @school, academic_year: @academic_year)
end
def student_response_rate
return "N / A" if Respondent.where(school: @school, academic_year: @academic_year).count.zero?
"#{@subcategory.response_rate(school: @school, academic_year: @academic_year).student_response_rate.round}%"
end
def teacher_response_rate
return "N / A" if Respondent.where(school: @school, academic_year: @academic_year).count.zero?
"#{@subcategory.response_rate(school: @school, academic_year: @academic_year).teacher_response_rate.round}%"
end
def admin_collection_rate
rate = [admin_data_values_count, admin_data_item_count]
format_a_non_applicable_rate rate
end
def measure_presenters
@subcategory.measures.sort_by(&:measure_id).map do |measure|
MeasurePresenter.new(measure:, academic_year: @academic_year, school: @school)
end
end
private
def admin_data_values_count
@subcategory.measures.map do |measure|
measure.scales.map do |scale|
scale.admin_data_items.map do |admin_data_item|
admin_data_item.admin_data_values.where(school: @school, academic_year: @academic_year).count
end
end
end.flatten.sum
end
def admin_data_item_count
measures = @subcategory.measures
return AdminDataItem.for_measures(measures).count if @school.is_hs
AdminDataItem.non_hs_items_for_measures(measures).count
end
def format_a_non_applicable_rate(rate)
rate == [0, 0] ? %w[N A] : rate
end
def zones
Zones.new(
watch_low_benchmark: measures.map(&:watch_low_benchmark).average,
growth_low_benchmark: measures.map(&:growth_low_benchmark).average,
approval_low_benchmark: measures.map(&:approval_low_benchmark).average,
ideal_low_benchmark: measures.map(&:ideal_low_benchmark).average
)
end
def measures
@measures ||= @subcategory.measures.includes([:admin_data_items]).order(:measure_id)
end
end
end

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Dashboard
class TeacherSurveyPresenter < DataItemPresenter
attr_reader :survey_items
def initialize(measure_id:, survey_items:, has_sufficient_data:, school:, academic_year:)
super(measure_id:, has_sufficient_data:, school:, academic_year:)
@survey_items = survey_items
end
def title
"Teacher survey"
end
def id
"teacher-survey-items-#{@measure_id}"
end
def reason_for_insufficiency
"low response rate"
end
def descriptions_and_availability
if @measure_id == "1B-i"
return [Summary.new("1B-i", "Items available upon request to ECP",
true)]
end
survey_items.map do |survey_item|
Summary.new(survey_item.survey_item_id, survey_item.prompt, true)
end
end
end
end

View file

@ -0,0 +1,118 @@
# frozen_string_literal: true
module Dashboard
class VarianceChartRowPresenter
include Comparable
attr_reader :score, :measure_name, :measure_id, :category
def initialize(measure:, score:)
@measure = measure
@score = score.average
@meets_teacher_threshold = score.meets_teacher_threshold?
@meets_student_threshold = score.meets_student_threshold?
@measure_name = @measure.name
@measure_id = @measure.measure_id
@category = @measure.subcategory.category
end
def sufficient_data?
@score != nil
end
def bar_color
"fill-#{zone.type}"
end
def bar_width
"#{(bar_width_percentage * 100).round(2)}%"
end
def x_offset
case zone.type
when :ideal, :approval
"60%"
else
"#{((0.6 - bar_width_percentage) * 100).abs.round(2)}%"
end
end
def order
case zone.type
when :ideal, :approval
bar_width_percentage
when :warning, :watch, :growth
-bar_width_percentage
when :insufficient_data
-100
end
end
def <=>(other)
other.order <=> order
end
def show_partial_data_indicator?
partial_data_sources.present?
end
def partial_data_sources
[].tap do |sources|
sources << "teacher survey results" if @measure.includes_teacher_survey_items? && !@meets_teacher_threshold
sources << "student survey results" if @measure.includes_student_survey_items? && !@meets_student_threshold
sources << "administrative data" if @measure.includes_admin_data_items?
end
end
private
IDEAL_ZONE_WIDTH_PERCENTAGE = 0.2
APPROVAL_ZONE_WIDTH_PERCENTAGE = 0.2
GROWTH_ZONE_WIDTH_PERCENTAGE = 0.2
WATCH_ZONE_WIDTH_PERCENTAGE = 0.2
WARNING_ZONE_WIDTH_PERCENTAGE = 0.2
def bar_width_percentage
send("#{zone.type}_bar_width_percentage")
end
def ideal_bar_width_percentage
percentage * IDEAL_ZONE_WIDTH_PERCENTAGE + APPROVAL_ZONE_WIDTH_PERCENTAGE
end
def approval_bar_width_percentage
percentage * APPROVAL_ZONE_WIDTH_PERCENTAGE
end
def growth_bar_width_percentage
(1 - percentage) * GROWTH_ZONE_WIDTH_PERCENTAGE
end
def watch_bar_width_percentage
(1 - percentage) * WATCH_ZONE_WIDTH_PERCENTAGE + GROWTH_ZONE_WIDTH_PERCENTAGE
end
def warning_bar_width_percentage
(1 - percentage) * WARNING_ZONE_WIDTH_PERCENTAGE + WATCH_ZONE_WIDTH_PERCENTAGE + GROWTH_ZONE_WIDTH_PERCENTAGE
end
def insufficient_data_bar_width_percentage
0
end
def percentage
low_benchmark = zone.low_benchmark
(@score - 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)
end
end
end

View file

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Dashboard
class Zones
def initialize(watch_low_benchmark:, growth_low_benchmark:, approval_low_benchmark:, ideal_low_benchmark:)
@watch_low_benchmark = watch_low_benchmark
@growth_low_benchmark = growth_low_benchmark
@approval_low_benchmark = approval_low_benchmark
@ideal_low_benchmark = ideal_low_benchmark
@warning_low_benchmark = 1
end
Zone = Struct.new(:low_benchmark, :high_benchmark, :type)
def all_zones
[
ideal_zone, approval_zone, growth_zone, watch_zone, warning_zone, insufficient_data
]
end
def warning_zone
Zone.new(1, @watch_low_benchmark, :warning)
end
def watch_zone
Zone.new(@watch_low_benchmark, @growth_low_benchmark, :watch)
end
def growth_zone
Zone.new(@growth_low_benchmark, @approval_low_benchmark, :growth)
end
def approval_zone
Zone.new(@approval_low_benchmark, @ideal_low_benchmark, :approval)
end
def ideal_zone
Zone.new(@ideal_low_benchmark, 5.0, :ideal)
end
def insufficient_data
Zone.new(Float::MIN, Float::MAX, :insufficient_data)
end
def zone_for_score(score)
all_zones.find { |zone| Score.new(average: score).in_zone?(zone:) } || insufficient_data
end
end
end