feat: Add income table to the database. Add seeder for income. Add a reference to income from survey item response. Update the loader to import income data from the survey response csv. Refactor analyze controller to extract presenter. Add corresponding specs. Add income graph to analyze page

This commit is contained in:
rebuilt 2023-06-20 14:50:33 -07:00
parent 7e1be4860c
commit 4f035f6a63
45 changed files with 1494 additions and 856 deletions

View file

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

View file

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

View file

@ -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
selected_items(type) {
let checkboxes = [...document.getElementsByName(`${type}-checkbox`)]
let items = checkboxes
.filter((item) => {
return item.checked;
})
.map((item) => {
return item.id;
return item.id.replace(`${type}-`, '');
});
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
.filter((item) => {
return item.checked;
})
.map((item) => {
return item.id.replace('gender-', '');
});
return genders;
return items;
}
}

7
app/models/income.rb Normal file
View file

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

View file

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

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

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

View file

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

View file

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

View file

@ -0,0 +1,43 @@
module Analyze
module Graph
module Column
module IncomeColumn
module ScoreForIncome
def score(year_index)
academic_year = academic_years[year_index]
averages = SurveyItemResponse.averages_for_income(measure.student_survey_items, school, academic_year,
income)
average = bubble_up_averages(averages:).round(2)
scorify(average:, meets_student_threshold: sufficient_student_responses?(academic_year:))
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 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|
count[1] >= 10
end
end
end
end
end
end
end

View file

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

View file

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

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

View file

@ -1,8 +0,0 @@
module Analyze
class Ui
attr_reader :params
def initialize(params:)
@params = params
end
end
end

View file

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

View file

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

View file

@ -99,7 +99,9 @@ class SurveyItemValues
end
def raw_income
@raw_income ||= value_from(pattern: /Income|Low\s*Income/i)
@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]]
@ -109,6 +111,9 @@ class SurveyItemValues
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'

View file

@ -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,10 +60,10 @@ class SurveyResponsesDataLoader
return if rule.new(row:).skip_row?
end
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
@ -70,19 +72,20 @@ class SurveyResponsesDataLoader
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

View file

@ -0,0 +1,17 @@
<div class="d-flex align-items-center mx-5">
<input
id="<%= id %>"
class="m-3 <%= name %>-checkbox form-check-input"
type="checkbox"
name="<%= name %>-checkbox"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= selected_items.include?(item) ? "checked" : "" %>
<%= @presenter.graph.slug == 'students-and-teachers' || @presenter.source.slug == 'all-data' ? "disabled" : "" %>
<%= @presenter.group.slug == name ? "" : "hidden" %>>
<label for="<%= id %>"
<%= @presenter.group.slug == name ? "" : "hidden" %>>
<%= label_text %>
</label>
</div>

View file

@ -1,7 +1,7 @@
<h3 class="sub-header-4 mt-5">Data Filters</h3>
<div class="bg-gray p-3" data-controller="analyze">
<% @sources.each do |source| %>
<% @presenter.sources.each do |source| %>
<input type="radio"
id="<%= source.slug %>"
@ -9,7 +9,7 @@
name="source"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= source.slug == @source.slug ? "checked" : "" %>>
<%= source.slug == @presenter.source.slug ? "checked" : "" %>>
<label for="<%= source.slug %>"><%= source.to_s %></label>
<% 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" : "" %>>
<label for="<%= slice.slug %>"
@ -34,8 +34,8 @@
</div>
<script>
window.source = "<%= @source.slug %>";
window.slice = "<%= @slice.slug %>";
window.group = "<%= @group.slug %>";
window.graph = "<%= @graph.slug %>";
window.source = "<%= @presenter.source.slug %>";
window.slice = "<%= @presenter.slice.slug %>";
window.group = "<%= @presenter.group.slug %>";
window.graph = "<%= @presenter.graph.slug %>";
</script>

View file

@ -2,12 +2,12 @@
<p>Select a category & subcategory to analyze measure-level results</p>
<select id="select-category" class="mx-3 form-select" data-id="category-dropdown" data-action="analyze#refresh">
<% categories.each do |category| %>
<option value="<%= analyze_category_link(district: district, school: school, academic_year: academic_year, category: category) %>" <%= category.id == @category.id ? "selected": "" %>><%= "#{category.category_id}: #{category.name}" %></option>
<option value="<%= analyze_category_link(district: district, school: school, academic_year: academic_year, category: category) %>" <%= category.id == @presenter.category.id ? "selected": "" %>><%= "#{category.category_id}: #{category.name}" %></option>
<% end %>
</select>
<select id="select-subcategory" class="mx-3 form-select mt-3" data-id="subcategory-dropdown" data-action="analyze#refresh">
<% subcategories.each do |subcategory| %>
<option value="<%= analyze_subcategory_link(district: district, school: school, academic_year: academic_year, category: category, subcategory: subcategory) %>" <%= subcategory.subcategory_id == @subcategory.subcategory_id ? "selected": "" %>>
<option value="<%= analyze_subcategory_link(district: district, school: school, academic_year: academic_year, category: category, subcategory: subcategory) %>" <%= subcategory.subcategory_id == @presenter.subcategory.subcategory_id ? "selected": "" %>>
<%= "#{subcategory.subcategory_id}: #{subcategory.name}" %>
</option>
<% end %>

