diff --git a/.rubocop.yml b/.rubocop.yml
index 49357fb1..61850707 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -12,4 +12,4 @@ Style/Documentation:
Enabled: false
Style/StringLiterals:
- EnforcedStyle: single_quotes
+ EnforcedStyle: double_quotes
diff --git a/app/controllers/analyze_controller.rb b/app/controllers/analyze_controller.rb
index a0f1469b..f46fa907 100644
--- a/app/controllers/analyze_controller.rb
+++ b/app/controllers/analyze_controller.rb
@@ -1,172 +1,8 @@
# frozen_string_literal: true
class AnalyzeController < SqmApplicationController
- before_action :assign_categories, :assign_subcategories, :assign_measures, :assign_academic_years,
- :races, :selected_races, :graph, :graphs, :background, :race_score_timestamp,
- :source, :sources, :group, :groups, :selected_grades, :grades, :slice, :selected_genders, :genders, only: [:index]
- def index; end
-
- private
-
- def assign_categories
- @category ||= Category.find_by_category_id(params[:category])
- @category ||= Category.order(:category_id).first
- @categories = Category.all.order(:category_id)
- end
-
- def assign_subcategories
- @subcategories = @category.subcategories.order(:subcategory_id)
- @subcategory ||= Subcategory.find_by_subcategory_id(params[:subcategory])
- @subcategory ||= @subcategories.first
- end
-
- def assign_measures
- @measures = @subcategory.measures.order(:measure_id).includes(%i[admin_data_items subcategory])
- end
-
- def assign_academic_years
- @available_academic_years = AcademicYear.order(:range).all
- year_params = params[:academic_years]
- @academic_year_params = year_params.split(',') if year_params
- @selected_academic_years = []
- @academic_year_params ||= []
- @academic_year_params.each do |year|
- @selected_academic_years << AcademicYear.find_by_range(year)
- end
- end
-
- def races
- @races ||= Race.all.order(designation: :ASC)
- end
-
- def selected_races
- @selected_races ||= begin
- race_params = params[:races]
- return @selected_races = races unless race_params
-
- race_list = race_params.split(',') if race_params
- if race_list
- race_list = race_list.map do |race|
- Race.find_by_slug race
- end
- end
- race_list
- end
- end
-
- def graph
- graphs.each do |graph|
- @graph = graph if graph.slug == params[:graph]
- end
-
- @graph ||= graphs.first
- 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)]
- end
-
- def background
- @background ||= BackgroundPresenter.new(num_of_columns: graph.columns.count)
- end
-
- def race_score_timestamp
- @race_score_timestamp ||= begin
- score = RaceScore.where(school: @school,
- academic_year: @academic_year).order(updated_at: :DESC).first || Today.new
- score.updated_at
- end
- end
-
- def source
- source_param = params[:source]
- sources.each do |source|
- @source = source if source.slug == source_param
- end
-
- @source ||= sources.first
- 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 slice
- slice_param = params[:slice]
- slices.each do |slice|
- @slice = slice if slice.slug == slice_param
- end
-
- @slice ||= slices.first
- end
-
- def slices
- source.slices
- end
-
- def group
- groups.each do |group|
- @group = group if group.slug == params[:group]
- end
-
- @group ||= groups.first
- end
-
- def groups
- @groups = [Analyze::Group::Race.new, Analyze::Group::Grade.new, Analyze::Group::Gender.new]
- end
-
- def selected_grades
- @selected_grades ||= begin
- grade_params = params[:grades]
- return @selected_grades = grades unless grade_params
-
- grade_list = grade_params.split(',') if grade_params
- if grade_list
- grade_list = grade_list.map do |grade|
- grade.to_i
- end
- end
- grade_list
- end
- end
-
- def grades
- @grades ||= SurveyItemResponse.where(school: @school, academic_year: @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 selected_genders
- @selected_genders ||= begin
- gender_params = params[:genders]
- return @selected_genders = genders unless gender_params
-
- gender_list = gender_params.split(',') if gender_params
- if gender_list
- gender_list = gender_list.map do |gender|
- Gender.find_by_designation(gender)
- end
- end
- gender_list
- end
- end
-
- def genders
- @genders ||= Gender.all
+ def index
+ @presenter = Analyze::Presenter.new(params:, school: @school, academic_year: @academic_year)
+ @background ||= BackgroundPresenter.new(num_of_columns: @presenter.graph.columns.count)
end
end
diff --git a/app/helpers/analyze_helper.rb b/app/helpers/analyze_helper.rb
index 7ac34581..e253167c 100644
--- a/app/helpers/analyze_helper.rb
+++ b/app/helpers/analyze_helper.rb
@@ -62,7 +62,7 @@ module AnalyzeHelper
end
def base_url
- analyze_subcategory_link(district: @district, school: @school, academic_year: @academic_year, category: @category,
- subcategory: @subcategory)
+ analyze_subcategory_link(district: @district, school: @school, academic_year: @academic_year, category: @presenter.category,
+ subcategory: @presenter.subcategory)
end
end
diff --git a/app/javascript/controllers/analyze_controller.js b/app/javascript/controllers/analyze_controller.js
index ed11416a..bf290c9b 100644
--- a/app/javascript/controllers/analyze_controller.js
+++ b/app/javascript/controllers/analyze_controller.js
@@ -22,16 +22,17 @@ export default class extends Controller {
"&graph=" +
this.selected_graph(target) +
"&races=" +
- this.selected_races().join(",") +
+ this.selected_items("race").join(",") +
"&genders=" +
- this.selected_genders().join(",") +
+ this.selected_items("gender").join(",") +
+ "&incomes=" +
+ this.selected_items("income").join(",") +
"&grades=" +
- this.selected_grades().join(",");
+ this.selected_items("grade").join(",");
this.go_to(url);
}
-
go_to(location) {
window.location = location;
}
@@ -121,58 +122,34 @@ export default class extends Controller {
return item.id;
})[0];
+ const groups = new Map([
+ ['gender', 'students-by-gender'],
+ ['grade', 'students-by-grade'],
+ ['income', 'students-by-income'],
+ ['race', 'students-by-race']
+ ])
+
if (target.name === 'slice' || target.name === 'group') {
if (selected_slice === 'students-and-teachers') {
return 'students-and-teachers';
- } else if (this.selected_group() === 'race') {
- return 'students-by-race';
- } else if (this.selected_group() === 'gender') {
- return 'students-by-gender';
- } else if (this.selected_group() === 'grade') {
- return 'students-by-grade';
}
+ return groups.get(this.selected_group());
}
return window.graph;
}
- selected_races() {
- let race_checkboxes = [...document.getElementsByName("race-checkbox")]
- let races = race_checkboxes
- .filter((item) => {
- return item.checked;
- })
- .map((item) => {
- return item.id;
- });
-
- return races;
- }
-
- selected_grades() {
- let grade_checkboxes = [...document.getElementsByName("grade-checkbox")]
- let grades = grade_checkboxes
- .filter((item) => {
- return item.checked;
- })
- .map((item) => {
- return item.id.replace('grade-', '');
- });
-
- return grades;
- }
-
- selected_genders() {
- let gender_checkboxes = [...document.getElementsByName("gender-checkbox")]
- let genders = gender_checkboxes
+ selected_items(type) {
+ let checkboxes = [...document.getElementsByName(`${type}-checkbox`)]
+ let items = checkboxes
.filter((item) => {
return item.checked;
})
.map((item) => {
- return item.id.replace('gender-', '');
+ return item.id.replace(`${type}-`, '');
});
- return genders;
+ return items;
}
}
diff --git a/app/models/income.rb b/app/models/income.rb
new file mode 100644
index 00000000..447f8465
--- /dev/null
+++ b/app/models/income.rb
@@ -0,0 +1,7 @@
+class Income < ApplicationRecord
+ scope :by_designation, -> { all.map { |income| [income.designation, income] }.to_h }
+
+ include FriendlyId
+
+ friendly_id :designation, use: [:slugged]
+end
diff --git a/app/models/survey_item_response.rb b/app/models/survey_item_response.rb
index 2387aea2..5e561160 100644
--- a/app/models/survey_item_response.rb
+++ b/app/models/survey_item_response.rb
@@ -9,6 +9,7 @@ class SurveyItemResponse < ActiveRecord::Base
belongs_to :survey_item, counter_cache: true
belongs_to :student, foreign_key: :student_id, optional: true
belongs_to :gender
+ belongs_to :income
has_one :measure, through: :survey_item
@@ -25,4 +26,9 @@ class SurveyItemResponse < ActiveRecord::Base
SurveyItemResponse.where(survey_item: survey_items, school:,
academic_year:, gender:).group(:survey_item).average(:likert_score)
}
+
+ scope :averages_for_income, lambda { |survey_items, school, academic_year, income|
+ SurveyItemResponse.where(survey_item: survey_items, school:,
+ academic_year:, income:).group(:survey_item).average(:likert_score)
+ }
end
diff --git a/app/presenters/analyze/bar_presenter.rb b/app/presenters/analyze/bar_presenter.rb
new file mode 100644
index 00000000..7dc9bdc1
--- /dev/null
+++ b/app/presenters/analyze/bar_presenter.rb
@@ -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
diff --git a/app/presenters/analyze/graph/column/grouped_bar_column_presenter.rb b/app/presenters/analyze/graph/column/grouped_bar_column_presenter.rb
index c06fe18f..ceb425ba 100644
--- a/app/presenters/analyze/graph/column/grouped_bar_column_presenter.rb
+++ b/app/presenters/analyze/graph/column/grouped_bar_column_presenter.rb
@@ -27,10 +27,10 @@ module Analyze
def bars
@bars ||= yearly_scores.map.each_with_index do |yearly_score, index|
year = yearly_score.year
- AnalyzeBarPresenter.new(measure:, academic_year: year,
- score: yearly_score.score,
- x_position: bar_x(index),
- color: bar_color(year))
+ Analyze::BarPresenter.new(measure:, academic_year: year,
+ score: yearly_score.score,
+ x_position: bar_x(index),
+ color: bar_color(year))
end
end
diff --git a/app/presenters/analyze/graph/column/income_column/disadvantaged.rb b/app/presenters/analyze/graph/column/income_column/disadvantaged.rb
new file mode 100644
index 00000000..ecf5469a
--- /dev/null
+++ b/app/presenters/analyze/graph/column/income_column/disadvantaged.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Analyze
+ module Graph
+ module Column
+ module IncomeColumn
+ class Disadvantaged < GroupedBarColumnPresenter
+ include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
+ def label
+ "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
diff --git a/app/presenters/analyze/graph/column/income_column/not_disadvantaged.rb b/app/presenters/analyze/graph/column/income_column/not_disadvantaged.rb
new file mode 100644
index 00000000..8b75d723
--- /dev/null
+++ b/app/presenters/analyze/graph/column/income_column/not_disadvantaged.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Analyze
+ module Graph
+ module Column
+ module IncomeColumn
+ class NotDisadvantaged < GroupedBarColumnPresenter
+ include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
+ def label
+ "Not 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
diff --git a/app/presenters/analyze/graph/column/income_column/score_for_income.rb b/app/presenters/analyze/graph/column/income_column/score_for_income.rb
index 52a23244..3e1140c3 100644
--- a/app/presenters/analyze/graph/column/income_column/score_for_income.rb
+++ b/app/presenters/analyze/graph/column/income_column/score_for_income.rb
@@ -5,17 +5,11 @@ module Analyze
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)
+ scorify(average:, meets_student_threshold: sufficient_student_responses?(academic_year:))
end
def bubble_up_averages(averages:)
@@ -26,9 +20,16 @@ module Analyze
end.remove_blanks.average
end
- def sufficient_student_responses?(academic_year:)
- return false unless measure.subcategory.response_rate(school:, academic_year:).meets_student_threshold?
+ def scorify(average:, meets_student_threshold:)
+ return Score::NIL_SCORE unless meets_student_threshold
+ Score.new(average:,
+ meets_teacher_threshold: false,
+ meets_student_threshold: true,
+ meets_admin_data_threshold: false)
+ end
+
+ def sufficient_student_responses?(academic_year:)
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|
diff --git a/app/presenters/analyze/graph/column/income_column/unknown.rb b/app/presenters/analyze/graph/column/income_column/unknown.rb
new file mode 100644
index 00000000..3583856c
--- /dev/null
+++ b/app/presenters/analyze/graph/column/income_column/unknown.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Analyze
+ module Graph
+ module Column
+ module IncomeColumn
+ class Unknown < GroupedBarColumnPresenter
+ include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
+ 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
diff --git a/app/presenters/analyze/graph/students_by_income.rb b/app/presenters/analyze/graph/students_by_income.rb
new file mode 100644
index 00000000..148b22c1
--- /dev/null
+++ b/app/presenters/analyze/graph/students_by_income.rb
@@ -0,0 +1,40 @@
+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
diff --git a/app/presenters/analyze/group/income.rb b/app/presenters/analyze/group/income.rb
new file mode 100644
index 00000000..21e364b4
--- /dev/null
+++ b/app/presenters/analyze/group/income.rb
@@ -0,0 +1,13 @@
+module Analyze
+ module Group
+ class Income
+ def name
+ 'Income'
+ end
+
+ def slug
+ 'income'
+ end
+ end
+ end
+end
diff --git a/app/presenters/analyze/presenter.rb b/app/presenters/analyze/presenter.rb
new file mode 100644
index 00000000..418bc4dd
--- /dev/null
+++ b/app/presenters/analyze/presenter.rb
@@ -0,0 +1,159 @@
+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 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)]
+ 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(",").map { |gender| Gender.find_by_designation(gender) }.compact
+ end
+ end
+
+ def genders
+ @genders ||= Gender.all
+ end
+
+ def groups
+ @groups = [Analyze::Group::Gender.new, Analyze::Group::Grade.new, Analyze::Group::Income.new,
+ Analyze::Group::Race.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 race_score_timestamp
+ score = RaceScore.where(school: @school,
+ academic_year: @academic_year).order(updated_at: :DESC).first || Today.new
+ score.updated_at
+ 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
+ end
+end
diff --git a/app/presenters/analyze/ui.rb b/app/presenters/analyze/ui.rb
deleted file mode 100644
index cf440140..00000000
--- a/app/presenters/analyze/ui.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module Analyze
- class Ui
- attr_reader :params
- def initialize(params:)
- @params = params
- end
- end
-end
diff --git a/app/presenters/analyze_bar_presenter.rb b/app/presenters/analyze_bar_presenter.rb
deleted file mode 100644
index 32582411..00000000
--- a/app/presenters/analyze_bar_presenter.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-# frozen_string_literal: true
-
-class AnalyzeBarPresenter
- 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
diff --git a/app/services/demographic_loader.rb b/app/services/demographic_loader.rb
index 0de44bd3..345020e8 100644
--- a/app/services/demographic_loader.rb
+++ b/app/services/demographic_loader.rb
@@ -5,6 +5,7 @@ class DemographicLoader
CSV.parse(File.read(filepath), headers: true) do |row|
process_race(row:)
process_gender(row:)
+ process_income(row:)
end
end
@@ -28,6 +29,13 @@ class DemographicLoader
gender = ::Gender.find_or_create_by!(qualtrics_code:, designation:)
gender.save
end
+
+ def self.process_income(row:)
+ designation = row['Income']
+ return unless designation
+
+ Income.find_or_create_by!(designation:)
+ end
end
class KnownRace
diff --git a/app/services/survey_item_values.rb b/app/services/survey_item_values.rb
index 7999201e..581920a3 100644
--- a/app/services/survey_item_values.rb
+++ b/app/services/survey_item_values.rb
@@ -93,6 +93,36 @@ class SurveyItemValues
genders[gender_code]
end
+ def lasid
+ @lasid ||= value_from(pattern: /LASID/i)
+ end
+
+ def raw_income
+ @raw_income ||= value_from(pattern: /Low\s*Income|Raw\s*Income/i)
+ return @raw_income if @raw_income.present?
+
+ return "Unknown" unless disaggregation_data.present?
+
+ disaggregation = disaggregation_data[[lasid, district.name, academic_year.range]]
+ return "Unknown" unless disaggregation.present?
+
+ @raw_income ||= disaggregation.income
+ end
+
+ def income
+ @income ||= value_from(pattern: /^Income$/i)
+ return @income if @income.present?
+
+ @income ||= case raw_income
+ in /Free\s*Lunch|Reduced\s*Lunch|Low\s*Income/i
+ "Economically Disadvantaged - Y"
+ in /Not\s*Eligible/i
+ "Economically Disadvantaged - N"
+ else
+ "Unknown"
+ end
+ end
+
def value_from(pattern:)
output = nil
matches = headers.select do |header|
@@ -107,9 +137,9 @@ class SurveyItemValues
def to_a
copy_likert_scores_from_variant_survey_items
headers.select(&:present?)
- .reject { |key, _value| key.start_with? "Q" }
- .reject { |key, _value| key.end_with? "-1" }
- .map { |header| row[header] }
+ .reject { |key, _value| key.start_with? "Q" }
+ .reject { |key, _value| key.end_with? "-1" }
+ .map { |header| row[header] }
end
def duration
@@ -122,17 +152,17 @@ class SurveyItemValues
def respondent_type
return :teacher if headers
- .filter(&:present?)
- .filter { |header| header.start_with? "t-" }.count > 0
+ .filter(&:present?)
+ .filter { |header| header.start_with? "t-" }.count > 0
:student
end
def survey_type
survey_item_ids = headers
- .filter(&:present?)
- .reject { |header| header.end_with?("-1") }
- .filter { |header| header.start_with?("t-", "s-") }
+ .filter(&:present?)
+ .reject { |header| header.end_with?("-1") }
+ .filter { |header| header.start_with?("t-", "s-") }
SurveyItem.survey_type(survey_item_ids:)
end
diff --git a/app/services/survey_responses_data_loader.rb b/app/services/survey_responses_data_loader.rb
index 2d23bdbb..8f5f2a3a 100644
--- a/app/services/survey_responses_data_loader.rb
+++ b/app/services/survey_responses_data_loader.rb
@@ -7,12 +7,13 @@ class SurveyResponsesDataLoader
headers_array = CSV.parse(headers).first
genders = Gender.gender_hash
schools = School.school_hash
+ incomes = Income.by_designation
all_survey_items = survey_items(headers:)
file.lazy.each_slice(500) do |lines|
survey_item_responses = CSV.parse(lines.join, headers:).map do |row|
process_row(row: SurveyItemValues.new(row:, headers: headers_array, genders:, survey_items: all_survey_items, schools:),
- rules:)
+ rules:, incomes:)
end
SurveyItemResponse.import survey_item_responses.compact.flatten, batch_size: 500
end
@@ -24,6 +25,7 @@ class SurveyResponsesDataLoader
headers_array = CSV.parse(headers).first
genders = Gender.gender_hash
schools = School.school_hash
+ incomes = Income.by_designation
all_survey_items = survey_items(headers:)
survey_item_responses = []
@@ -34,7 +36,7 @@ class SurveyResponsesDataLoader
CSV.parse(line, headers:).map do |row|
survey_item_responses << process_row(row: SurveyItemValues.new(row:, headers: headers_array, genders:, survey_items: all_survey_items, schools:),
- rules:)
+ rules:, incomes:)
end
row_count += 1
@@ -50,7 +52,7 @@ class SurveyResponsesDataLoader
private
- def self.process_row(row:, rules:)
+ def self.process_row(row:, rules:, incomes:)
return unless row.dese_id?
return unless row.school.present?
@@ -58,32 +60,32 @@ class SurveyResponsesDataLoader
return if rule.new(row:).skip_row?
end
- # byebug if row.response_id == 'butler_student_survey_response_1'
- process_survey_items(row:)
+ process_survey_items(row:, incomes:)
end
- def self.process_survey_items(row:)
+ def self.process_survey_items(row:, incomes:)
row.survey_items.map do |survey_item|
likert_score = row.likert_score(survey_item_id: survey_item.survey_item_id) || next
unless likert_score.valid_likert_score?
- puts "Response ID: #{row.response_id}, Likert score: #{likert_score} rejected" unless likert_score == 'NA'
+ puts "Response ID: #{row.response_id}, Likert score: #{likert_score} rejected" unless likert_score == "NA"
next
end
response = row.survey_item_response(survey_item:)
- create_or_update_response(survey_item_response: response, likert_score:, row:, survey_item:)
+ create_or_update_response(survey_item_response: response, likert_score:, row:, survey_item:, incomes:)
end.compact
end
- def self.create_or_update_response(survey_item_response:, likert_score:, row:, survey_item:)
+ def self.create_or_update_response(survey_item_response:, likert_score:, row:, survey_item:, incomes:)
gender = row.gender
grade = row.grade
+ income = incomes[row.income]
if survey_item_response.present?
- survey_item_response.update!(likert_score:, grade:, gender:, recorded_date: row.recorded_date)
+ survey_item_response.update!(likert_score:, grade:, gender:, recorded_date: row.recorded_date, income:)
[]
else
SurveyItemResponse.new(response_id: row.response_id, academic_year: row.academic_year, school: row.school, survey_item:,
- likert_score:, grade:, gender:, recorded_date: row.recorded_date)
+ likert_score:, grade:, gender:, recorded_date: row.recorded_date, income:)
end
end
@@ -94,7 +96,7 @@ class SurveyResponsesDataLoader
def self.get_survey_item_ids_from_headers(headers:)
CSV.parse(headers).first
.filter(&:present?)
- .filter { |header| header.start_with? 't-', 's-' }
+ .filter { |header| header.start_with? "t-", "s-" }
end
private_class_method :process_row
diff --git a/app/views/analyze/_checkboxes.html.erb b/app/views/analyze/_checkboxes.html.erb
new file mode 100644
index 00000000..d64d7e6b
--- /dev/null
+++ b/app/views/analyze/_checkboxes.html.erb
@@ -0,0 +1,17 @@
+
+
+ <%= @presenter.graph.slug == 'students-and-teachers' || @presenter.source.slug == 'all-data' ? "disabled" : "" %>
+ <%= @presenter.group.slug == name ? "" : "hidden" %>>
+
+
+
diff --git a/app/views/analyze/_data_filters.html.erb b/app/views/analyze/_data_filters.html.erb
index e7011a36..0cba336f 100644
--- a/app/views/analyze/_data_filters.html.erb
+++ b/app/views/analyze/_data_filters.html.erb
@@ -1,7 +1,7 @@
- <% @sources.each do |source| %>
+ <% @presenter.sources.each do |source| %>
>
+ <%= source.slug == @presenter.source.slug ? "checked" : "" %>>
<% source.slices.each do | slice | %>
@@ -20,7 +20,7 @@
name="slice"
value="<%= base_url %>"
data-action="click->analyze#refresh"
- <%= slice.slug == @slice.slug ? "checked" : "" %>
+ <%= slice.slug == @presenter.slice.slug ? "checked" : "" %>
<%= slice.slug == "all-data" ? "hidden" : "" %>>