feat: create a parents by language graph

Update demographics table with lanugage options

Create a lanugage table to hold the new languages

Update the demographic loader to input languages into the database

Update the cleaner to read the language column

Update the parent table to hold a reference to a language

Update the data uploader script to read the language from the csv and update the language information for any parent items that already exist (or create database entries if none already exist)

update the analyze interface to add controls for selecting ‘parents by group’ and a dropdown for ‘parent by language’

Update the analyze controller to read the parent-by-group parameter

Create a graph for the parent-by-group view

Bubble up averages for language calculations.

Make sure n-size only counts responses for a given measure.
mciea-main
rebuilt 8 months ago
parent 1845d1ac7b
commit b0df611f4d

@ -1,5 +1,6 @@
class Housing < ApplicationRecord class Housing < ApplicationRecord
has_many :parents, dependent: :nullify has_many :parents, dependent: :nullify
scope :by_designation, -> { all.map { |housing| [housing.designation, housing] }.to_h }
def self.to_designation(housing) def self.to_designation(housing)
return "Unknown" if housing.blank? return "Unknown" if housing.blank?

@ -1,12 +1,13 @@
class Language < ApplicationRecord class Language < ApplicationRecord
scope :by_designation, -> { all.map { |language| [language.designation, language] }.to_h } scope :by_designation, -> { all.map { |language| [language.designation, language] }.to_h }
has_many :parents, dependent: :nullify has_many :parent_languages, dependent: :destroy
has_many :parents, through: :parent_languages, dependent: :nullify
include FriendlyId include FriendlyId
friendly_id :designation, use: [:slugged] friendly_id :designation, use: [:slugged]
def self.to_designation(language) def self.to_designation(language)
return "Unknown" if language.blank? return "Prefer not to disclose" if language.blank?
case language case language
in /^1$/i in /^1$/i

@ -32,6 +32,8 @@ module Analyze
end end
def score(measure:, school:, academic_year:) def score(measure:, school:, academic_year:)
return Score::NIL_SCORE if n_size(measure:, school:, academic_year:) < 10
averages = SurveyItemResponse.averages_for_language(measure.parent_survey_items, school, academic_year, averages = SurveyItemResponse.averages_for_language(measure.parent_survey_items, school, academic_year,
designations) designations)
average = bubble_up_averages(measure:, averages:).round(2) average = bubble_up_averages(measure:, averages:).round(2)

@ -1,46 +0,0 @@
module Analyze
module Graph
module Column
module Parent
class Language < ColumnBase
attr_reader :parent
def initialize(parent:)
@parent = parent
end
def label
["#{parent.designation}"]
end
def basis
"parent"
end
def show_irrelevancy_message?(measure:)
false
end
def show_insufficient_data_message?(measure:, school:, academic_years:)
false
end
def type
:parent
end
def n_size(measure:, school:, academic_year:)
SurveyItemResponse.where( survey_item: measure.parent_survey_items, school:, academic_year:),
academic_year:).select(:response_id).distinct.count
end
def score(measure:, school:, academic_year:)
Score.new(average: 3,
meets_teacher_threshold: false,
meets_student_threshold:,
meets_admin_data_threshold: false)
end
end
end
end
end

@ -23,6 +23,7 @@ module Analyze
array << Analyze::Graph::Column::Language.new(languages: ENGLISH_LANGUAGES, label: ["English", "Speaking"]) array << Analyze::Graph::Column::Language.new(languages: ENGLISH_LANGUAGES, label: ["English", "Speaking"])
array << Analyze::Graph::Column::Language.new(languages: NON_ENGLISH_LANGUAGES, label: ["Non English", "Speaking"]) array << Analyze::Graph::Column::Language.new(languages: NON_ENGLISH_LANGUAGES, label: ["Non English", "Speaking"])
array << Analyze::Graph::Column::Language.new(languages: UNKNOWN_LANGUAGES, label: ["Unknown"]) array << Analyze::Graph::Column::Language.new(languages: UNKNOWN_LANGUAGES, label: ["Unknown"])
array << Analyze::Graph::Column::Language.new(languages: ALL_LANGUAGES, label: ["All", "Parents"])
end end
end end
@ -31,11 +32,11 @@ module Analyze
end end
def slice def slice
Analyze::Slice::StudentsByGroup.new(graph: self) Analyze::Slice::ParentsByGroup.new(graph: self)
end end
def group def group
Analyze::Group::Base.new(name: "Special Education", slug: "sped", graph: self) Analyze::Group::Base.new(name: "Language", slug: "language", graph: self)
end end
end end
end end