View file

@ -1,67 +1,23 @@
<select id="select-group" name="group" class="mx-4 form-select" data-id="group-dropdown" data-action="analyze#refresh">
<% @groups.each do |group| %>
<option id="<%= group.slug %>" name="group-option" value="<%= base_url %>" <%= group.slug == @group.slug ? "Selected": "" %>><%= group.name %> </option>
<% @presenter.groups.each do |group| %>
<option id="<%= group.slug %>" name="group-option" value="<%= base_url %>" <%= group.slug == @presenter.group.slug ? "Selected": "" %>><%= group.name %> </option>
<% end %>
</select>
<p class="sub-header-5 mx-4 mt-3 font-size-14"> Select a group </p>
<% @races.each do |race| %>
<div class="d-flex align-items-center mx-5">
<input
id="<%= race.slug %>"
class="m-3 race-checkbox form-check-input"
type="checkbox"
name="race-checkbox"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= @selected_races.map(&:slug).include?(race.slug) ? "checked" : "" %>
<%= @graph.slug == 'students-and-teachers' || @source.slug == 'all-data' ? "disabled" : "" %>
<%= @group.slug == 'race' ? "" : "hidden" %>>
<label for="<%= race.qualtrics_code %>"
<%= @group.slug == 'race' ? "" : "hidden" %>>
<%= race.designation %>
</label>
</div>
<% @presenter.races.each do |race| %>
<%= render(partial: "checkboxes", locals: {id: "race-#{race.slug}", item: race, selected_items: @presenter.selected_races, name: "race", label_text: race.designation}) %>
<% end %>
<% @grades.each do |grade| %>
<div class="d-flex align-items-center mx-5">
<input
id="grade-<%= grade %>"
class="m-3 grade-checkbox form-check-input"
type="checkbox"
name="grade-checkbox"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= @selected_grades.include?(grade) ? "checked" : "" %>
<%= @graph.slug == 'students-and-teachers' || @source.slug == 'all-data' ? "disabled" : "" %>
<%= @group.slug == 'grade' ? "" : "hidden" %>>
<label for="grade-<%= grade %>"
<%= @group.slug == 'grade' ? "" : "hidden" %>>
<%= grade %>
</label>
</div>
<% @presenter.grades.each do |grade| %>
<%= render(partial: "checkboxes", locals: {id: "grade-#{grade}", item: grade, selected_items: @presenter.selected_grades, name: "grade", label_text: grade}) %>
<% end %>
<% @genders.each do |gender| %>
<div class="d-flex align-items-center mx-5">
<input
id="gender-<%= gender.designation %>"
class="m-3 gender-checkbox form-check-input"
type="checkbox"
name="gender-checkbox"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= @selected_genders.include?(gender) ? "checked" : "" %>
<%= @graph.slug == 'students-and-teachers' || @source.slug == 'all-data' ? "disabled" : "" %>
<%= @group.slug == 'gender' ? "" : "hidden" %>>
<label for="gender-<%= gender %>"
<%= @group.slug == 'gender' ? "" : "hidden" %>>
<%= gender.designation %>
</label>
</div>
<% @presenter.genders.each do |gender| %>
<%= render(partial: "checkboxes", locals: {id: "gender-#{gender.designation}", item: gender, selected_items: @presenter.selected_genders, name: "gender", label_text: gender.designation}) %>
<% end %>
<% @presenter.incomes.each do |income| %>
<%= render(partial: "checkboxes", locals: {id: "income-#{income.slug}", item: income, selected_items: @presenter.selected_incomes, name: "income", label_text: income.designation}) %>
<% end %>

View file

@ -1,9 +1,9 @@
<svg width="100%" height="<%= svg_height %>">
<%= render partial: "graph_background", locals: {background: @background} %>
<% number_of_columns = @graph.columns.length %>
<% @graph.columns.each_with_index do |column, index| %>
<% p = column.new(measure: measure, school: @school, academic_years: @selected_academic_years, position: index , number_of_columns:) %>
<% number_of_columns = @presenter.graph.columns.length %>
<% @presenter.graph.columns.each_with_index do |column, index| %>
<% p = column.new(measure: measure, school: @school, academic_years: @presenter.selected_academic_years, position: index , number_of_columns:) %>
<%= render partial: "grouped_bar_column", locals: {column: p} %>
<% end %>

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 493 B

Before After
Before After

View file

@ -10,7 +10,7 @@
data-action="click->analyze#refresh"
<% empty_dataset = empty_dataset?(measures: measures, school: school, academic_year: year) %>
<% empty_survey_dataset = empty_survey_dataset?(measures: measures, school: school, academic_year: year) %>
<% if @graph.slug == 'all-data' %>
<% if graph.slug == 'all-data' %>
<%= empty_dataset ? "disabled" : "" %>
<% else %>
<%= empty_survey_dataset ? "disabled" : "" %>
@ -18,13 +18,13 @@
>
<label class="px-3" for="<%= year.range %>"><%= year.range %></label><br>
<div class="bg-color-blue px-3" style="width:20px;height:20px;background-color:<%= colors[index] %>;"></div>
<% if @graph.slug == 'all-data' && empty_dataset %>
<% if graph.slug == 'all-data' && empty_dataset %>
<i class="fa-solid fa-circle-exclamation px-3"
data-bs-toggle="popover" data-bs-placement="right"
data-bs-content="No admin data OR teacher and student survey response rates below <%= ResponseRateCalculator::TEACHER_RATE_THRESHOLD %>%">
</i>
<% end %>
<% if @graph.slug != 'all-data' && empty_survey_dataset %>
<% if graph.slug != 'all-data' && empty_survey_dataset %>
<i class="fa-solid fa-circle-exclamation px-3"
data-bs-toggle="popover" data-bs-placement="right"
data-bs-content="Teacher and student survey response rates below <%= ResponseRateCalculator::TEACHER_RATE_THRESHOLD %>%">

View file

@ -3,19 +3,19 @@
<% end %>
<div class="graph-content">
<div class="breadcrumbs sub-header-4">
<%= @category.category_id %>:<%= @category.name %> > <%= @subcategory.subcategory_id %>:<%= @subcategory.name %>
<%= @presenter.category.category_id %>:<%= @presenter.category.name %> > <%= @presenter.subcategory.subcategory_id %>:<%= @presenter.subcategory.name %>
</div>
<hr>
</div>
<div class="d-flex flex-row pt-5 row">
<div class="d-flex flex-column flex-grow-6 bg-color-white col-3 px-5" data-controller="analyze">
<%= render partial: "focus_area", locals: {categories: @categories, district: @district, school: @school, academic_year: @academic_year, category: @category, subcategories: @subcategories} %>
<%= render partial: "school_years", locals: {available_academic_years: @available_academic_years, selected_academic_years: @selected_academic_years, district: @district, school: @school, academic_year: @academic_year, category: @category, subcategory: @subcategory, measures: @measures} %>
<%= render partial: "data_filters", locals: {district: @district, school: @school, academic_year: @academic_year, category: @category, subcategory: @subcategory} %>
<%= render partial: "focus_area", locals: {categories: @presenter.categories, district: @district, school: @school, academic_year: @academic_year, category: @presenter.category, subcategories: @presenter.subcategories} %>
<%= render partial: "school_years", locals: {available_academic_years: @presenter.academic_years, selected_academic_years: @presenter.selected_academic_years, district: @district, school: @school, academic_year: @academic_year, category: @presenter.category, subcategory: @presenter.subcategory, measures: @presenter.measures, graph: @presenter.graph} %>
<%= render partial: "data_filters", locals: {district: @district, school: @school, academic_year: @academic_year, category: @presenter.category, subcategory: @presenter.subcategory} %>
</div>
<% cache [@subcategory, @school, @selected_academic_years, @graph, @selected_races, @race_score_timestamp, @selected_grades, @grades, @selected_genders, @genders] do %>
<% cache [@presenter.subcategory, @school, @presenter.selected_academic_years, @presenter.graph, @presenter.selected_races, @presenter.race_score_timestamp, @presenter.selected_grades, @presenter.grades, @presenter.selected_genders, @presenter.genders] do %>
<div class="bg-color-white flex-grow-1 col-9">
<% @measures.each do |measure| %>
<% @presenter.measures.each do |measure| %>
<section class="mb-6">
<p class="construct-id">Measure <%= measure.measure_id %></p>
<h2> <%= measure.name %> </h2>