ECP-125 feat:

Update cleaner to read parent races.
Update uploader to set races for parents.
Add race graphs to analyze page.  Show both measure and scale level graphs
This commit is contained in:
rebuilt 2025-05-15 18:07:28 -07:00
parent 76f2467b97
commit 5b00454a1b
15 changed files with 322 additions and 39 deletions

View file

@ -2,4 +2,5 @@ class Parent < ApplicationRecord
belongs_to :housing, optional: true
has_many :parent_languages
has_and_belongs_to_many :languages, join_table: :parent_languages
has_and_belongs_to_many :races, join_table: :parent_races
end

View file

@ -51,6 +51,17 @@ class SurveyItemResponse < ActiveRecord::Base
).where("student_races.race_id": race.id).group(:survey_item).having("count(*) >= 10").average(:likert_score)
}
scope :averages_for_parent_race, lambda { |survey_items, school, academic_year, race|
id = if race.instance_of? ::Race
race.id
else
race.map(&:id)
end
SurveyItemResponse.joins("JOIN parent_races on survey_item_responses.parent_id = parent_races.parent_id JOIN parents on parents.id = parent_races.parent_id").where(
school:, academic_year:, survey_item: survey_items
).where("parent_races.race_id": id).group(:survey_item).having("count(*) >= 10").average(:likert_score)
}
scope :averages_for_language, lambda { |survey_items, school, academic_year, designations|
SurveyItemResponse.joins([parent: :languages]).where(languages: { designation: designations }, survey_item: survey_items, school:, academic_year:).group(:survey_item).average(:likert_score)
}

View file

@ -17,10 +17,6 @@ module Analyze
"parent surveys"
end
def show_irrelevancy_message?(construct:)
false
end
def show_insufficient_data_message?(construct:, school:, academic_years:)
false
end

View file

@ -0,0 +1,74 @@
# frozen_string_literal: true
module Analyze
module Graph
module Column
module Parent
class Race < ColumnBase
attr_reader :race, :label
def initialize(races:, label:, show_irrelevancy_message:)
@race = races
@label = label
@show_irrelevancy_message = show_irrelevancy_message
end
def basis
"parent surveys"
end
def show_insufficient_data_message?(construct:, school:, academic_years:)
false
end
def type
:parent
end
def n_size(construct:, school:, academic_year:)
designation = if race.instance_of? ::Race
race.designation
else
race.map(&:designation)
end
SurveyItemResponse.joins([parent: :races]).where(races: { designation: }, survey_item: construct.parent_survey_items, school:, academic_year:).select(:parent_id).distinct.count
end
def score(construct:, school:, academic_year:)
return Score::NIL_SCORE if n_size(construct:, school:, academic_year:) < 10
averages = SurveyItemResponse.averages_for_parent_race(construct.parent_survey_items, school, academic_year, race)
average = bubble_up_averages(construct:, averages:).round(2)
Score.new(average:,
meets_teacher_threshold: false,
meets_student_threshold: true,
meets_admin_data_threshold: false)
end
def bubble_up_averages(construct:, averages:)
name = construct.class.name.downcase
send("#{name}_bubble_up_averages", construct:, averages:)
end
def measure_bubble_up_averages(construct:, averages:)
construct.parent_scales.map do |scale|
scale_bubble_up_averages(construct: scale, averages:)
end.remove_blanks.average
end
def scale_bubble_up_averages(construct:, averages:)
construct.survey_items.map do |survey_item|
averages[survey_item]
end.remove_blanks.average
end
def show_irrelevancy_message?(construct:)
return false if @show_irrelevancy_message == false
construct.survey_items.parent_survey_items.count.zero?
end
end
end
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
module Analyze
module Graph
class ParentsByRace
def to_s
"Parents by Race"
end
def slug
"parents-by-race"
end
def columns
[].tap do |array|
Race.all.each do |race|
label = if race.designation.match(/\or\s/i)
[race.designation.split("or").first.squish]
else
race.designation.split(" ", 2).compact
end
array << Analyze::Graph::Column::Parent::Race.new(races: race, label:, show_irrelevancy_message: false)
end
array << Analyze::Graph::Column::Parent::Race.new(races: Race.all, label: ["All Parent"], show_irrelevancy_message: true)
end
end
def source
Analyze::Source::SurveyData.new(slices: nil, graph: self)
end
def slice
Analyze::Slice::ParentsByGroup.new(graph: self)
end
def group
Analyze::Group::Base.new(name: "Race", slug: "race", graph: self)
end
end
end
end

