mirror of
https://github.com/edcommonwealth/sqm-dashboards.git
synced 2026-03-07 13:38:18 -08:00
feat: Update cleaner and uploader to load gender data from parent surveys
Implement graph views for parent gender data
This commit is contained in:
parent
7ebe8234cb
commit
9d8543afae
16 changed files with 262 additions and 98 deletions
|
|
@ -3,4 +3,5 @@ class Parent < ApplicationRecord
|
|||
has_many :parent_languages
|
||||
has_and_belongs_to_many :languages, join_table: :parent_languages
|
||||
has_and_belongs_to_many :races, join_table: :parent_races
|
||||
has_and_belongs_to_many :genders, join_table: :parent_genders
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,9 +57,18 @@ class SurveyItemResponse < ActiveRecord::Base
|
|||
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)
|
||||
|
||||
SurveyItemResponse.joins([parent: :races]).where(races: { id: }, survey_item: survey_items, school:, academic_year:).group(:survey_item).having("count(*) >= 10").average(:likert_score)
|
||||
}
|
||||
|
||||
scope :averages_for_parent_gender, lambda { |survey_items, school, academic_year, gender|
|
||||
id = if gender.instance_of? ::Gender
|
||||
gender.id
|
||||
else
|
||||
gender.map(&:id)
|
||||
end
|
||||
|
||||
SurveyItemResponse.joins([parent: :genders]).where(genders: { id: }, survey_item: survey_items, school:, academic_year:).group(:survey_item).having("count(*) >= 10").where.not(parent: nil).average(:likert_score)
|
||||
}
|
||||
|
||||
scope :averages_for_language, lambda { |survey_items, school, academic_year, designations|
|
||||
|
|
|
|||
78
app/presenters/analyze/graph/column/parent/base.rb
Normal file
78
app/presenters/analyze/graph/column/parent/base.rb
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Parent
|
||||
class Base
|
||||
def label
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def basis
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def show_irrelevancy_message?(construct:)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?(construct:, school:, academic_years:)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def insufficiency_message
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def score(construct:, school:, academic_year:)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def type
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def n_size(construct:, school:, academic_year:)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def show_insufficient_data_message?(construct:, school:, academic_years:)
|
||||
false
|
||||
end
|
||||
|
||||
def basis
|
||||
"parent surveys"
|
||||
end
|
||||
|
||||
def type
|
||||
:parent
|
||||
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
|
||||
39
app/presenters/analyze/graph/column/parent/gender.rb
Normal file
39
app/presenters/analyze/graph/column/parent/gender.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
module Column
|
||||
module Parent
|
||||
class Gender < Base
|
||||
attr_reader :genders, :label
|
||||
|
||||
def initialize(genders:, label:, show_irrelevancy_message:)
|
||||
@genders = genders
|
||||
@label = label
|
||||
@show_irrelevancy_message = show_irrelevancy_message
|
||||
end
|
||||
|
||||
def n_size(construct:, school:, academic_year:)
|
||||
id = if genders.instance_of? ::Gender
|
||||
genders.id
|
||||
else
|
||||
genders.map(&:id)
|
||||
end
|
||||
SurveyItemResponse.joins([parent: :genders]).where(genders: { id: }, 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_gender(construct.parent_survey_items, school, academic_year, genders)
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,7 +4,7 @@ module Analyze
|
|||
module Graph
|
||||
module Column
|
||||
module Parent
|
||||
class Language < ColumnBase
|
||||
class Language < Base
|
||||
attr_reader :language, :label
|
||||
|
||||
def initialize(languages:, label:, show_irrelevancy_message:)
|
||||
|
|
@ -13,18 +13,6 @@ module Analyze
|
|||
@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:)
|
||||
SurveyItemResponse.joins([parent: :languages]).where(languages: { designation: designations }, survey_item: construct.parent_survey_items, school:, academic_year:).select(:parent_id).distinct.count
|
||||
end
|
||||
|
|
@ -44,29 +32,6 @@ module Analyze
|
|||
def designations
|
||||
language.map(&:designation)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ module Analyze
|
|||
module Graph
|
||||
module Column
|
||||
module Parent
|
||||
class Race < ColumnBase
|
||||
class Race < Base
|
||||
attr_reader :race, :label
|
||||
|
||||
def initialize(races:, label:, show_irrelevancy_message:)
|
||||
|
|
@ -13,18 +13,6 @@ module Analyze
|
|||
@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
|
||||
|
|
@ -44,29 +32,6 @@ module Analyze
|
|||
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
|
||||
|
|
|
|||
42
app/presenters/analyze/graph/parents_by_gender.rb
Normal file
42
app/presenters/analyze/graph/parents_by_gender.rb
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analyze
|
||||
module Graph
|
||||
class ParentsByGender
|
||||
def to_s
|
||||
"Parents by Gender"
|
||||
end
|
||||
|
||||
def slug
|
||||
"parents-by-gender"
|
||||
end
|
||||
|
||||
def columns
|
||||
[].tap do |array|
|
||||
Gender.all.each do |gender|
|
||||
label = if gender.designation.match(/\or\s/i)
|
||||
[gender.designation.split("or").first.squish]
|
||||
else
|
||||
gender.designation.split(" ", 2).compact
|
||||
end
|
||||
|
||||
array << Analyze::Graph::Column::Parent::Gender.new(genders: gender, label:, show_irrelevancy_message: false)
|
||||
end
|
||||
array << Analyze::Graph::Column::Parent::Gender.new(genders: Gender.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: "Student Gender", slug: "student-gender", graph: self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -166,7 +166,8 @@ module Analyze
|
|||
"students-by-sped" => Analyze::Graph::StudentsBySped.new(speds: selected_speds),
|
||||
"students-by-ell" => Analyze::Graph::StudentsByEll.new(ells: selected_ells),
|
||||
"parents-by-race" => Analyze::Graph::ParentsByRace.new,
|
||||
"parents-by-language" => Analyze::Graph::ParentsByLanguage.new }
|
||||
"parents-by-language" => Analyze::Graph::ParentsByLanguage.new,
|
||||
"parents-by-gender" => Analyze::Graph::ParentsByGender.new }
|
||||
end
|
||||
|
||||
# The last item will per slice type will be selected as the default slice
|
||||
|
|
@ -183,7 +184,8 @@ module Analyze
|
|||
"students-by-sped" => nil,
|
||||
"students-by-ell" => nil,
|
||||
"parents-by-race" => Analyze::Graph::ParentsByRace.new,
|
||||
"parents-by-language" => Analyze::Graph::ParentsByLanguage.new }
|
||||
"parents-by-language" => Analyze::Graph::ParentsByLanguage.new,
|
||||
"parents-by-gender" => Analyze::Graph::ParentsByGender.new }
|
||||
end
|
||||
|
||||
def graphs
|
||||
|
|
|
|||
|
|
@ -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", "Declared Races of Children from Parents"])).to_a
|
||||
"Race", "Gender", "Raw Housing Status", "Housing Status", "Home Language", "Home Languages", "Declared Races of Children from Parents", "Declared Genders 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?",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class SurveyItemValues
|
|||
row["Housing Status"] = housing
|
||||
row["Home Languages"] = languages.join(",")
|
||||
row["Declared Races of Children from Parents"] = races_of_children.join(",")
|
||||
row["Declared Genders of Children from Parents"] = genders_of_children.join(",")
|
||||
end
|
||||
|
||||
def normalize_headers(headers:)
|
||||
|
|
@ -146,6 +147,25 @@ class SurveyItemValues
|
|||
end
|
||||
end
|
||||
|
||||
def genders_of_children
|
||||
@genders_of_children ||= [].tap do |gender_codes|
|
||||
matches = headers.select do |header|
|
||||
# Explanation:
|
||||
# ^: Start of the string.
|
||||
# (?!.*text): Negative lookahead — ensures that the word text does not appear anywhere in the string.
|
||||
# .*?: Lazily match any characters (to get to the word gender).
|
||||
# gender: Match the word gender
|
||||
header.match(/^(?!.*text).*?gender/i)
|
||||
end
|
||||
|
||||
matches.each do |match|
|
||||
code = row[match]&.strip
|
||||
gender_codes << Gender.qualtrics_code_from(code).to_i unless code.nil?
|
||||
end
|
||||
gender_codes << 99 if gender_codes.empty?
|
||||
end.uniq.sort
|
||||
end
|
||||
|
||||
def races
|
||||
@races ||= begin
|
||||
race_codes ||= self_report = value_from(pattern: /Race\s*self\s*report/i)
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ class SurveyResponsesDataLoader
|
|||
tmp_races = row.races_of_children.map { |race| races[race] }.reject(&:nil?)
|
||||
parent.races.concat(tmp_races)
|
||||
|
||||
parent.genders.delete_all
|
||||
tmp_genders = row.genders_of_children.map { |race| genders[race] }.reject(&:nil?)
|
||||
parent.genders.concat(tmp_genders)
|
||||
|
||||
parent.housing = housings[row.housing] if row.housing.present?
|
||||
parent.save
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue