From 0f457becf066fa500ad920a0d7620876cc7d94a1 Mon Sep 17 00:00:00 2001 From: rebuilt Date: Fri, 11 Apr 2025 14:37:18 -0700 Subject: [PATCH] feat: create a parents by language graph MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- app/models/housing.rb | 3 + app/models/language.rb | 33 +++++++ app/models/measure.rb | 4 + app/models/parent.rb | 3 + app/models/parent_language.rb | 4 + app/models/report/measure_summary.rb | 12 +-- app/models/sped.rb | 4 +- app/models/survey_item_response.rb | 4 + .../analyze/graph/column/language.rb | 60 +++++++++++++ .../analyze/graph/parents_by_language.rb | 43 +++++++++ app/presenters/analyze/presenter.rb | 15 ++-- .../analyze/slice/parents_by_group.rb | 9 ++ app/services/cleaner.rb | 2 +- app/services/demographic_loader.rb | 1 + app/services/survey_item_values.rb | 22 ++++- app/services/survey_responses_data_loader.rb | 14 ++- app/views/analyze/_data_filters.html.erb | 88 +++++++++---------- data/demographics.csv | 22 ++--- .../20250408212201_add_housing_to_parent.rb | 5 ++ db/migrate/20250411213850_create_languages.rb | 10 +++ .../20250418184427_create_parent_languages.rb | 12 +++ ...85032_add_designation_index_to_language.rb | 5 ++ db/schema.rb | 25 +++++- spec/factories.rb | 9 ++ spec/fixtures/demographic_glossary.csv | 45 +++++----- spec/models/language_spec.rb | 5 ++ spec/models/parent_language_spec.rb | 5 ++ spec/presenters/analyze/presenter_spec.rb | 21 +++-- spec/services/cleaner_spec.rb | 3 +- spec/services/survey_item_values_spec.rb | 20 ++++- .../survey_responses_data_loader_spec.rb | 16 ++++ 31 files changed, 414 insertions(+), 110 deletions(-) create mode 100644 app/models/language.rb create mode 100644 app/models/parent_language.rb create mode 100644 app/presenters/analyze/graph/column/language.rb create mode 100644 app/presenters/analyze/graph/parents_by_language.rb create mode 100644 app/presenters/analyze/slice/parents_by_group.rb create mode 100644 db/migrate/20250408212201_add_housing_to_parent.rb create mode 100644 db/migrate/20250411213850_create_languages.rb create mode 100644 db/migrate/20250418184427_create_parent_languages.rb create mode 100644 db/migrate/20250418185032_add_designation_index_to_language.rb create mode 100644 spec/models/language_spec.rb create mode 100644 spec/models/parent_language_spec.rb diff --git a/app/models/housing.rb b/app/models/housing.rb index a5dbe10d..b930a547 100644 --- a/app/models/housing.rb +++ b/app/models/housing.rb @@ -1,4 +1,7 @@ class Housing < ApplicationRecord + has_many :parents, dependent: :nullify + scope :by_designation, -> { all.map { |housing| [housing.designation, housing] }.to_h } + def self.to_designation(housing) return "Unknown" if housing.blank? diff --git a/app/models/language.rb b/app/models/language.rb new file mode 100644 index 00000000..71f5e992 --- /dev/null +++ b/app/models/language.rb @@ -0,0 +1,33 @@ +class Language < ApplicationRecord + scope :by_designation, -> { all.map { |language| [language.designation, language] }.to_h } + has_many :parent_languages, dependent: :destroy + has_many :parents, through: :parent_languages, dependent: :nullify + + include FriendlyId + + friendly_id :designation, use: [:slugged] + def self.to_designation(language) + return "Prefer not to disclose" if language.blank? + + case language + in /^1$/i + "English" + in /^2$/i + "Portuguese" + in /^3$/i + "Spanish" + in /^99$/i + "Prefer not to disclose" + in /|^100$/i + "Prefer to self-describe" + else + puts "************************************" + puts "******** ERROR **********" + puts "" + puts "Error parsing Language column. '#{language}' is not a known value. Halting execution" + puts "" + puts "************************************" + exit + end + end +end diff --git a/app/models/measure.rb b/app/models/measure.rb index 72a794b4..67c7e9fa 100644 --- a/app/models/measure.rb +++ b/app/models/measure.rb @@ -51,6 +51,10 @@ class Measure < ActiveRecord::Base @student_scales ||= scales.student_scales end + def parent_scales + @parent_scales ||= scales.parent_scales + end + def includes_teacher_survey_items? @includes_teacher_survey_items ||= teacher_survey_items.length.positive? end diff --git a/app/models/parent.rb b/app/models/parent.rb index 2d556d15..ea815ac8 100644 --- a/app/models/parent.rb +++ b/app/models/parent.rb @@ -1,2 +1,5 @@ class Parent < ApplicationRecord + belongs_to :housing, optional: true + has_many :parent_languages + has_and_belongs_to_many :languages, join_table: :parent_languages end diff --git a/app/models/parent_language.rb b/app/models/parent_language.rb new file mode 100644 index 00000000..ec878727 --- /dev/null +++ b/app/models/parent_language.rb @@ -0,0 +1,4 @@ +class ParentLanguage < ApplicationRecord + belongs_to :parent + belongs_to :language +end diff --git a/app/models/report/measure_summary.rb b/app/models/report/measure_summary.rb index ae8099d7..b9b64242 100644 --- a/app/models/report/measure_summary.rb +++ b/app/models/report/measure_summary.rb @@ -21,15 +21,11 @@ module Report Thread.new do while measure = jobs.pop(true) academic_years.each do |academic_year| - all_grades = Set.new - respondents = Respondent.where(school: schools, academic_year:) - respondents.each do |respondent| - respondent.enrollment_by_grade.keys.each do |grade| - all_grades.add(grade) - end - end - all_grades = all_grades.to_a + + enrollment = respondents.map do | respondent| respondent.enrollment_by_grade.keys end.flatten.compact.uniq.sort + grades_with_responses = ::SurveyItemResponse.where(school: schools, academic_year:).where.not(grade: nil).pluck(:grade).uniq.sort + all_grades = (enrollment & grades_with_responses).sort grades = "#{all_grades.first}-#{all_grades.last}" begin_date = ::SurveyItemResponse.where(school: schools, diff --git a/app/models/sped.rb b/app/models/sped.rb index eaca0437..b132aeb9 100644 --- a/app/models/sped.rb +++ b/app/models/sped.rb @@ -10,9 +10,9 @@ class Sped < ApplicationRecord case sped in /active|^A$|1|^Special\s*Education$/i "Special Education" - in %r{^I$|exited|0|^Not\s*Special\s*Education$|Does\s*not\s*apply|Referred|Ineligible|^No\s*special\s*needs$|Not\s*SPED|^#*N/*A$}i + in /^I$|exited|0|^Not\s*Special\s*Education$|Does\s*not\s*apply|Referred|Ineligible|^No\s*special\s*needs$|Not\s*SPED/i "Not Special Education" - in /^Unknown|^SpecialEdStatus|^SPED/i + in %r{^#*N/*A$|^Unknown|^SpecialEdStatus|^SPED}i "Unknown" else puts "************************************" diff --git a/app/models/survey_item_response.rb b/app/models/survey_item_response.rb index ed559983..5c93dbcb 100644 --- a/app/models/survey_item_response.rb +++ b/app/models/survey_item_response.rb @@ -51,6 +51,10 @@ class SurveyItemResponse < ActiveRecord::Base ).where("student_races.race_id": race.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) + } + def self.grouped_responses(school:, academic_year:) @grouped_responses ||= Hash.new do |memo, (school, academic_year)| memo[[school, academic_year]] = diff --git a/app/presenters/analyze/graph/column/language.rb b/app/presenters/analyze/graph/column/language.rb new file mode 100644 index 00000000..68d6915d --- /dev/null +++ b/app/presenters/analyze/graph/column/language.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Analyze + module Graph + module Column + class Language < ColumnBase + attr_reader :language, :label + + def initialize(languages:, label:) + @language = languages + @label = label + end + + def basis + "parent surveys" + 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.joins([parent: :languages]).where(languages: { designation: designations }, survey_item: measure.parent_survey_items, school:, academic_year:).select(:parent_id).distinct.count + end + + 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, + designations) + average = bubble_up_averages(measure:, averages:).round(2) + Score.new(average:, + meets_teacher_threshold: false, + meets_student_threshold: true, + meets_admin_data_threshold: false) + end + + def designations + language.map(&:designation) + end + + def bubble_up_averages(measure:, averages:) + measure.parent_scales.map do |scale| + scale.survey_items.map do |survey_item| + averages[survey_item] + end.remove_blanks.average + end.remove_blanks.average + end + end + end + end +end diff --git a/app/presenters/analyze/graph/parents_by_language.rb b/app/presenters/analyze/graph/parents_by_language.rb new file mode 100644 index 00000000..06079db8 --- /dev/null +++ b/app/presenters/analyze/graph/parents_by_language.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Analyze + module Graph + class ParentsByLanguage + attr_reader :speds + + ALL_LANGUAGES = Language.all + ENGLISH_LANGUAGES = ALL_LANGUAGES.select { |language| language.designation == "English" } + UNKNOWN_LANGUAGES = ALL_LANGUAGES.select { |language| language.designation == "Prefer not to disclose" } + NON_ENGLISH_LANGUAGES = (ALL_LANGUAGES - ENGLISH_LANGUAGES - UNKNOWN_LANGUAGES) + + def to_s + "Parents by Language" + end + + def slug + "parents-by-language" + end + + def columns + [].tap do |array| + 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: UNKNOWN_LANGUAGES, label: ["Unknown"]) + array << Analyze::Graph::Column::Language.new(languages: ALL_LANGUAGES, label: ["All", "Parents"]) + 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: "Language", slug: "language", graph: self) + end + end + end +end diff --git a/app/presenters/analyze/presenter.rb b/app/presenters/analyze/presenter.rb index d3dde077..50e27b92 100644 --- a/app/presenters/analyze/presenter.rb +++ b/app/presenters/analyze/presenter.rb @@ -96,10 +96,14 @@ module Analyze end def groups - @groups = graphs.map(&:group) - .reject { |group| group.name.nil? } - .sort_by { |group| group.name } - .uniq + @groups ||= + begin + first_char_of_class_name = graph.class.name.demodulize.first + graphs.select { |graph| graph.class.name.demodulize.first == first_char_of_class_name }.map(&:group) + .reject { |group| group.name.nil? } + .sort_by { |group| group.name } + .uniq + end end def group @@ -159,7 +163,8 @@ module Analyze Analyze::Graph::StudentsByGender.new(genders: selected_genders), Analyze::Graph::StudentsByIncome.new(incomes: selected_incomes), Analyze::Graph::StudentsBySped.new(speds: selected_speds), - Analyze::Graph::StudentsByEll.new(ells: selected_ells)] + Analyze::Graph::StudentsByEll.new(ells: selected_ells), + Analyze::Graph::ParentsByLanguage.new] end def graph diff --git a/app/presenters/analyze/slice/parents_by_group.rb b/app/presenters/analyze/slice/parents_by_group.rb new file mode 100644 index 00000000..f97d93f7 --- /dev/null +++ b/app/presenters/analyze/slice/parents_by_group.rb @@ -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 diff --git a/app/services/cleaner.rb b/app/services/cleaner.rb index a190a37a..6463340b 100644 --- a/app/services/cleaner.rb +++ b/app/services/cleaner.rb @@ -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"])).to_a + "Race", "Gender", "Raw Housing Status", "Housing Status", "Home Language", "Home Languages"])).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?", diff --git a/app/services/demographic_loader.rb b/app/services/demographic_loader.rb index 36170251..5c2aa3ce 100644 --- a/app/services/demographic_loader.rb +++ b/app/services/demographic_loader.rb @@ -9,6 +9,7 @@ class DemographicLoader create_from_column(column: "ELL", row:, model: Ell) create_from_column(column: "Special Ed Status", row:, model: Sped) create_from_column(column: "Housing", row:, model: Housing) + create_from_column(column: "Language", row:, model: Language) end end diff --git a/app/services/survey_item_values.rb b/app/services/survey_item_values.rb index e619bb62..0e739883 100644 --- a/app/services/survey_item_values.rb +++ b/app/services/survey_item_values.rb @@ -22,6 +22,7 @@ class SurveyItemValues row["Gender"] ||= gender 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) @@ -161,7 +162,7 @@ class SurveyItemValues # Only check the secondary hispanic column if we don't have self reported data and are relying on SIS data if self_report.nil? && sis.present? hispanic = value_from(pattern: /Hispanic\s*Latino/i)&.downcase - race_codes = race_codes.reject { |code| code == 5 } if hispanic == "true" && race_codes.count == 1 + race_codes = race_codes.reject { |code| code == 5 } if ["true", "1"].include?(hispanic) || race_codes.count == 1 race_codes = race_codes.push(4) if %w[true 1].include?(hispanic) end @@ -170,7 +171,7 @@ class SurveyItemValues end def lasid - @lasid ||= value_from(pattern: /LASID/i) + @lasid ||= value_from(pattern: /LASID/i) || "" end def raw_income @@ -205,6 +206,20 @@ class SurveyItemValues @housing ||= Housing.to_designation(raw_housing) end + def raw_language + @raw_language ||= value_from(pattern: /^Language$/i) || "" + end + + def languages + @languages ||= [].tap do |languages| + if raw_language.present? + raw_language.split(",").each do |item| + languages << Language.to_designation(item) + end + end + end + end + def number_of_children @number_of_children ||= value_from(pattern: /Number\s*Of\s*Children/i).to_i end @@ -219,6 +234,9 @@ class SurveyItemValues output ||= row[match]&.strip 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 end diff --git a/app/services/survey_responses_data_loader.rb b/app/services/survey_responses_data_loader.rb index 6da9ef71..ea303679 100644 --- a/app/services/survey_responses_data_loader.rb +++ b/app/services/survey_responses_data_loader.rb @@ -86,19 +86,31 @@ class SurveyResponsesDataLoader process_survey_items(row:) end + def languages + @languages ||= Language.by_designation + end + + def housings + @housings ||= Housing.by_designation + end + def process_survey_items(row:) student = nil parent = nil if row.respondent_type == :student student = Student.find_or_create_by(response_id: row.response_id, lasid: row.lasid) 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 end if row.respondent_type == :parent parent = Parent.find_or_create_by(response_id: row.response_id) parent.number_of_children = row.number_of_children + tmp_languages = row.languages.map { |language| languages[language] }.reject(&:nil?) + parent.languages.delete_all + parent.languages.concat(tmp_languages) + parent.housing = housings[row.housing] if row.housing.present? parent.save end diff --git a/app/views/analyze/_data_filters.html.erb b/app/views/analyze/_data_filters.html.erb index f1eecfa4..a1698a36 100644 --- a/app/views/analyze/_data_filters.html.erb +++ b/app/views/analyze/_data_filters.html.erb @@ -1,32 +1,8 @@

Data Filters

- <% @presenter.sources.each do |source| %> - -<%= form_with(url: district_school_analyze_index_path, - method: :get, - data: { - turbo_frame: "results", - turbo_action: "advance", - controller: "analyze", - action: "input->analyze#submit" - }) do |f| %> - - <% params.reject{|key,_| key == "graph"}.each do |key, value| %> - - <% end %> - > - - - <% end %> - - -<% if source.slug != "all-data" %> +<% @presenter.sources.each do |source| %> + <%# Source options; e.g. 'All Data' or 'Survey Data only' %> <%= form_with(url: district_school_analyze_index_path, method: :get, data: { @@ -36,28 +12,52 @@ action: "input->analyze#submit" }) do |f| %> - <% params.reject{|key,_| key == "graph"}.each do |key, value| %> - - <% end %> + <% params.reject{|key,_| key == "graph"}.each do |key, value| %> + + <% end %> - <% @presenter.slices.each do | slice | %> -
- <%= slice.slug == "all-data" ? "hidden" : "" %>> - - -
+ id="<%= source.slug %>" + class="form-check-input" + name="graph" + value="<%= source.graph.slug %>" + <%= source.slug == @presenter.source.slug ? "checked" : "" %>> + + <% end %> + + + <% if source.slug != "all-data" %> + <%= form_with(url: district_school_analyze_index_path, + method: :get, + data: { + turbo_frame: "results", + turbo_action: "advance", + controller: "analyze", + action: "input->analyze#submit" + }) do |f| %> + + <% params.reject{|key,_| key == "graph"}.each do |key, value| %> + + <% end %> + + <% @presenter.slices.each do | slice | %> +
+ + <%= slice.slug == "all-data" ? "hidden" : "" %>> + + +
+ <% end %> <% end %> - <% end %> <% end %> <% end %> - <%= render partial: "group_selectors" %> +<%= render partial: "group_selectors" %>
diff --git a/data/demographics.csv b/data/demographics.csv index e2a005aa..9823c81c 100644 --- a/data/demographics.csv +++ b/data/demographics.csv @@ -1,11 +1,11 @@ -Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income,ELL,Special Ed Status,Housing -1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged - N,ELL,Special Education,Own -2,Asian or Pacific Islander,1,Female,Economically Disadvantaged - Y,Not ELL,Not Special Education,Rent -3,Black or African American,4,Non-Binary,Unknown,Unknown,Unknown,Unknown -4,Hispanic or Latinx,99,Unknown,,,, -5,White or Caucasian,,,,,, -6,Prefer not to disclose,,,,,, -7,Prefer to self-describe,,,,,, -8,Middle Eastern,,,,,, -99,Race/Ethnicity Not Listed,,,,,, -100,Multiracial,,,,,, +Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income,ELL,Special Ed Status,Housing,Language +1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged - N,ELL,Special Education,Own,English +2,Asian or Pacific Islander,1,Female,Economically Disadvantaged - Y,Not ELL,Not Special Education,Rent,Portuguese +3,Black or African American,4,Non-Binary,Unknown,Unknown,Unknown,Unknown,Spanish +4,Hispanic or Latinx,99,Unknown,,,,,Prefer not to disclose +5,White or Caucasian,,,,,,,Prefer to self-describe +6,Prefer not to disclose,,,,,,, +7,Prefer to self-describe,,,,,,, +8,Middle Eastern,,,,,,, +99,Race/Ethnicity Not Listed,,,,,,, +100,Multiracial,,,,,,, diff --git a/db/migrate/20250408212201_add_housing_to_parent.rb b/db/migrate/20250408212201_add_housing_to_parent.rb new file mode 100644 index 00000000..ed297cf3 --- /dev/null +++ b/db/migrate/20250408212201_add_housing_to_parent.rb @@ -0,0 +1,5 @@ +class AddHousingToParent < ActiveRecord::Migration[8.0] + def change + add_reference :parents, :housing, foreign_key: true + end +end diff --git a/db/migrate/20250411213850_create_languages.rb b/db/migrate/20250411213850_create_languages.rb new file mode 100644 index 00000000..65021c7f --- /dev/null +++ b/db/migrate/20250411213850_create_languages.rb @@ -0,0 +1,10 @@ +class CreateLanguages < ActiveRecord::Migration[8.0] + def change + create_table :languages do |t| + t.string :designation + t.string :slug + + t.timestamps + end + end +end diff --git a/db/migrate/20250418184427_create_parent_languages.rb b/db/migrate/20250418184427_create_parent_languages.rb new file mode 100644 index 00000000..1cae5fc1 --- /dev/null +++ b/db/migrate/20250418184427_create_parent_languages.rb @@ -0,0 +1,12 @@ +class CreateParentLanguages < ActiveRecord::Migration[8.0] + def change + create_table :parent_languages do |t| + t.references :parent, null: false, foreign_key: true + t.references :language, null: false, foreign_key: true + + t.timestamps + end + + add_index :parent_languages, %i[parent_id language_id] + end +end diff --git a/db/migrate/20250418185032_add_designation_index_to_language.rb b/db/migrate/20250418185032_add_designation_index_to_language.rb new file mode 100644 index 00000000..221b98bf --- /dev/null +++ b/db/migrate/20250418185032_add_designation_index_to_language.rb @@ -0,0 +1,5 @@ +class AddDesignationIndexToLanguage < ActiveRecord::Migration[8.0] + def change + add_index :languages, %i[designation] + end +end diff --git a/db/schema.rb b/db/schema.rb index f16565ab..9e62ba38 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_03_27_205800) do +ActiveRecord::Schema[8.0].define(version: 2025_04_18_185655) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -102,6 +102,14 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_27_205800) do t.index ["slug"], name: "index_incomes_on_slug", unique: true end + create_table "languages", force: :cascade do |t| + t.string "designation" + t.string "slug" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["designation"], name: "index_languages_on_designation" + end + create_table "legacy_attempts", id: :serial, force: :cascade do |t| t.integer "recipient_id" t.integer "schedule_id" @@ -322,11 +330,23 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_27_205800) do t.index ["subcategory_id"], name: "index_measures_on_subcategory_id" end + create_table "parent_languages", force: :cascade do |t| + t.bigint "parent_id", null: false + t.bigint "language_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["language_id"], name: "index_parent_languages_on_language_id" + t.index ["parent_id", "language_id"], name: "index_parent_languages_on_parent_id_and_language_id" + t.index ["parent_id"], name: "index_parent_languages_on_parent_id" + end + create_table "parents", force: :cascade do |t| t.string "response_id" t.integer "number_of_children" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "housing_id" + t.index ["housing_id"], name: "index_parents_on_housing_id" end create_table "races", force: :cascade do |t| @@ -514,6 +534,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_27_205800) do add_foreign_key "legacy_school_categories", "legacy_categories", column: "category_id" add_foreign_key "legacy_school_categories", "legacy_schools", column: "school_id" add_foreign_key "measures", "subcategories" + add_foreign_key "parent_languages", "languages" + add_foreign_key "parent_languages", "parents" + add_foreign_key "parents", "housings" add_foreign_key "respondents", "academic_years" add_foreign_key "respondents", "schools" add_foreign_key "response_rates", "academic_years" diff --git a/spec/factories.rb b/spec/factories.rb index e9aa534b..81f184bf 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,4 +1,13 @@ FactoryBot.define do + factory :parent_language do + parent { nil } + language { nil } + end + + factory :language do + designation { "MyString" } + end + factory :housing do designation { "MyString" } end diff --git a/spec/fixtures/demographic_glossary.csv b/spec/fixtures/demographic_glossary.csv index 4ff277d8..ca8bfcb1 100644 --- a/spec/fixtures/demographic_glossary.csv +++ b/spec/fixtures/demographic_glossary.csv @@ -1,21 +1,24 @@ -ELL Value,ELL Type,ELL Headers,Sped Value,Sped Type,Sped Headers,Income Value,Income Type,Income Headers -EL student not 1st year,ELL,EL Student First Year,A,Special Education,Special Ed Status,Free Lunch,Economically Disadvantaged – Y,Low Income -"EL student, not 1st year",ELL,Raw ELL,active,Special Education,Raw SPED,Reduced Lunch,Economically Disadvantaged – Y,Raw Income -EL student 1st year,ELL,ELL- SIS,1,Special Education,SPED- SIS,LowIncome,Economically Disadvantaged – Y,SES- SIS -"EL student, 1st year",ELL,DirectCert,Special Education,Special Education,SPED,Low Income,Economically Disadvantaged – Y,EconDisadvantaged -EL - Early Child. or PK,ELL,ELL,Referred,Not Special Education,,Reduced price lunch,Economically Disadvantaged – Y,Income SIS -1,ELL,English Learner,Ineligible,Not Special Education,,TRUE,Economically Disadvantaged – Y,SES -lep student 1st year,ELL,,I,Not Special Education,,1,Economically Disadvantaged – Y,DirectCert -lep student not 1st year,ELL,,exited,Not Special Education,,Not Eligible,Economically Disadvantaged – N, -LEP Not1stYr,ELL,,0,Not Special Education,,FALSE,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, -0,Not ELL,,[blanks],Not Special Education,,#NA,Unknown, -2,Not ELL,,#NA,Unknown,,NA,Unknown, -3,Not ELL,,NA,Unknown,,N/A,Unknown, -[blanks],Not ELL,,N/A,Unknown,,#N/A,Unknown, -#NA,Unknown,,#N/A,Unknown,,Income,Unknown, -NA,Unknown,,SPED,Unknown,,Yes,Economically Disadvantaged – Y, -N/A,Unknown,,No special needs,Not Special Education,,No,Economically Disadvantaged – N, -#N/A,Unknown,,,,,,, -ELL,ELL,,,,,,, +ELL Value,ELL Type,ELL Headers,Sped Value,Sped Type,Sped Headers,Income Value,Income Type,Income Headers,Language Value,Language Type +EL student not 1st year,ELL,EL Student First Year,A,Special Education,Special Ed Status,Free Lunch,Economically Disadvantaged – Y,Low Income,1,English +"EL student, not 1st year",ELL,Raw ELL,active,Special Education,Raw SPED,Reduced Lunch,Economically Disadvantaged – Y,Raw Income,2,Portuguese +EL student 1st year,ELL,ELL- SIS,1,Special Education,SPED- SIS,LowIncome,Economically Disadvantaged – Y,SES- SIS,3,Spanish +"EL student, 1st year",ELL,DirectCert,Special Education,Special Education,SPED,Low Income,Economically Disadvantaged – Y,EconDisadvantaged,99,Prefer not to disclose +EL - Early Child. or PK,ELL,ELL,Referred,Not Special Education,,Reduced price lunch,Economically Disadvantaged – Y,Income SIS,100,Prefer to self-describe +1,ELL,English Learner,Ineligible,Not Special Education,,TRUE,Economically Disadvantaged – Y,SES ,, +lep student 1st year,ELL,ELL Status- SIS,I,Not Special Education,,1,Economically Disadvantaged – Y,DirectCert,, +lep student not 1st year,ELL,,exited,Not Special Education,,Not Eligible,Economically Disadvantaged – N,,, +LEP Not1stYr,ELL,,0,Not Special Education,,FALSE,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,,, +0,Not ELL,,[blanks],Unknown,,#NA,Unknown,,, +2,Not ELL,,#NA,Unknown,,NA,Unknown,,, +3,Not ELL,,NA,Unknown,,N/A,Unknown,,, +[blanks],Not ELL,,N/A,Unknown,,#N/A,Unknown,,, +#NA,Unknown,,#N/A,Unknown,,Income,Unknown,,, +NA,Unknown,,SPED,Unknown,,Yes,Economically Disadvantaged – Y,,, +N/A,Unknown,,No special needs,Not Special Education,,No,Economically Disadvantaged – N,,, +#N/A,Unknown,,Not SPED,Not Special Education,,,,,, +ELL,ELL,,,,,,,,, +LEP Not 1st Year,ELL,,,,,,,,, +Yes,ELL,,,,,,,,, +No,Not ELL,,,,,,,,, diff --git a/spec/models/language_spec.rb b/spec/models/language_spec.rb new file mode 100644 index 00000000..924d9ca5 --- /dev/null +++ b/spec/models/language_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Language, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/parent_language_spec.rb b/spec/models/parent_language_spec.rb new file mode 100644 index 00000000..720c6862 --- /dev/null +++ b/spec/models/parent_language_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe ParentLanguage, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/presenters/analyze/presenter_spec.rb b/spec/presenters/analyze/presenter_spec.rb index bba430b3..675dfb98 100644 --- a/spec/presenters/analyze/presenter_spec.rb +++ b/spec/presenters/analyze/presenter_spec.rb @@ -366,38 +366,37 @@ describe Analyze::Presenter do context ".group" do context "when no parameters are provided" do - it "returns the first item in the list of groups" do - params = {} - presenter = Analyze::Presenter.new(params:, school:, academic_year:) - expect(presenter.group.slug).to eq presenter.groups.first.slug + it "returns no groups when no params are defined" do + presenter = Analyze::Presenter.new(params: {}, school:, academic_year:) + expect(presenter.groups).to eq [] end end context "when a group is provided in the params hash" do it "returns the group with the given slug" do - params = { group: "gender" } + params = { group: "gender", graph: "students-by-gender" } presenter = Analyze::Presenter.new(params:, school:, academic_year:) expect(presenter.group.slug).to eq "gender" - params = { group: "grade" } + params = { group: "grade", graph: "students-by-grade" } presenter = Analyze::Presenter.new(params:, school:, academic_year:) expect(presenter.group.slug).to eq "grade" - params = { group: "race" } + params = { group: "race", graph: "students-by-race" } presenter = Analyze::Presenter.new(params:, school:, academic_year:) expect(presenter.group.slug).to eq "race" - params = { group: "income" } + params = { group: "income", graph: "students-by-income" } presenter = Analyze::Presenter.new(params:, school:, academic_year:) expect(presenter.group.slug).to eq "income" end end context "when a parameter that does not match a group is provided" do - it "returns the first item in the list of groups" do - params = { group: "invalid group" } + it "returns nil when invalid parameters are given" do + params = { group: "invalid group", graph: "invalid graph" } presenter = Analyze::Presenter.new(params:, school:, academic_year:) - expect(presenter.group.slug).to eq presenter.groups.first.slug + expect(presenter.group).to eq nil end end end diff --git a/spec/services/cleaner_spec.rb b/spec/services/cleaner_spec.rb index 4733f7d6..6b7a137c 100644 --- a/spec/services/cleaner_spec.rb +++ b/spec/services/cleaner_spec.rb @@ -301,6 +301,7 @@ end def reads_headers_from_raw_csv(processed_data) processed_data in [headers, clean_csv, log_csv, data] + expect(headers.to_set.sort).to eq ["StartDate", "EndDate", "Status", "IPAddress", "Progress", "Duration (in seconds)", "Finished", "RecordedDate", "ResponseId", "District", "School", "LASID", "Gender", "Race", "What grade are you in?", "s-tint-q1", @@ -314,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-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-peff-q5-1", "s-peff-q6-1", "Raw Income", "Income", "Raw ELL", "ELL", "Raw SpEd", "SpEd", "Progress Count", "Housing Status", "Raw Housing Status"].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 def invalid_rows_are_rejected_for_the_correct_reasons(data) diff --git a/spec/services/survey_item_values_spec.rb b/spec/services/survey_item_values_spec.rb index e884d645..ac60e8c3 100644 --- a/spec/services/survey_item_values_spec.rb +++ b/spec/services/survey_item_values_spec.rb @@ -414,7 +414,7 @@ RSpec.describe SurveyItemValues, type: :model do end context "when there are multiple races" do - it "returns the gender that maps to the gender provided" do + it "returns the race that maps to the race provided" do row = { "Race" => "1,2,3" } values = SurveyItemValues.new(row:, headers:, survey_items:, schools:, academic_years:) expect(values.races).to eq [1, 2, 3, 100] @@ -452,7 +452,7 @@ RSpec.describe SurveyItemValues, type: :model do headers.push("HispanicLatino") headers.push("Race- SIS") values = SurveyItemValues.new(row:, headers:, survey_items:, schools:, academic_years:) - expect(values.races).to eq [5, 2, 3, 4, 100] + expect(values.races).to eq [2, 3, 4, 100] end end end @@ -567,6 +567,22 @@ RSpec.describe SurveyItemValues, type: :model do # end end + context ".language" do + before :each do + attleboro + ay_2022_23 + end + + it "validates the code matches the expectations defined in the demographic_glossary" do + + list = read(demographic_filepath, "Language Value", "Language Type") + + list.each do |target, result| + compare("Language", target, [result], :languages) + end + end + end + context ".ell" do before :each do attleboro diff --git a/spec/services/survey_responses_data_loader_spec.rb b/spec/services/survey_responses_data_loader_spec.rb index 2f2cf747..7212882e 100644 --- a/spec/services/survey_responses_data_loader_spec.rb +++ b/spec/services/survey_responses_data_loader_spec.rb @@ -91,6 +91,12 @@ describe SurveyResponsesDataLoader do ] end + let(:housings) do + create(:housing, designation: "Own") + create(:housing, designation: "Rent") + create(:housing, designation: "Unknown") + end + let(:t_pcom_q3) { create(:survey_item, survey_item_id: "t-pcom-q3") } let(:t_pcom_q2) { create(:survey_item, survey_item_id: "t-pcom-q2") } let(:t_coll_q1) { create(:survey_item, survey_item_id: "t-coll-q1") } @@ -123,12 +129,20 @@ describe SurveyResponsesDataLoader do let(:unknown_race) { create(:race, qualtrics_code: 99) } let(:multiracial) { create(:race, qualtrics_code: 100) } + let(:languages){ + create(:language, designation: "English") + create(:language, designation: "Spanish") + create(:language, designation: "Portuguese") + create(:language, designation: "Unknown") + } + let(:setup) do ay_2020_21 ay_2022_23 school second_school butler_school + housings t_pcom_q3 t_pcom_q2 t_coll_q1 @@ -161,6 +175,8 @@ describe SurveyResponsesDataLoader do middle_eastern unknown_race multiracial + + languages end before :each do