View file

@ -139,7 +139,7 @@ module Analyze
def show_scale_level_graphs?(measure:)
return false unless measure.includes_parent_survey_items?
["parents-by-language"].include?(requested_graphs)
requested_graphs.starts_with? "parents-by"
end
def sources
@ -162,7 +162,8 @@ module Analyze
"students-by-income" => Analyze::Graph::StudentsByIncome.new(incomes: selected_incomes),
"students-by-sped" => Analyze::Graph::StudentsBySped.new(speds: selected_speds),
"students-by-ell" => Analyze::Graph::StudentsByEll.new(ells: selected_ells),
"parents-by-language" => Analyze::Graph::ParentsByLanguage.new }
"parents-by-language" => Analyze::Graph::ParentsByLanguage.new,
"parents-by-race" => Analyze::Graph::ParentsByRace.new }
end
def scale_level_graphs
@ -175,7 +176,8 @@ module Analyze
"students-by-income" => nil,
"students-by-sped" => nil,
"students-by-ell" => nil,
"parents-by-language" => Analyze::Graph::ParentsByLanguage.new }
"parents-by-language" => Analyze::Graph::ParentsByLanguage.new,
"parents-by-race" => Analyze::Graph::ParentsByRace.new }
end
def graphs

View file

@ -79,7 +79,7 @@ class Cleaner
headers = headers.to_set
headers = headers.merge(Set.new(["Raw Income", "Income", "Raw ELL", "ELL", "Raw SpEd", "SpEd", "Progress Count",
"Race", "Gender", "Raw Housing Status", "Housing Status", "Home Language", "Home Languages"])).to_a
"Race", "Gender", "Raw Housing Status", "Housing Status", "Home Language", "Home Languages", "Declared Races of Children from Parents"])).to_a
filtered_headers = include_all_headers(headers:)
filtered_headers = remove_unwanted_headers(headers: filtered_headers)
log_headers = (filtered_headers + ["Valid Duration?", "Valid Progress?", "Valid Grade?",

View file

@ -11,21 +11,26 @@ class SurveyItemValues
@academic_years = academic_years
copy_likert_scores_from_variant_survey_items
row["Income"] = income
row["Raw Income"] = raw_income
row["Raw ELL"] = raw_ell
row["ELL"] = ell
row["Raw SpEd"] = raw_sped
row["SpEd"] = sped
row["Progress Count"] = progress
row["Race"] ||= races.join(",")
row["Gender"] ||= gender
if survey_type == :student
row["Income"] = income
row["Raw Income"] = raw_income
row["Raw ELL"] = raw_ell
row["ELL"] = ell
row["Raw SpEd"] = raw_sped
row["SpEd"] = sped
row["Progress Count"] = progress
row["Race"] ||= races.join(",")
row["Gender"] ||= gender
copy_data_to_main_column(main: /Race/i, secondary: /Race Secondary|Race-1/i)
copy_data_to_main_column(main: /Gender/i, secondary: /Gender Secondary|Gender-1/i)
end
return unless survey_type == :parent
row["Raw Housing Status"] = raw_housing
row["Housing Status"] = housing
row["Home Languages"] = languages.join(",")
copy_data_to_main_column(main: /Race/i, secondary: /Race Secondary|Race-1/i)
copy_data_to_main_column(main: /Gender/i, secondary: /Gender Secondary|Gender-1/i)
row["Declared Races of Children from Parents"] = races_of_children.join(",")
end
def normalize_headers(headers:)
@ -170,6 +175,21 @@ class SurveyItemValues
end
end
def races_of_children
race_codes = []
matches = headers.select do |header|
header.match(/^Race$|^Race-\d+/i)
end
matches.each do |match|
row[match]&.split(",")&.each do |item|
race_codes << item&.strip&.to_i
end
end
Race.normalize_race_list(race_codes.sort)
end
def lasid
@lasid ||= value_from(pattern: /LASID/i) || ""
end

View file

@ -113,6 +113,11 @@ class SurveyResponsesDataLoader
tmp_languages = row.languages.map { |language| languages[language] }.reject(&:nil?)
parent.languages.delete_all
parent.languages.concat(tmp_languages)
parent.races.delete_all
tmp_races = row.races_of_children.map { |race| races[race] }.reject(&:nil?)
parent.races.concat(tmp_races)
parent.housing = housings[row.housing] if row.housing.present?
parent.save
end