@ -0,0 +1,9 @@
module Analyze
module Slice
class ParentsByGroup < Base
def initialize(graph:, label: "Parents by Group", slug: "parents-by-group")
super(label:, slug:, graph:)
end
end
end
end

@ -79,7 +79,7 @@ class Cleaner
headers = headers.to_set headers = headers.to_set
headers = headers.merge(Set.new(["Raw Income", "Income", "Raw ELL", "ELL", "Raw SpEd", "SpEd", "Progress Count", 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"])).to_a "Race", "Gender", "Raw Housing Status", "Housing Status", "Home Language", "Home Languages"])).to_a
filtered_headers = include_all_headers(headers:) filtered_headers = include_all_headers(headers:)
filtered_headers = remove_unwanted_headers(headers: filtered_headers) filtered_headers = remove_unwanted_headers(headers: filtered_headers)
log_headers = (filtered_headers + ["Valid Duration?", "Valid Progress?", "Valid Grade?", log_headers = (filtered_headers + ["Valid Duration?", "Valid Progress?", "Valid Grade?",

@ -171,7 +171,7 @@ class SurveyItemValues
end end
def lasid def lasid
@lasid ||= value_from(pattern: /LASID/i) @lasid ||= value_from(pattern: /LASID/i) || ""
end end
def raw_income def raw_income
@ -207,7 +207,7 @@ class SurveyItemValues
end end
def raw_language def raw_language
@raw_language ||= value_from(pattern: /^Language$/i) @raw_language ||= value_from(pattern: /^Language$/i) || ""
end end
def languages def languages
@ -234,6 +234,9 @@ class SurveyItemValues
output ||= row[match]&.strip output ||= row[match]&.strip
end end
output = output.delete("\u0000") if output.present?
output = output.delete("\x00") if output.present?
output.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') if output.present?
output output
end end

@ -99,24 +99,27 @@ class SurveyResponsesDataLoader
@languages ||= Language.by_designation @languages ||= Language.by_designation
end end
def housings
@housings ||= Housing.by_designation
end
def process_survey_items(row:) def process_survey_items(row:)
student = nil student = nil
parent = nil parent = nil
if row.respondent_type == :student if row.respondent_type == :student
student = Student.find_or_create_by(response_id: row.response_id, lasid: row.lasid) student = Student.find_or_create_by(response_id: row.response_id, lasid: row.lasid)
student.races.delete_all student.races.delete_all
tmp_races = row.races.map { |race| races[race] } tmp_races = row.races.map { |race| races[race] }.reject(&:nil?)
student.races += tmp_races student.races += tmp_races
end end
if row.respondent_type == :parent if row.respondent_type == :parent
parent = Parent.find_or_create_by(response_id: row.response_id) parent = Parent.find_or_create_by(response_id: row.response_id)
parent.number_of_children = row.number_of_children parent.number_of_children = row.number_of_children
tmp_languages = row.languages.map { |language| languages[language] } tmp_languages = row.languages.map { |language| languages[language] }.reject(&:nil?)
parent.housing_id = Housing.find_by(designation: row.housing).id
parent.languages.delete_all parent.languages.delete_all
parent.languages.concat(tmp_languages) parent.languages.concat(tmp_languages)
parent.housing = Housing.find_by(designation: row.housing) parent.housing = housings[row.housing] if row.housing.present?
parent.save parent.save
end end

@ -0,0 +1,10 @@
class CreateHousings < ActiveRecord::Migration[8.0]
def change
create_table :housings do |t|
t.string :designation
t.string :slug
t.timestamps
end
end
end

@ -1,7 +1,5 @@
class AddHousingToParent < ActiveRecord::Migration[8.0] class AddHousingToParent < ActiveRecord::Migration[8.0]
def change def change
add_reference :parents, :housing, foreign_key: true add_reference :parents, :housing, foreign_key: true
Parent.update_all(housing_id: Housing.find_by(designation: 'Unknown').id)
change_column_null :parents, :housing_id, false
end end
end end

@ -89,6 +89,13 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_18_185655) do
t.index ["slug"], name: "index_genders_on_slug", unique: true t.index ["slug"], name: "index_genders_on_slug", unique: true
end end
create_table "housings", force: :cascade do |t|
t.string "designation"
t.string "slug"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "incomes", force: :cascade do |t| create_table "incomes", force: :cascade do |t|
t.string "designation" t.string "designation"
t.datetime "created_at", null: false t.datetime "created_at", null: false

@ -11,10 +11,10 @@ LEP Not1stYr,ELL,,0,Not Special Education,,FALSE,Economically Disadvantaged
LEP1stYr US Sch,ELL,,Not Special Education,Not Special Education,,0,Economically Disadvantaged N,,, LEP1stYr US Sch,ELL,,Not Special Education,Not Special Education,,0,Economically Disadvantaged N,,,
Does not apply,Not ELL,,Does not apply,Not Special Education,,[blanks],Economically Disadvantaged N,,, Does not apply,Not ELL,,Does not apply,Not Special Education,,[blanks],Economically Disadvantaged N,,,
0,Not ELL,,[blanks],Not Special Education,,#NA,Unknown,,, 0,Not ELL,,[blanks],Not Special Education,,#NA,Unknown,,,
2,Not ELL,,#NA,Not Special Education,,NA,Unknown,,, 2,Not ELL,,#NA,Unknown,,NA,Unknown,,,
3,Not ELL,,NA,Not Special Education,,N/A,Unknown,,, 3,Not ELL,,NA,Unknown,,N/A,Unknown,,,
[blanks],Not ELL,,N/A,Not Special Education,,#N/A,Unknown,,, [blanks],Not ELL,,N/A,Unknown,,#N/A,Unknown,,,
#NA,Unknown,,#N/A,Not Special Education,,Income,Unknown,,, #NA,Unknown,,#N/A,Unknown,,Income,Unknown,,,
NA,Unknown,,SPED,Unknown,,Yes,Economically Disadvantaged Y,,, NA,Unknown,,SPED,Unknown,,Yes,Economically Disadvantaged Y,,,
N/A,Unknown,,No special needs,Not Special Education,,No,Economically Disadvantaged N,,, N/A,Unknown,,No special needs,Not Special Education,,No,Economically Disadvantaged N,,,
#N/A,Unknown,,Not SPED,Not Special Education,,,,,, #N/A,Unknown,,Not SPED,Not Special Education,,,,,,

1 ELL Value ELL Type ELL Headers Sped Value Sped Type Sped Headers Income Value Income Type Income Headers Language Value Language Type
11 LEP1stYr US Sch ELL Not Special Education Not Special Education 0 Economically Disadvantaged – N
12 Does not apply Not ELL Does not apply Not Special Education [blanks] Economically Disadvantaged – N
13 0 Not ELL [blanks] Not Special Education #NA Unknown
14 2 Not ELL #NA Not Special Education Unknown NA Unknown
15 3 Not ELL NA Not Special Education Unknown N/A Unknown
16 [blanks] Not ELL N/A Not Special Education Unknown #N/A Unknown
17 #NA Unknown #N/A Not Special Education Unknown Income Unknown
18 NA Unknown SPED Unknown Yes Economically Disadvantaged – Y
19 N/A Unknown No special needs Not Special Education No Economically Disadvantaged – N
20 #N/A Unknown Not SPED Not Special Education

@ -315,7 +315,7 @@ def reads_headers_from_raw_csv(processed_data)
"s-grit-q1", "s-grit-q2", "s-grit-q3", "s-grit-q4", "s-expa-q1", "s-poaf-q1", "s-poaf-q2", "s-poaf-q3", "s-grit-q1", "s-grit-q2", "s-grit-q3", "s-grit-q4", "s-expa-q1", "s-poaf-q1", "s-poaf-q2", "s-poaf-q3",
"s-poaf-q4", "s-tint-q1-1", "s-tint-q2-1", "s-tint-q3-1", "s-tint-q4-1", "s-tint-q5-1", "s-acpr-q1-1", "s-poaf-q4", "s-tint-q1-1", "s-tint-q2-1", "s-tint-q3-1", "s-tint-q4-1", "s-tint-q5-1", "s-acpr-q1-1",
"s-acpr-q2-1", "s-acpr-q3-1", "s-acpr-q4-1", "s-peff-q1-1", "s-peff-q2-1", "s-peff-q3-1", "s-peff-q4-1", "s-acpr-q2-1", "s-acpr-q3-1", "s-acpr-q4-1", "s-peff-q1-1", "s-peff-q2-1", "s-peff-q3-1", "s-peff-q4-1",
"s-peff-q5-1", "s-peff-q6-1", "Raw Income", "Income", "Raw ELL", "ELL", "Raw SpEd", "SpEd", "Progress Count", "Housing Status", "Raw Housing Status", "Home Language"].to_set.sort "s-peff-q5-1", "s-peff-q6-1", "Raw Income", "Income", "Raw ELL", "ELL", "Raw SpEd", "SpEd", "Progress Count", "Housing Status", "Raw Housing Status", "Home Language", "Home Languages"].to_set.sort
end end
def invalid_rows_are_rejected_for_the_correct_reasons(data) def invalid_rows_are_rejected_for_the_correct_reasons(data)

Loading…
Cancel
Save