From 2fd56047d401d3dd83542ca6701e824c8e6e1171 Mon Sep 17 00:00:00 2001 From: rebuilt Date: Wed, 30 Aug 2023 15:18:38 -0700 Subject: [PATCH] Add disaggregation by ELL --- .../controllers/analyze_controller.js | 7 +- app/models/district.rb | 7 +- app/models/ell.rb | 7 + app/models/gender.rb | 2 +- app/models/income.rb | 1 + app/models/school.rb | 4 + app/models/survey_item_response.rb | 7 + .../analyze/graph/column/ell_column/ell.rb | 33 ++ .../graph/column/ell_column/ell_count.rb | 18 ++ .../graph/column/ell_column/not_ell.rb | 33 ++ .../graph/column/ell_column/score_for_ell.rb | 42 +++ .../graph/column/ell_column/unknown.rb | 33 ++ .../analyze/graph/students_by_ell.rb | 44 +++ .../analyze/graph/students_by_gender.rb | 2 + .../analyze/graph/students_by_grade.rb | 6 +- .../analyze/graph/students_by_income.rb | 2 + .../analyze/graph/students_by_race.rb | 22 +- app/presenters/analyze/group/ell.rb | 13 + app/presenters/analyze/presenter.rb | 24 +- app/services/cleaner.rb | 43 +-- app/services/demographic_loader.rb | 20 +- app/services/disaggregation_row.rb | 54 ++++ app/services/survey_item_values.rb | 29 +- app/services/survey_responses_data_loader.rb | 57 ++-- app/views/analyze/_group_selectors.html.erb | 4 + app/views/analyze/index.html.erb | 2 +- data/demographics.csv | 22 +- db/migrate/20230830213521_create_ell.rb | 12 + ...2223701_add_ell_to_survey_item_response.rb | 5 + db/schema.rb | 28 +- lib/tasks/clean.rake | 32 +- lib/tasks/data.rake | 2 +- lib/tasks/one_off.rake | 23 +- spec/factories.rb | 22 +- spec/fixtures/sample_demographics.csv | 22 +- .../test_2020-21_student_survey_responses.csv | 17 +- spec/services/cleaner_spec.rb | 51 ++- spec/services/demographic_loader_spec.rb | 33 +- spec/services/disaggregation_loader_spec.rb | 39 +++ spec/services/disaggregation_row_spec.rb | 108 +++++++ spec/services/student_loader_spec.rb | 4 +- spec/services/survey_item_values_spec.rb | 140 ++++---- .../survey_responses_data_loader_spec.rb | 302 ++++++++++-------- 43 files changed, 948 insertions(+), 430 deletions(-) create mode 100644 app/models/ell.rb create mode 100644 app/presenters/analyze/graph/column/ell_column/ell.rb create mode 100644 app/presenters/analyze/graph/column/ell_column/ell_count.rb create mode 100644 app/presenters/analyze/graph/column/ell_column/not_ell.rb create mode 100644 app/presenters/analyze/graph/column/ell_column/score_for_ell.rb create mode 100644 app/presenters/analyze/graph/column/ell_column/unknown.rb create mode 100644 app/presenters/analyze/graph/students_by_ell.rb create mode 100644 app/presenters/analyze/group/ell.rb create mode 100644 app/services/disaggregation_row.rb create mode 100644 db/migrate/20230830213521_create_ell.rb create mode 100644 db/migrate/20230912223701_add_ell_to_survey_item_response.rb create mode 100644 spec/services/disaggregation_loader_spec.rb create mode 100644 spec/services/disaggregation_row_spec.rb diff --git a/app/javascript/controllers/analyze_controller.js b/app/javascript/controllers/analyze_controller.js index bf290c9b..bef3ccae 100644 --- a/app/javascript/controllers/analyze_controller.js +++ b/app/javascript/controllers/analyze_controller.js @@ -28,7 +28,9 @@ export default class extends Controller { "&incomes=" + this.selected_items("income").join(",") + "&grades=" + - this.selected_items("grade").join(","); + this.selected_items("grade").join(",") + + "&ells=" + + this.selected_items("ell").join(","); this.go_to(url); } @@ -126,7 +128,8 @@ export default class extends Controller { ['gender', 'students-by-gender'], ['grade', 'students-by-grade'], ['income', 'students-by-income'], - ['race', 'students-by-race'] + ['race', 'students-by-race'], + ['ell', 'students-by-ell'], ]) if (target.name === 'slice' || target.name === 'group') { diff --git a/app/models/district.rb b/app/models/district.rb index eefbb4ab..023fac3c 100644 --- a/app/models/district.rb +++ b/app/models/district.rb @@ -16,6 +16,11 @@ class District < ApplicationRecord end def self.boston - District.find_by_name('Boston') + District.find_by_name("Boston") + end + + def short_name + name.split(" ").first.downcase end end + diff --git a/app/models/ell.rb b/app/models/ell.rb new file mode 100644 index 00000000..6b121fb1 --- /dev/null +++ b/app/models/ell.rb @@ -0,0 +1,7 @@ +class Ell < ApplicationRecord + scope :by_designation, -> { all.map { |ell| [ell.designation, ell] }.to_h } + + include FriendlyId + + friendly_id :designation, use: [:slugged] +end diff --git a/app/models/gender.rb b/app/models/gender.rb index 5bfdc766..50f4d74c 100644 --- a/app/models/gender.rb +++ b/app/models/gender.rb @@ -1,5 +1,5 @@ class Gender < ApplicationRecord - scope :gender_hash, lambda { + scope :by_qualtrics_code, lambda { all.map { |gender| [gender.qualtrics_code, gender] }.to_h } end diff --git a/app/models/income.rb b/app/models/income.rb index 447f8465..f43be8d1 100644 --- a/app/models/income.rb +++ b/app/models/income.rb @@ -1,5 +1,6 @@ class Income < ApplicationRecord scope :by_designation, -> { all.map { |income| [income.designation, income] }.to_h } + scope :by_slug, -> { all.map { |income| [income.slug, income] }.to_h } include FriendlyId diff --git a/app/models/school.rb b/app/models/school.rb index 53fd88a9..98367218 100644 --- a/app/models/school.rb +++ b/app/models/school.rb @@ -19,4 +19,8 @@ class School < ApplicationRecord .where(districts: { qualtrics_code: district_code }) .find_by_qualtrics_code(school_code) end + + def grades(academic_year:) + Respondent.find_by(school: self, academic_year:)&.counts_by_grade&.keys || (-1..12).to_a + end end diff --git a/app/models/survey_item_response.rb b/app/models/survey_item_response.rb index 21f05887..e777239c 100644 --- a/app/models/survey_item_response.rb +++ b/app/models/survey_item_response.rb @@ -10,6 +10,7 @@ class SurveyItemResponse < ActiveRecord::Base belongs_to :student, foreign_key: :student_id, optional: true belongs_to :gender belongs_to :income + belongs_to :ell has_one :measure, through: :survey_item @@ -32,9 +33,15 @@ class SurveyItemResponse < ActiveRecord::Base academic_year:, income:, grade: school.grades(academic_year:)).group(:survey_item).having("count(*) >= 10").average(:likert_score) } + scope :averages_for_ell, lambda { |survey_items, school, academic_year, ell| + SurveyItemResponse.where(survey_item: survey_items, school:, + academic_year:, ell:, grade: school.grades(academic_year:)).group(:survey_item).having("count(*) >= 10").average(:likert_score) + } + scope :averages_for_race, lambda { |school, academic_year, race| SurveyItemResponse.joins("JOIN student_races on survey_item_responses.student_id = student_races.student_id JOIN students on students.id = student_races.student_id").where( school:, academic_year:, grade: school.grades(academic_year:) ).where("student_races.race_id": race.id).group(:survey_item_id).having("count(*) >= 10").average(:likert_score) } end + diff --git a/app/presenters/analyze/graph/column/ell_column/ell.rb b/app/presenters/analyze/graph/column/ell_column/ell.rb new file mode 100644 index 00000000..cba07408 --- /dev/null +++ b/app/presenters/analyze/graph/column/ell_column/ell.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Analyze + module Graph + module Column + module EllColumn + class Ell < GroupedBarColumnPresenter + include Analyze::Graph::Column::EllColumn::ScoreForEll + include Analyze::Graph::Column::EllColumn::EllCount + def label + %w[ELL] + end + + def basis + "student" + end + + def show_irrelevancy_message? + false + end + + def show_insufficient_data_message? + false + end + + def ell + ::Ell.find_by_slug "ell" + end + end + end + end + end +end diff --git a/app/presenters/analyze/graph/column/ell_column/ell_count.rb b/app/presenters/analyze/graph/column/ell_column/ell_count.rb new file mode 100644 index 00000000..fa20518a --- /dev/null +++ b/app/presenters/analyze/graph/column/ell_column/ell_count.rb @@ -0,0 +1,18 @@ +module Analyze + module Graph + module Column + module EllColumn + module EllCount + def type + :student + end + + def n_size(year_index) + SurveyItemResponse.where(ell:, survey_item: measure.student_survey_items, school:, grade: grades(year_index), + academic_year: academic_years[year_index]).select(:response_id).distinct.count + end + end + end + end + end +end diff --git a/app/presenters/analyze/graph/column/ell_column/not_ell.rb b/app/presenters/analyze/graph/column/ell_column/not_ell.rb new file mode 100644 index 00000000..ba93d5f7 --- /dev/null +++ b/app/presenters/analyze/graph/column/ell_column/not_ell.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Analyze + module Graph + module Column + module EllColumn + class NotEll < GroupedBarColumnPresenter + include Analyze::Graph::Column::EllColumn::ScoreForEll + include Analyze::Graph::Column::EllColumn::EllCount + def label + %w[Not-ELL] + end + + def basis + "student" + end + + def show_irrelevancy_message? + false + end + + def show_insufficient_data_message? + false + end + + def ell + ::Ell.find_by_slug "not-ell" + end + end + end + end + end +end diff --git a/app/presenters/analyze/graph/column/ell_column/score_for_ell.rb b/app/presenters/analyze/graph/column/ell_column/score_for_ell.rb new file mode 100644 index 00000000..cd39cd0f --- /dev/null +++ b/app/presenters/analyze/graph/column/ell_column/score_for_ell.rb @@ -0,0 +1,42 @@ +module Analyze + module Graph + module Column + module EllColumn + module ScoreForEll + def score(year_index) + academic_year = academic_years[year_index] + meets_student_threshold = sufficient_student_responses?(academic_year:) + return Score::NIL_SCORE unless meets_student_threshold + + averages = SurveyItemResponse.averages_for_ell(measure.student_survey_items, school, academic_year, + ell) + average = bubble_up_averages(averages:).round(2) + + Score.new(average:, + meets_teacher_threshold: false, + meets_student_threshold:, + meets_admin_data_threshold: false) + 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 sufficient_student_responses?(academic_year:) + return false unless measure.subcategory.response_rate(school:, academic_year:).meets_student_threshold? + + yearly_counts = SurveyItemResponse.where(school:, academic_year:, + ell:, survey_item: measure.student_survey_items).group(:ell).select(:response_id).distinct(:response_id).count + yearly_counts.any? do |count| + count[1] >= 10 + end + end + end + end + end + end +end diff --git a/app/presenters/analyze/graph/column/ell_column/unknown.rb b/app/presenters/analyze/graph/column/ell_column/unknown.rb new file mode 100644 index 00000000..37f077ae --- /dev/null +++ b/app/presenters/analyze/graph/column/ell_column/unknown.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Analyze + module Graph + module Column + module EllColumn + class Unknown < GroupedBarColumnPresenter + include Analyze::Graph::Column::EllColumn::ScoreForEll + include Analyze::Graph::Column::EllColumn::EllCount + def label + %w[Unknown] + end + + def basis + "student" + end + + def show_irrelevancy_message? + false + end + + def show_insufficient_data_message? + false + end + + def ell + ::Ell.find_by_slug "unknown" + end + end + end + end + end +end diff --git a/app/presenters/analyze/graph/students_by_ell.rb b/app/presenters/analyze/graph/students_by_ell.rb new file mode 100644 index 00000000..ff96435b --- /dev/null +++ b/app/presenters/analyze/graph/students_by_ell.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true +# +module Analyze + module Graph + class StudentsByEll + include Analyze::Graph::Column::GenderColumn + attr_reader :ells + + def initialize(ells:) + @ells = ells + end + + def to_s + "Students by Ell" + end + + def slug + "students-by-ell" + end + + def columns + [].tap do |array| + ells.each do |ell| + array << column_for_ell_code(code: ell.slug) + end + array.sort_by!(&:to_s) + array << Analyze::Graph::Column::AllStudent + end + end + + private + + def column_for_ell_code(code:) + CFR[code] + end + + CFR = { + "ell" => Analyze::Graph::Column::EllColumn::Ell, + "not-ell" => Analyze::Graph::Column::EllColumn::NotEll, + "unknown" => Analyze::Graph::Column::EllColumn::Unknown + }.freeze + end + end +end diff --git a/app/presenters/analyze/graph/students_by_gender.rb b/app/presenters/analyze/graph/students_by_gender.rb index 8e3fb50a..97d33634 100644 --- a/app/presenters/analyze/graph/students_by_gender.rb +++ b/app/presenters/analyze/graph/students_by_gender.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Analyze module Graph class StudentsByGender diff --git a/app/presenters/analyze/graph/students_by_grade.rb b/app/presenters/analyze/graph/students_by_grade.rb index 267a9037..3fb113d4 100644 --- a/app/presenters/analyze/graph/students_by_grade.rb +++ b/app/presenters/analyze/graph/students_by_grade.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Analyze module Graph class StudentsByGrade @@ -9,11 +11,11 @@ module Analyze end def to_s - 'Students by Grade' + "Students by Grade" end def slug - 'students-by-grade' + "students-by-grade" end def columns diff --git a/app/presenters/analyze/graph/students_by_income.rb b/app/presenters/analyze/graph/students_by_income.rb index 148b22c1..e72b6efb 100644 --- a/app/presenters/analyze/graph/students_by_income.rb +++ b/app/presenters/analyze/graph/students_by_income.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Analyze module Graph class StudentsByIncome diff --git a/app/presenters/analyze/graph/students_by_race.rb b/app/presenters/analyze/graph/students_by_race.rb index 0c4009ae..74a7cb8f 100644 --- a/app/presenters/analyze/graph/students_by_race.rb +++ b/app/presenters/analyze/graph/students_by_race.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Analyze module Graph class StudentsByRace @@ -8,11 +10,11 @@ module Analyze end def to_s - 'Students by Race' + "Students by Race" end def slug - 'students-by-race' + "students-by-race" end def columns @@ -31,14 +33,14 @@ module Analyze end CFR = { - '1' => Analyze::Graph::Column::RaceColumn::AmericanIndian, - '2' => Analyze::Graph::Column::RaceColumn::Asian, - '3' => Analyze::Graph::Column::RaceColumn::Black, - '4' => Analyze::Graph::Column::RaceColumn::Hispanic, - '5' => Analyze::Graph::Column::RaceColumn::White, - '8' => Analyze::Graph::Column::RaceColumn::MiddleEastern, - '99' => Analyze::Graph::Column::RaceColumn::Unknown, - '100' => Analyze::Graph::Column::RaceColumn::Multiracial + "1" => Analyze::Graph::Column::RaceColumn::AmericanIndian, + "2" => Analyze::Graph::Column::RaceColumn::Asian, + "3" => Analyze::Graph::Column::RaceColumn::Black, + "4" => Analyze::Graph::Column::RaceColumn::Hispanic, + "5" => Analyze::Graph::Column::RaceColumn::White, + "8" => Analyze::Graph::Column::RaceColumn::MiddleEastern, + "99" => Analyze::Graph::Column::RaceColumn::Unknown, + "100" => Analyze::Graph::Column::RaceColumn::Multiracial }.freeze end end diff --git a/app/presenters/analyze/group/ell.rb b/app/presenters/analyze/group/ell.rb new file mode 100644 index 00000000..512b3d5b --- /dev/null +++ b/app/presenters/analyze/group/ell.rb @@ -0,0 +1,13 @@ +module Analyze + module Group + class Ell + def name + "ELL" + end + + def slug + "ell" + end + end + end +end diff --git a/app/presenters/analyze/presenter.rb b/app/presenters/analyze/presenter.rb index 9e5492d0..4353d905 100644 --- a/app/presenters/analyze/presenter.rb +++ b/app/presenters/analyze/presenter.rb @@ -54,9 +54,27 @@ module Analyze end end + def ells + @ells ||= Ell.all.order(slug: :ASC) + end + + def selected_ells + @selected_ells ||= begin + ell_params = params[:ells] + return ells unless ell_params + + ell_params.split(",").map { |ell| Ell.find_by_slug ell }.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)] + @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), + Analyze::Graph::StudentsByEll.new(ells: selected_ells)] end def graph @@ -88,7 +106,7 @@ module Analyze end def groups - @groups = [Analyze::Group::Gender.new, Analyze::Group::Grade.new, Analyze::Group::Income.new, + @groups = [Analyze::Group::Ell.new, Analyze::Group::Gender.new, Analyze::Group::Grade.new, Analyze::Group::Income.new, Analyze::Group::Race.new] end diff --git a/app/services/cleaner.rb b/app/services/cleaner.rb index e457092d..39d8c38c 100644 --- a/app/services/cleaner.rb +++ b/app/services/cleaner.rb @@ -1,6 +1,6 @@ require "fileutils" class Cleaner - attr_reader :input_filepath, :output_filepath, :log_filepath, :clean_csv, :log_csv + attr_reader :input_filepath, :output_filepath, :log_filepath def initialize(input_filepath:, output_filepath:, log_filepath:) @input_filepath = input_filepath @@ -9,18 +9,13 @@ class Cleaner initialize_directories end - def initialize_directories - create_ouput_directory - create_log_directory - end - def clean Dir.glob(Rails.root.join(input_filepath, "*.csv")).each do |filepath| puts filepath - File.open(filepath) do |_file| - clean_csv = [] - log_csv = [] - data = [] + File.open(filepath) do |file| + processed_data = process_raw_file(file:) + processed_data in [headers, clean_csv, log_csv, data] + return if data.empty? filename = filename(headers:, data:) write_csv(data: clean_csv, output_filepath:, filename:) @@ -37,23 +32,22 @@ class Cleaner range = data.first.academic_year.range districts = data.map do |row| - row.district.name + row.district.short_name end.to_set.to_a districts.join(".").to_s + "." + survey_type.to_s + "." + range + ".csv" end - def process_raw_file(file:, disaggregation_data:) + def process_raw_file(file:) clean_csv = [] log_csv = [] data = [] - headers = (CSV.parse(file.first).first << "Raw Income") << "Income" + headers = CSV.parse(file.first).first.push("Raw Income").push("Income").push("Raw ELL").push("ELL").push("Raw SpEd").push("SpEd") filtered_headers = include_all_headers(headers:) filtered_headers = remove_unwanted_headers(headers: filtered_headers) log_headers = (filtered_headers + ["Valid Duration?", "Valid Progress?", "Valid Grade?", "Valid Standard Deviation?"]).flatten - clean_csv << filtered_headers log_csv << log_headers @@ -62,7 +56,7 @@ class Cleaner file.lazy.each_slice(1000) do |lines| CSV.parse(lines.join, headers:).map do |row| values = SurveyItemValues.new(row:, headers:, genders:, - survey_items: all_survey_items, schools:, disaggregation_data:) + survey_items: all_survey_items, schools:) next unless values.valid_school? data << values @@ -104,24 +98,12 @@ class Cleaner File.write(output_filepath.join(prefix + filename), csv) end - def process_row(row:) - clean_csv << row.to_csv - log_csv << row.to_csv - end - def schools @schools ||= School.school_hash end def genders - @genders ||= begin - gender_hash = {} - - Gender.all.each do |gender| - gender_hash[gender.qualtrics_code] = gender - end - gender_hash - end + @genders ||= Gender.by_qualtrics_code end def survey_items(headers:) @@ -138,8 +120,5 @@ class Cleaner def create_log_directory FileUtils.mkdir_p log_filepath end - - def create_file(path:, filename:) - FileUtils.touch path.join(filename) - end end + diff --git a/app/services/demographic_loader.rb b/app/services/demographic_loader.rb index 345020e8..cb11a51c 100644 --- a/app/services/demographic_loader.rb +++ b/app/services/demographic_loader.rb @@ -6,12 +6,13 @@ class DemographicLoader process_race(row:) process_gender(row:) process_income(row:) + process_ell(row:) end end def self.process_race(row:) - qualtrics_code = row['Race Qualtrics Code'].to_i - designation = row['Race/Ethnicity'] + qualtrics_code = row["Race Qualtrics Code"].to_i + designation = row["Race/Ethnicity"] return unless qualtrics_code && designation if qualtrics_code.between?(6, 7) @@ -22,8 +23,8 @@ class DemographicLoader end def self.process_gender(row:) - qualtrics_code = row['Gender Qualtrics Code'].to_i - designation = row['Sex/Gender'] + qualtrics_code = row["Gender Qualtrics Code"].to_i + designation = row["Sex/Gender"] return unless qualtrics_code && designation gender = ::Gender.find_or_create_by!(qualtrics_code:, designation:) @@ -31,11 +32,18 @@ class DemographicLoader end def self.process_income(row:) - designation = row['Income'] + designation = row["Income"] return unless designation Income.find_or_create_by!(designation:) end + + def self.process_ell(row:) + designation = row["ELL"] + return unless designation + + Ell.find_or_create_by!(designation:) + end end class KnownRace @@ -50,7 +58,7 @@ end class UnknownRace def initialize(qualtrics_code:, designation:) unknown = Race.find_or_create_by!(qualtrics_code: 99) - unknown.designation = 'Race/Ethnicity Not Listed' + unknown.designation = "Race/Ethnicity Not Listed" unknown.slug = designation.parameterize unknown.save end diff --git a/app/services/disaggregation_row.rb b/app/services/disaggregation_row.rb new file mode 100644 index 00000000..f251f8b2 --- /dev/null +++ b/app/services/disaggregation_row.rb @@ -0,0 +1,54 @@ +class DisaggregationRow + attr_reader :row, :headers + + def initialize(row:, headers:) + @row = row + @headers = headers + end + + def district + @district ||= value_from(pattern: /District/i) + end + + def academic_year + @academic_year ||= value_from(pattern: /Academic\s*Year/i) + end + + def raw_income + @income ||= value_from(pattern: /Low\s*Income/i) + end + + def lasid + @lasid ||= value_from(pattern: /LASID/i) + end + + def raw_ell + @raw_ell ||= value_from(pattern: /EL Student First Year/i) + end + + def ell + @ell ||= begin + value = value_from(pattern: /EL Student First Year/i).downcase + + case value + when /lep student 1st year|LEP student not 1st year/i + "ELL" + when /Does not apply/i + "Not ELL" + else + "Unknown" + end + end + end + + def value_from(pattern:) + output = nil + matches = headers.select do |header| + pattern.match(header) + end.map { |item| item.delete("\n") } + matches.each do |match| + output ||= row[match] + end + output + end +end diff --git a/app/services/survey_item_values.rb b/app/services/survey_item_values.rb index 4fb9f1c1..e27f12dd 100644 --- a/app/services/survey_item_values.rb +++ b/app/services/survey_item_values.rb @@ -9,11 +9,12 @@ class SurveyItemValues @genders = genders @survey_items = survey_items @schools = schools - @disaggregation_data = disaggregation_data copy_likert_scores_from_variant_survey_items row["Income"] = income row["Raw Income"] = raw_income + row["Raw ELL"] = raw_ell + row["ELL"] = ell 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) @@ -134,20 +135,9 @@ class SurveyItemValues def raw_income @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]] - return "Unknown" unless disaggregation.present? - - @raw_income ||= disaggregation.income 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" @@ -158,6 +148,21 @@ class SurveyItemValues end end + def raw_ell + @raw_ell ||= value_from(pattern: /EL Student First Year|Raw\s*ELL/i) + end + + def ell + @ell ||= case raw_ell + in /lep student 1st year|LEP student not 1st year|EL Student First Year/i + "ELL" + in /Does not apply/i + "Not ELL" + else + "Unknown" + end + end + def value_from(pattern:) output = nil matches = headers.select do |header| diff --git a/app/services/survey_responses_data_loader.rb b/app/services/survey_responses_data_loader.rb index 8f5f2a3a..723ceb86 100644 --- a/app/services/survey_responses_data_loader.rb +++ b/app/services/survey_responses_data_loader.rb @@ -1,31 +1,25 @@ # frozen_string_literal: true class SurveyResponsesDataLoader - def self.load_data(filepath:, rules: [Rule::NoRule]) + def load_data(filepath:, rules: [Rule::NoRule]) File.open(filepath) do |file| headers = file.first 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:, incomes:) + rules:) end SurveyItemResponse.import survey_item_responses.compact.flatten, batch_size: 500 end end end - def self.from_file(file:, rules: []) + def from_file(file:, rules: []) headers = file.gets 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 = [] @@ -36,7 +30,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:, incomes:) + rules:) end row_count += 1 @@ -52,7 +46,23 @@ class SurveyResponsesDataLoader private - def self.process_row(row:, rules:, incomes:) + def schools + @schools = School.school_hash + end + + def genders + @genders = Gender.by_qualtrics_code + end + + def incomes + @incomes ||= Income.by_slug + end + + def ells + @ells ||= Ell.by_designation + end + + def process_row(row:, rules:) return unless row.dese_id? return unless row.school.present? @@ -60,10 +70,10 @@ class SurveyResponsesDataLoader return if rule.new(row:).skip_row? end - process_survey_items(row:, incomes:) + process_survey_items(row:) end - def self.process_survey_items(row:, incomes:) + def process_survey_items(row:) row.survey_items.map do |survey_item| likert_score = row.likert_score(survey_item_id: survey_item.survey_item_id) || next @@ -72,38 +82,33 @@ class SurveyResponsesDataLoader next end response = row.survey_item_response(survey_item:) - create_or_update_response(survey_item_response: response, likert_score:, row:, survey_item:, incomes:) + create_or_update_response(survey_item_response: response, likert_score:, row:, survey_item:) end.compact end - def self.create_or_update_response(survey_item_response:, likert_score:, row:, survey_item:, incomes:) + def create_or_update_response(survey_item_response:, likert_score:, row:, survey_item:) gender = row.gender grade = row.grade - income = incomes[row.income] + income = incomes[row.income.parameterize] + ell = ells[row.ell] if survey_item_response.present? - survey_item_response.update!(likert_score:, grade:, gender:, recorded_date: row.recorded_date, income:) + survey_item_response.update!(likert_score:, grade:, gender:, recorded_date: row.recorded_date, income:, ell:) [] 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, income:) + likert_score:, grade:, gender:, recorded_date: row.recorded_date, income:, ell:) end end - def self.survey_items(headers:) + def survey_items(headers:) SurveyItem.where(survey_item_id: get_survey_item_ids_from_headers(headers:)) end - def self.get_survey_item_ids_from_headers(headers:) + def get_survey_item_ids_from_headers(headers:) CSV.parse(headers).first .filter(&:present?) .filter { |header| header.start_with? "t-", "s-" } end - - private_class_method :process_row - private_class_method :process_survey_items - private_class_method :create_or_update_response - private_class_method :survey_items - private_class_method :get_survey_item_ids_from_headers end module StringMonkeyPatches diff --git a/app/views/analyze/_group_selectors.html.erb b/app/views/analyze/_group_selectors.html.erb index 72bd8d38..c4e58603 100644 --- a/app/views/analyze/_group_selectors.html.erb +++ b/app/views/analyze/_group_selectors.html.erb @@ -21,3 +21,7 @@ <% @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 %> + +<% @presenter.ells.each do |ell| %> + <%= render(partial: "checkboxes", locals: {id: "ell-#{ell.slug}", item: ell, selected_items: @presenter.selected_ells, name: "ell", label_text: ell.designation}) %> +<% end %> diff --git a/app/views/analyze/index.html.erb b/app/views/analyze/index.html.erb index 63ec8a1f..84737b19 100644 --- a/app/views/analyze/index.html.erb +++ b/app/views/analyze/index.html.erb @@ -13,7 +13,7 @@ <%= 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} %> - <% 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 %> + <% cache [@presenter.subcategory, @school, @presenter.selected_academic_years, @presenter.graph, @presenter.selected_races, @presenter.selected_grades, @presenter.grades, @presenter.selected_genders, @presenter.genders, @presenter.selected_ells, @presenter.ells] do %>
<% @presenter.measures.each do |measure| %>
diff --git a/data/demographics.csv b/data/demographics.csv index 3a09ce00..f95115e8 100644 --- a/data/demographics.csv +++ b/data/demographics.csv @@ -1,11 +1,11 @@ -Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income -1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged - N -2,Asian or Pacific Islander,1,Female,Economically Disadvantaged - Y -3,Black or African American,4,Non-Binary,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 +1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged - N,ELL +2,Asian or Pacific Islander,1,Female,Economically Disadvantaged - Y,Not ELL +3,Black or African American,4,Non-Binary,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,,,, diff --git a/db/migrate/20230830213521_create_ell.rb b/db/migrate/20230830213521_create_ell.rb new file mode 100644 index 00000000..d4dca8ae --- /dev/null +++ b/db/migrate/20230830213521_create_ell.rb @@ -0,0 +1,12 @@ +class CreateEll < ActiveRecord::Migration[7.0] + def change + create_table :ells do |t| + t.string :designation + t.string :slug + + t.timestamps + end + + add_index :ells, :designation, unique: true + end +end diff --git a/db/migrate/20230912223701_add_ell_to_survey_item_response.rb b/db/migrate/20230912223701_add_ell_to_survey_item_response.rb new file mode 100644 index 00000000..026bcac9 --- /dev/null +++ b/db/migrate/20230912223701_add_ell_to_survey_item_response.rb @@ -0,0 +1,5 @@ +class AddEllToSurveyItemResponse < ActiveRecord::Migration[7.0] + def change + add_reference :survey_item_responses, :ell, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index c2da769f..71d8bf6c 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[7.0].define(version: 20_230_630_215_110) do +ActiveRecord::Schema[7.0].define(version: 2023_09_12_223701) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -69,6 +69,14 @@ ActiveRecord::Schema[7.0].define(version: 20_230_630_215_110) do t.datetime "updated_at", null: false end + create_table "ells", 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_ells_on_designation", unique: true + end + create_table "genders", force: :cascade do |t| t.integer "qualtrics_code" t.string "designation" @@ -352,7 +360,7 @@ ActiveRecord::Schema[7.0].define(version: 20_230_630_215_110) do t.integer "eleven" t.integer "twelve" t.index ["academic_year_id"], name: "index_respondents_on_academic_year_id" - t.index %w[school_id academic_year_id], name: "index_respondents_on_school_id_and_academic_year_id", unique: true + t.index ["school_id", "academic_year_id"], name: "index_respondents_on_school_id_and_academic_year_id", unique: true end create_table "response_rates", force: :cascade do |t| @@ -366,7 +374,7 @@ ActiveRecord::Schema[7.0].define(version: 20_230_630_215_110) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["academic_year_id"], name: "index_response_rates_on_academic_year_id" - t.index %w[school_id subcategory_id], name: "index_response_rates_on_school_id_and_subcategory_id" + t.index ["school_id", "subcategory_id"], name: "index_response_rates_on_school_id_and_subcategory_id" t.index ["school_id"], name: "index_response_rates_on_school_id" t.index ["subcategory_id"], name: "index_response_rates_on_subcategory_id" end @@ -419,7 +427,7 @@ ActiveRecord::Schema[7.0].define(version: 20_230_630_215_110) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["race_id"], name: "index_student_races_on_race_id" - t.index %w[student_id race_id], name: "index_student_races_on_student_id_and_race_id" + t.index ["student_id", "race_id"], name: "index_student_races_on_student_id_and_race_id" t.index ["student_id"], name: "index_student_races_on_student_id" end @@ -453,12 +461,16 @@ ActiveRecord::Schema[7.0].define(version: 20_230_630_215_110) do t.integer "grade" t.bigint "gender_id" t.datetime "recorded_date" + t.bigint "ell_id" + t.bigint "income_id" t.index ["academic_year_id"], name: "index_survey_item_responses_on_academic_year_id" + t.index ["ell_id"], name: "index_survey_item_responses_on_ell_id" t.index ["gender_id"], name: "index_survey_item_responses_on_gender_id" + t.index ["income_id"], name: "index_survey_item_responses_on_income_id" t.index ["response_id"], name: "index_survey_item_responses_on_response_id" - t.index %w[school_id academic_year_id survey_item_id], name: "by_school_year_and_survey_item" - t.index %w[school_id academic_year_id], name: "index_survey_item_responses_on_school_id_and_academic_year_id" - t.index %w[school_id survey_item_id academic_year_id grade], name: "index_survey_responses_on_grade" + t.index ["school_id", "academic_year_id", "survey_item_id"], name: "by_school_year_and_survey_item" + t.index ["school_id", "academic_year_id"], name: "index_survey_item_responses_on_school_id_and_academic_year_id" + t.index ["school_id", "survey_item_id", "academic_year_id", "grade"], name: "index_survey_responses_on_grade" t.index ["school_id"], name: "index_survey_item_responses_on_school_id" t.index ["student_id"], name: "index_survey_item_responses_on_student_id" t.index ["survey_item_id"], name: "index_survey_item_responses_on_survey_item_id" @@ -508,7 +520,9 @@ ActiveRecord::Schema[7.0].define(version: 20_230_630_215_110) do add_foreign_key "student_races", "students" add_foreign_key "subcategories", "categories" add_foreign_key "survey_item_responses", "academic_years" + add_foreign_key "survey_item_responses", "ells" add_foreign_key "survey_item_responses", "genders" + add_foreign_key "survey_item_responses", "incomes" add_foreign_key "survey_item_responses", "schools" add_foreign_key "survey_item_responses", "students" add_foreign_key "survey_item_responses", "survey_items" diff --git a/lib/tasks/clean.rake b/lib/tasks/clean.rake index ef8ab445..87223190 100644 --- a/lib/tasks/clean.rake +++ b/lib/tasks/clean.rake @@ -1,33 +1,33 @@ namespace :clean do # These tasks must be run in their respective project so the correct schools are in the database - desc 'clean ecp data' + desc "clean ecp data" task ecp: :environment do - input_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'raw') - output_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'clean') - log_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'removed') + input_filepath = Rails.root.join("tmp", "data", "ecp_data", "raw") + output_filepath = Rails.root.join("tmp", "data", "ecp_data", "clean") + log_filepath = Rails.root.join("tmp", "data", "ecp_data", "removed") Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean end - desc 'clean prepped data' + desc "clean prepped data" task prepped: :environment do - input_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'prepped') - output_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'prepped', 'clean') - log_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'prepped', 'removed') + input_filepath = Rails.root.join("tmp", "data", "ecp_data", "prepped") + output_filepath = Rails.root.join("tmp", "data", "ecp_data", "prepped", "clean") + log_filepath = Rails.root.join("tmp", "data", "ecp_data", "prepped", "removed") Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean end - desc 'clean mciea data' + desc "clean mciea data" task mciea: :environment do - input_filepath = Rails.root.join('tmp', 'data', 'mciea_data', 'raw') - output_filepath = Rails.root.join('tmp', 'data', 'mciea_data', 'clean') - log_filepath = Rails.root.join('tmp', 'data', 'mciea_data', 'removed') + input_filepath = Rails.root.join("tmp", "data", "mciea_data", "raw") + output_filepath = Rails.root.join("tmp", "data", "mciea_data", "clean") + log_filepath = Rails.root.join("tmp", "data", "mciea_data", "removed") Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean end - desc 'clean rpp data' + desc "clean rpp data" task rpp: :environment do - input_filepath = Rails.root.join('tmp', 'data', 'rpp_data', 'raw') - output_filepath = Rails.root.join('tmp', 'data', 'rpp_data', 'clean') - log_filepath = Rails.root.join('tmp', 'data', 'rpp_data', 'removed') + input_filepath = Rails.root.join("tmp", "data", "rpp_data", "raw") + output_filepath = Rails.root.join("tmp", "data", "rpp_data", "clean") + log_filepath = Rails.root.join("tmp", "data", "rpp_data", "removed") Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean end end diff --git a/lib/tasks/data.rake b/lib/tasks/data.rake index 1d88bc56..4075a32d 100644 --- a/lib/tasks/data.rake +++ b/lib/tasks/data.rake @@ -29,7 +29,7 @@ namespace :data do student_count = Student.count path = '/data/survey_responses/clean/' Sftp::Directory.open(path:) do |file| - SurveyResponsesDataLoader.from_file(file:) + SurveyResponsesDataLoader.new.from_file(file:) end puts "=====================> Completed loading #{SurveyItemResponse.count - survey_item_response_count} survey responses. #{SurveyItemResponse.count} total responses in the database" diff --git a/lib/tasks/one_off.rake b/lib/tasks/one_off.rake index 0d9da30d..8441efc7 100644 --- a/lib/tasks/one_off.rake +++ b/lib/tasks/one_off.rake @@ -82,27 +82,6 @@ namespace :one_off do puts values end - desc "load survey responses for lowell schools" - task load_survey_responses_for_lowell: :environment do - survey_item_response_count = SurveyItemResponse.count - student_count = Student.count - Sftp::Directory.open(path: "/test/survey_responses/") do |file| - SurveyResponsesDataLoader.from_file(file:) - end - puts "=====================> Completed loading #{SurveyItemResponse.count - survey_item_response_count} survey responses. #{SurveyItemResponse.count} total responses in the database" - - Sftp::Directory.open(path: "/test/survey_responses/") do |file| - StudentLoader.from_file(file:, rules: [Rule::SkipNonLowellSchools]) - end - puts "=====================> Completed loading #{Student.count - student_count} students. #{Student.count} total students" - - puts "Resetting race scores" - RaceScoreLoader.reset(fast_processing: false) - puts "=====================> Completed loading #{RaceScore.count} race scores" - - Rails.cache.clear - end - desc "delete 2022-23 survey responses" task delete_survey_responses_2022_23: :environment do response_count = SurveyItemResponse.all.count @@ -127,7 +106,7 @@ namespace :one_off do path = "/data/survey_responses/2022-23/" Sftp::Directory.open(path:) do |file| - SurveyResponsesDataLoader.from_file(file:) + SurveyResponsesDataLoader.new.from_file(file:) end puts "=====================> Completed loading #{SurveyItemResponse.count - survey_item_response_count} survey responses. #{SurveyItemResponse.count} total responses in the database" diff --git a/spec/factories.rb b/spec/factories.rb index a96f0e5d..28b34ece 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,11 +1,15 @@ FactoryBot.define do factory :income do - designation { "MyString" } + designation { "DefaultIncome" } + end + + factory :ell do + designation { "DefaultEll" } end factory :gender do qualtrics_code { 1 } - designation { 'MyString' } + designation { "MyString" } end factory :race_score do @@ -68,22 +72,22 @@ FactoryBot.define do end factory :academic_year do - range { '2050-51' } + range { "2050-51" } initialize_with { AcademicYear.find_or_initialize_by(range:) } end - factory :category, class: 'Category' do + factory :category, class: "Category" do name { "A #{rand} category" } category_id { rand.to_s } - description { 'A description of a category' } + description { "A description of a category" } slug { name.parameterize } sort_index { 1 } end factory :subcategory do - name { 'A subcategory' } + name { "A subcategory" } subcategory_id { rand.to_s } - description { 'A description of a subcategory' } + description { "A description of a subcategory" } category factory :subcategory_with_measures do @@ -102,7 +106,7 @@ FactoryBot.define do factory :measure do measure_id { rand.to_s } - name { 'A Measure' } + name { "A Measure" } subcategory trait :with_student_survey_items do after(:create) do |measure| @@ -136,7 +140,7 @@ FactoryBot.define do factory :survey_item do scale - prompt { 'What do YOU think?' } + prompt { "What do YOU think?" } factory :teacher_survey_item do survey_item_id { "t-#{rand}" } watch_low_benchmark { 2.0 } diff --git a/spec/fixtures/sample_demographics.csv b/spec/fixtures/sample_demographics.csv index c60cb690..5444303e 100644 --- a/spec/fixtures/sample_demographics.csv +++ b/spec/fixtures/sample_demographics.csv @@ -1,11 +1,11 @@ -Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income -1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged – N -2,Asian or Pacific Islander,1,Female,Economically Disadvantaged – Y -3,Black or African American,4,Non-Binary,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 +1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged – N,ELL +2,Asian or Pacific Islander,1,Female,Economically Disadvantaged – Y,Not ELL +3,Black or African American,4,Non-Binary,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,,,, diff --git a/spec/fixtures/test_2020-21_student_survey_responses.csv b/spec/fixtures/test_2020-21_student_survey_responses.csv index 0ba7eb03..21cb3b2b 100644 --- a/spec/fixtures/test_2020-21_student_survey_responses.csv +++ b/spec/fixtures/test_2020-21_student_survey_responses.csv @@ -1,9 +1,8 @@ -Start Date,End Date,Response Type,IP Address,Progress,Duration (in seconds),Finished,RecordedDate,ResponseId,LASID,Recipient Last Name,Recipient First Name,Recipient Email,External Data Reference,Location Latitude,Location Longitude,Distribution Channel,User Language,district,school,DESE ID,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,s-emsa-q1,s-emsa-q2,s-emsa-q3,s-tint-q1,s-tint-q2,#N/A,s-tint-q4,s-tint-q5,s-acpr-q1,s-acpr-q2,s-acpr-q3,s-acpr-q4,#N/A,#N/A,s-cure-q3,s-cure-q4,#N/A,s-sten-q2,s-sten-q3,s-sper-q1,s-sper-q2,s-sper-q3,s-sper-q4,s-civp-q1,s-civp-q2,s-civp-q3,s-civp-q4,s-grmi-q1,#N/A,#N/A,s-grmi-q4,s-appa-q1,s-appa-q2,#N/A,s-peff-q1,s-peff-q2,s-peff-q3,s-peff-q4,s-peff-q5,s-peff-q6,s-sbel-q1,s-sbel-q2,s-sbel-q3,s-sbel-q4,s-sbel-q5,s-phys-q1,s-phys-q1-1,s-phys-q2,s-phys-q3,s-phys-q4,s-vale-q1,s-vale-q2,s-vale-q3,s-vale-q4,s-acst-q1,s-acst-q2,s-acst-q3,s-acst-q4,s-acst-q5,s-grit-q1,s-grit-q2,s-grit-q3,s-grit-q4,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,race,What is your race/ethnicity?(Please select all that apply) - Selected Choice,grade,gender,Raw Income,Income -2020-09-29 18:28:41,2020-09-29 18:48:28,0,73.249.89.226,6,1186,0,2020-09-30T18:48:50,student_survey_response_1,123456,,,,,,,anonymous,EN,1,8,1500025,,,,dddd,4,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,0,some non-integer response,6,,,,5,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,EN,,,,,1,888,11th,1,Free Lunch,Economically Disadvantaged – Y -2021-02-23 15:12:58,2021-02-23 15:13:17,0,50.207.254.114,0,19,0,2021-02-24T15:13:19,student_survey_response_2,234567,,,,,,,anonymous,EN,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,NA,,,,,,,,,,,,,,,,,,,,,EN,,,,,"2,3,4",888,10,,Not Eligible,Economically Disadvantaged – N -2021-03-31 9:50:19,2021-03-31 9:59:01,0,108.7.17.250,100,522,1,2021-03-31T09:59:02,student_survey_response_3,345678,,,,,42.53340149,-70.96530151,anonymous,EN,3,2,1500505,12,4,108,3300,7,1,,,,,,,,,,,,,,2,4,2,1,4,3,3,,,,,3,3,3,3,,,,,NA,,,,,,,,,3,2,3,3,2,1,3,3,4,1,3,3,4,4,2,4,3,3,4,3,3,3,4,3,3,3,3,3,,,,,,,,,,3,4,4,2,3,3,1,,3,,EN,Math teacher,,,,6,888,8,2,Reduced Lunch,Economically Disadvantaged – Y -2021-03-31 9:50:09,2021-03-31 10:00:16,0,67.186.188.168,100,607,1,2021-03-31T10:00:17,student_survey_response_4,456789,,,,,42.63510132,-71.30139923,anonymous,EN,3,2,1500505,12,18,108,2064,7,1,,2,2,1,,,,,,,,,,,,,,,,,,,,,,,,,3,5,3,3,,,,,,,,,,4,4,3,4,5,1,,1,5,1,3,2,4,4,1,2,1,3,2,3,3,3,4,2,5,3,4,5,5,3,3,4,3,,,,,4,4,4,4,3,5,2,,2,,EN,,,,English teacher,7,888,8,3,,Unknown -2021-03-31 9:51:39,2021-03-31 10:01:36,0,73.47.153.77,100,596,1,2021-03-31T10:01:36,student_survey_response_5,567890,,,,,42.65820313,-71.30580139,anonymous,EN,3,2,1500505,6,15,109,3710,7,1,,2,2,2,,,,,,,,,,3,3,4,3,3,3,3,4,3,4,3,4,4,5,4,3,4,3,5,2,2,3,,,,,,,,,,,,1,,2,5,1,3,3,2,4,3,5,4,,,,,,,,,,,,5,4,3,4,4,4,4,4,4,,,,,,,2,,2,,EN,,,Social Studies teacher,,"1,2,3,4,5,8,6,7",888,7,4,Free Lunch,Economically Disadvantaged – Y -2021-03-31 9:51:39,2021-03-31 10:01:36,0,73.47.153.77,100,596,1,2021-03-31T10:01:36,student_survey_response_6,,,,,,42.65820313,-71.30580139,anonymous,EN,3,2,1500505,6,15,109,3710,7,1,,2,2,2,,,,,,,,,,3,3,4,3,3,3,3,4,3,4,3,4,4,5,4,3,4,3,5,2,2,3,,,,,,,,,,,,1,,2,5,1,3,3,2,4,3,5,4,,,,,,,,,,,,5,4,3,4,4,4,4,4,4,,,,,,,2,,2,,EN,,,Social Studies teacher,,"1,2,3,4,5,8",888,3,NA,Not Eligible,Economically Disadvantaged – N -2021-03-31 9:51:39,2021-03-31 10:01:36,0,73.47.153.77,100,596,1,2021-03-31T10:01:36,student_survey_response_7,,,,,,42.65820313,-71.30580139,anonymous,EN,3,2,1500505,6,15,109,3710,7,1,,2,2,2,,,,,,,,,,3,3,4,3,3,3,3,4,3,4,3,4,4,5,4,3,4,3,5,2,2,3,,,,,,,,,,,,1,,2,5,1,3,3,2,4,3,5,4,,,,,,,,,,,,5,4,3,4,4,4,4,4,4,,,,,,,2,,2,,EN,,,Social Studies teacher,,,,4,,Reduced Lunch,Economically Disadvantaged – Y -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"1,2,3,4,5,8",,,,, +Start Date,End Date,Response Type,IP Address,Progress,Duration (in seconds),Finished,RecordedDate,ResponseId,LASID,Recipient Last Name,Recipient First Name,Recipient Email,External Data Reference,Location Latitude,Location Longitude,Distribution Channel,User Language,district,school,DESE ID,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,s-emsa-q1,s-emsa-q2,s-emsa-q3,s-tint-q1,s-tint-q2,#N/A,s-tint-q4,s-tint-q5,s-acpr-q1,s-acpr-q2,s-acpr-q3,s-acpr-q4,#N/A,#N/A,s-cure-q3,s-cure-q4,#N/A,s-sten-q2,s-sten-q3,s-sper-q1,s-sper-q2,s-sper-q3,s-sper-q4,s-civp-q1,s-civp-q2,s-civp-q3,s-civp-q4,s-grmi-q1,#N/A,#N/A,s-grmi-q4,s-appa-q1,s-appa-q2,#N/A,s-peff-q1,s-peff-q2,s-peff-q3,s-peff-q4,s-peff-q5,s-peff-q6,s-sbel-q1,s-sbel-q2,s-sbel-q3,s-sbel-q4,s-sbel-q5,s-phys-q1,s-phys-q1-1,s-phys-q2,s-phys-q3,s-phys-q4,s-vale-q1,s-vale-q2,s-vale-q3,s-vale-q4,s-acst-q1,s-acst-q2,s-acst-q3,s-acst-q4,s-acst-q5,s-grit-q1,s-grit-q2,s-grit-q3,s-grit-q4,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,#N/A,race,What is your race/ethnicity?(Please select all that apply) - Selected Choice,grade,gender,Raw Income,Income,Raw ELL,ELL +2020-09-29 18:28:41,2020-09-29 18:48:28,0,73.249.89.226,6,1186,0,2020-09-30T18:48:50,student_survey_response_1,123456,,,,,,,anonymous,EN,1,8,1500025,,,,dddd,4,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,0,some non-integer response,6,,,,5,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,EN,,,,,1,888,11th,1,Free Lunch,Economically Disadvantaged – Y,Does not apply,Not ELL +2021-02-23 15:12:58,2021-02-23 15:13:17,0,50.207.254.114,0,19,0,2021-02-24T15:13:19,student_survey_response_2,234567,,,,,,,anonymous,EN,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,NA,,,,,,,,,,,,,,,,,,,,,EN,,,,,"2,3,4",888,10,,Not Eligible,Economically Disadvantaged – N,,Unknown +2021-03-31 9:50:19,2021-03-31 9:59:01,0,108.7.17.250,100,522,1,2021-03-31T09:59:02,student_survey_response_3,345678,,,,,42.53340149,-70.96530151,anonymous,EN,3,2,1500505,12,4,108,3300,7,1,,,,,,,,,,,,,,2,4,2,1,4,3,3,,,,,3,3,3,3,,,,,NA,,,,,,,,,3,2,3,3,2,1,3,3,4,1,3,3,4,4,2,4,3,3,4,3,3,3,4,3,3,3,3,3,,,,,,,,,,3,4,4,2,3,3,1,,3,,EN,Math teacher,,,,6,888,8,2,Reduced Lunch,Economically Disadvantaged – Y,,Unknown +2021-03-31 9:50:09,2021-03-31 10:00:16,0,67.186.188.168,100,607,1,2021-03-31T10:00:17,student_survey_response_4,456789,,,,,42.63510132,-71.30139923,anonymous,EN,3,2,1500505,12,18,108,2064,7,1,,2,2,1,,,,,,,,,,,,,,,,,,,,,,,,,3,5,3,3,,,,,,,,,,4,4,3,4,5,1,,1,5,1,3,2,4,4,1,2,1,3,2,3,3,3,4,2,5,3,4,5,5,3,3,4,3,,,,,4,4,4,4,3,5,2,,2,,EN,,,,English teacher,7,888,8,3,,Unknown,LEP student not 1st year,ELL +2021-03-31 9:51:39,2021-03-31 10:01:36,0,73.47.153.77,100,596,1,2021-03-31T10:01:36,student_survey_response_5,567890,,,,,42.65820313,-71.30580139,anonymous,EN,3,2,1500505,6,15,109,3710,7,1,,2,2,2,,,,,,,,,,3,3,4,3,3,3,3,4,3,4,3,4,4,5,4,3,4,3,5,2,2,3,,,,,,,,,,,,1,,2,5,1,3,3,2,4,3,5,4,,,,,,,,,,,,5,4,3,4,4,4,4,4,4,,,,,,,2,,2,,EN,,,Social Studies teacher,,"1,2,3,4,5,8,6,7",888,7,4,Free Lunch,Economically Disadvantaged – Y,EL Student First Year,ELL +2021-03-31 9:51:39,2021-03-31 10:01:36,0,73.47.153.77,100,596,1,2021-03-31T10:01:36,student_survey_response_6,,,,,,42.65820313,-71.30580139,anonymous,EN,3,2,1500505,6,15,109,3710,7,1,,2,2,2,,,,,,,,,,3,3,4,3,3,3,3,4,3,4,3,4,4,5,4,3,4,3,5,2,2,3,,,,,,,,,,,,1,,2,5,1,3,3,2,4,3,5,4,,,,,,,,,,,,5,4,3,4,4,4,4,4,4,,,,,,,2,,2,,EN,,,Social Studies teacher,,"1,2,3,4,5,8",888,3,NA,Not Eligible,Economically Disadvantaged – N,Unknown,Unknown +2021-03-31 9:51:39,2021-03-31 10:01:36,0,73.47.153.77,100,596,1,2021-03-31T10:01:36,student_survey_response_7,,,,,,42.65820313,-71.30580139,anonymous,EN,3,2,1500505,6,15,109,3710,7,1,,2,2,2,,,,,,,,,,3,3,4,3,3,3,3,4,3,4,3,4,4,5,4,3,4,3,5,2,2,3,,,,,,,,,,,,1,,2,5,1,3,3,2,4,3,5,4,,,,,,,,,,,,5,4,3,4,4,4,4,4,4,,,,,,,2,,2,,EN,,,Social Studies teacher,,,,4,,Reduced Lunch,Economically Disadvantaged – Y,#N/A,Unknown diff --git a/spec/services/cleaner_spec.rb b/spec/services/cleaner_spec.rb index 3c358e45..24cd6658 100644 --- a/spec/services/cleaner_spec.rb +++ b/spec/services/cleaner_spec.rb @@ -2,13 +2,18 @@ require "rails_helper" require "fileutils" RSpec.describe Cleaner do - let(:district) { create(:district, name: "District1") } + let(:district) { create(:district, name: "Maynard Public Schools") } let(:second_district) { create(:district, name: "District2") } let(:school) { create(:school, dese_id: 1_740_505, district:) } - let(:second_school) { create(:school, dese_id: 222_222, district: second_district) } + let(:second_school) { create(:school, dese_id: 1_740_305, district:) } + let(:third_school) { create(:school, dese_id: 222_222, district: second_district) } let(:academic_year) { create(:academic_year, range: "2022-23") } - let(:respondents) { create(:respondent, school:, academic_year:, nine: 40, ten: 40, eleven: 40, twelve: 40) } + let(:respondents) do + create(:respondent, school:, academic_year:, one: 0, nine: 40, ten: 40, eleven: 40, twelve: 40) + create(:respondent, school: second_school, academic_year:, one: 0, four: 40, five: 40, six: 40, seven: 40, + eight: 40) + end let(:recorded_date) { "2023-04-01" } let(:input_filepath) do Rails.root.join("spec", "fixtures", "raw") @@ -22,6 +27,10 @@ RSpec.describe Cleaner do Rails.root.join("tmp", "spec", "removed") end + let(:path_to_sample_raw_file) do + File.open(Rails.root.join("spec", "fixtures", "raw", "sample_maynard_raw_student_survey.csv")) + end + let(:common_headers) do ["Recorded Date", "Dese ID", "ResponseID"] end @@ -71,6 +80,7 @@ RSpec.describe Cleaner do before :each do school second_school + third_school standard_survey_items short_form_survey_items early_education_survey_items @@ -93,9 +103,9 @@ RSpec.describe Cleaner do context ".process_raw_file" do it "sorts data into valid and invalid csvs" do - cleaner = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:) + cleaner = Cleaner.new(input_filepath:, output_filepath:, log_filepath:) processed_data = cleaner.process_raw_file( - file: path_to_sample_raw_file, disaggregation_data: cleaner.disaggregation_data + file: path_to_sample_raw_file ) processed_data in [headers, clean_csv, log_csv, data] @@ -122,22 +132,6 @@ RSpec.describe Cleaner do csv_contains_the_correct_rows(log_csv, invalid_rows) invalid_rows_are_rejected_for_the_correct_reasons(data) end - - it "adds dissaggregation data to the cleaned file " do - cleaner = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:) - processed_data = cleaner.process_raw_file( - file: path_to_sample_raw_file, disaggregation_data: cleaner.disaggregation_data - ) - processed_data in [headers, clean_csv, log_csv, data] - index_of_income = clean_csv.first.index("Income") - expect(clean_csv.second[index_of_income]).to eq "Economically Disadvantaged - Y" - - one_thousand = data.find { |row| row.response_id == "1000" } - expect(one_thousand.income).to eq "Economically Disadvantaged - Y" - - one_thousand_one = data.find { |row| row.response_id == "1001" } - expect(one_thousand_one.income).to eq "Economically Disadvantaged - N" - end end context ".filename" do @@ -151,7 +145,7 @@ RSpec.describe Cleaner do filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( headers: standard_survey_items, data: ) - expect(filename).to eq "District1.standard.2022-23.csv" + expect(filename).to eq "maynard.standard.2022-23.csv" end context "when the file is based on short form survey items" do @@ -163,7 +157,7 @@ RSpec.describe Cleaner do filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( headers: short_form_survey_items, data: ) - expect(filename).to eq "District1.short_form.2022-23.csv" + expect(filename).to eq "maynard.short_form.2022-23.csv" end end @@ -176,7 +170,7 @@ RSpec.describe Cleaner do filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( headers: early_education_survey_items, data: ) - expect(filename).to eq "District1.early_education.2022-23.csv" + expect(filename).to eq "maynard.early_education.2022-23.csv" end end context "when the file is based on teacher survey items" do @@ -188,7 +182,7 @@ RSpec.describe Cleaner do filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( headers: teacher_survey_items, data: ) - expect(filename).to eq "District1.teacher.2022-23.csv" + expect(filename).to eq "maynard.teacher.2022-23.csv" end end @@ -202,7 +196,7 @@ RSpec.describe Cleaner do filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( headers: teacher_survey_items, data: ) - expect(filename).to eq "District1.District2.teacher.2022-23.csv" + expect(filename).to eq "maynard.district2.teacher.2022-23.csv" end end end @@ -212,7 +206,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)", + 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-emsa-q1", "s-emsa-q2", "s-emsa-q3", "s-tint-q1", "s-tint-q2", "s-tint-q3", "s-tint-q4", "s-tint-q5", "s-acpr-q1", "s-acpr-q2", @@ -225,7 +219,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"].to_set.sort + "s-peff-q5-1", "s-peff-q6-1", "Raw Income", "Income", "Raw ELL", "ELL", "Raw SpEd", "SpEd"].to_set.sort end def invalid_rows_are_rejected_for_the_correct_reasons(data) @@ -308,3 +302,4 @@ def csv_contains_the_correct_rows(csv, rows) expect(csv[index + 1][response_id]).to eq row end end + diff --git a/spec/services/demographic_loader_spec.rb b/spec/services/demographic_loader_spec.rb index 1169e586..c314da6b 100644 --- a/spec/services/demographic_loader_spec.rb +++ b/spec/services/demographic_loader_spec.rb @@ -1,20 +1,24 @@ -require 'rails_helper' +require "rails_helper" describe DemographicLoader do - let(:filepath) { 'spec/fixtures/sample_demographics.csv' } + let(:filepath) { "spec/fixtures/sample_demographics.csv" } let(:race_codes) do - { 'American Indian or Alaskan Native' => 1, 'Asian or Pacific Islander' => 2, 'Black or African American' => 3, - 'Hispanic or Latinx' => 4, 'White or Caucasian' => 5, 'Race/Ethnicity Not Listed' => 99, 'Middle Eastern' => 8, 'Multiracial' => 100 } + { "American Indian or Alaskan Native" => 1, "Asian or Pacific Islander" => 2, "Black or African American" => 3, + "Hispanic or Latinx" => 4, "White or Caucasian" => 5, "Race/Ethnicity Not Listed" => 99, "Middle Eastern" => 8, "Multiracial" => 100 } end let(:gender_codes) do { - 'Female' => 1, 'Male' => 2, 'Non-Binary' => 4, 'Unknown' => 99 + "Female" => 1, "Male" => 2, "Non-Binary" => 4, "Unknown" => 99 } end let(:incomes) do - ['Economically Disadvantaged – N', 'Economically Disadvantaged – Y', 'Unknown'] + ["Economically Disadvantaged – N", "Economically Disadvantaged – Y", "Unknown"] + end + + let(:ells) do + ["ELL", "Not ELL", "Unknown"] end before :each do @@ -25,12 +29,12 @@ describe DemographicLoader do DatabaseCleaner.clean end - describe 'self.load_data' do - it 'does not load qualtrics categories for `prefer not to disclose` or `prefer to self-describe`' do + describe "self.load_data" do + it "does not load qualtrics categories for `prefer not to disclose` or `prefer to self-describe`" do expect(Race.find_by_qualtrics_code(6)).to be nil end - it 'loads all racial designations' do + it "loads all racial designations" do expect(Race.all.count).to eq 8 race_codes.each do |key, value| expect(Race.find_by_qualtrics_code(value)).not_to eq nil @@ -40,7 +44,7 @@ describe DemographicLoader do end end - it 'loads all gender designations' do + it "loads all gender designations" do expect(Gender.all.count).to eq 4 gender_codes.each do |key, value| @@ -51,11 +55,18 @@ describe DemographicLoader do end end - it 'loads all the income designations' do + it "loads all the income designations" do expect(Income.all.count).to eq 3 incomes.each do |income| expect(Income.find_by_designation(income).designation).to eq income end end + + it "loads all the ells designations" do + expect(Ell.all.count).to eq 3 + ells.each do |ell| + expect(Ell.find_by_designation(ell).designation).to eq ell + end + end end end diff --git a/spec/services/disaggregation_loader_spec.rb b/spec/services/disaggregation_loader_spec.rb new file mode 100644 index 00000000..d8ff718e --- /dev/null +++ b/spec/services/disaggregation_loader_spec.rb @@ -0,0 +1,39 @@ +require "rails_helper" +require "fileutils" + +RSpec.describe DisaggregationLoader do + let(:path) do + Rails.root.join("spec", "fixtures", "disaggregation") + end + let(:academic_year) { create(:academic_year, range: "2022-23") } + let(:district) { create(:district, name: "Maynard Public Schools") } + context ".load" do + it "loads data from the file into a hash" do + data = DisaggregationLoader.new(path:).load + expect(data.values.first.lasid).to eq("1") + expect(data.values.first.academic_year).to eq("2022-23") + expect(data.values.first.district).to eq("Maynard Public Schools") + + expect(data.values.last.lasid).to eq("500") + expect(data.values.last.academic_year).to eq("2022-23") + expect(data.values.last.district).to eq("Maynard Public Schools") + end + + it "loads income data" do + data = DisaggregationLoader.new(path:).load + expect(data.values.first.raw_income).to eq("Free Lunch") + expect(data.values.last.raw_income).to eq("Not Eligible") + + expect(data[["1", "Maynard Public Schools", "2022-23"]].raw_income).to eq("Free Lunch") + expect(data[["2", "Maynard Public Schools", "2022-23"]].raw_income).to eq("Not Eligible") + expect(data[["3", "Maynard Public Schools", "2022-23"]].raw_income).to eq("Reduced Lunch") + end + end + + context "Creating a new loader" do + it "creates a directory for the loader file" do + DisaggregationLoader.new(path:) + expect(path).to exist + end + end +end diff --git a/spec/services/disaggregation_row_spec.rb b/spec/services/disaggregation_row_spec.rb new file mode 100644 index 00000000..fbebf064 --- /dev/null +++ b/spec/services/disaggregation_row_spec.rb @@ -0,0 +1,108 @@ +require "rails_helper" + +RSpec.describe DisaggregationRow do + let(:headers) do + ["District", "Academic Year", "LASID", "HispanicLatino", "Race", "Gender", "SpecialEdStatus", "In 504 Plan", + "LowIncome", "EL Student First Year"] + end + + context ".district" do + context "when the column heading is any upper or lowercase variant of the word district" do + it "returns the correct value for district" do + row = { "District" => "Maynard Public Schools" } + expect(DisaggregationRow.new(row:, headers:).district).to eq "Maynard Public Schools" + + headers = ["dISTRICT"] + headers in [district] + row = { district => "Maynard Public Schools" } + expect(DisaggregationRow.new(row:, headers:).district).to eq "Maynard Public Schools" + end + end + end + + context ".academic_year" do + context "when the column heading is any upper or lowercase variant of the words academic year" do + it "returns the correct value for district" do + row = { "Academic Year" => "2022-23" } + expect(DisaggregationRow.new(row:, headers:).academic_year).to eq "2022-23" + + headers = ["aCADEMIC yEAR"] + headers in [academic_year] + row = { academic_year => "2022-23" } + expect(DisaggregationRow.new(row:, headers:).academic_year).to eq "2022-23" + + headers = ["AcademicYear"] + headers in [academic_year] + row = { academic_year => "2022-23" } + expect(DisaggregationRow.new(row:, headers:).academic_year).to eq "2022-23" + end + end + end + + context ".raw_income" do + context "when the column heading is any upper or lowercase variant of the words low income" do + it "returns the correct value for low_income" do + row = { "LowIncome" => "Free Lunch" } + expect(DisaggregationRow.new(row:, headers:).raw_income).to eq "Free Lunch" + + headers = ["Low income"] + headers in [income] + row = { income => "Free Lunch" } + expect(DisaggregationRow.new(row:, headers:).raw_income).to eq "Free Lunch" + + headers = ["LoW InCOme"] + headers in [income] + row = { income => "Free Lunch" } + expect(DisaggregationRow.new(row:, headers:).raw_income).to eq "Free Lunch" + end + end + end + + context ".lasid" do + context "when the column heading is any upper or lowercase variant of the words lasid" do + it "returns the correct value for lasid" do + row = { "LASID" => "2366" } + expect(DisaggregationRow.new(row:, headers:).lasid).to eq "2366" + + headers = ["LaSiD"] + headers in [lasid] + row = { lasid => "2366" } + expect(DisaggregationRow.new(row:, headers:).lasid).to eq "2366" + end + end + end + + context ".ell" do + context "when the column heading is any upper or lowercase variant of the words 'ELL' or 'El Student First Year'" do + it "returns the correct value for a student" do + row = { "EL Student First Year" => "LEP student 1st year" } + expect(DisaggregationRow.new(row:, headers:).ell).to eq "ELL" + + headers = ["EL Student First Year"] + headers in [ell] + row = { ell => "LEP student not 1st year" } + expect(DisaggregationRow.new(row:, headers:).ell).to eq "ELL" + + headers = ["EL Student First Year"] + headers in [ell] + row = { ell => "Does not apply" } + expect(DisaggregationRow.new(row:, headers:).ell).to eq "Not ELL" + + headers = ["EL Student First Year"] + headers in [ell] + row = { ell => "Unknown" } + expect(DisaggregationRow.new(row:, headers:).ell).to eq "Unknown" + + headers = ["EL Student First Year"] + headers in [ell] + row = { ell => "Any other text" } + expect(DisaggregationRow.new(row:, headers:).ell).to eq "Unknown" + + headers = ["EL Student First Year"] + headers in [ell] + row = { ell => "" } + expect(DisaggregationRow.new(row:, headers:).ell).to eq "Unknown" + end + end + end +end diff --git a/spec/services/student_loader_spec.rb b/spec/services/student_loader_spec.rb index e78a35fb..e998ea7f 100644 --- a/spec/services/student_loader_spec.rb +++ b/spec/services/student_loader_spec.rb @@ -88,7 +88,7 @@ describe StudentLoader do describe "self.load_data" do context "load student data for all schools" do before :each do - SurveyResponsesDataLoader.load_data filepath: path_to_student_responses + SurveyResponsesDataLoader.new.load_data filepath: path_to_student_responses StudentLoader.load_data filepath: path_to_student_responses end @@ -102,7 +102,7 @@ describe StudentLoader do # TODO: get this test to run correctly. Since we are no longer seeding, we need to define schools, and districts; some Lowell, some not xcontext "When using the rule to skip non Lowell schools" do before :each do - SurveyResponsesDataLoader.load_data filepath: path_to_student_responses + SurveyResponsesDataLoader.new.load_data filepath: path_to_student_responses StudentLoader.load_data filepath: path_to_student_responses, rules: [Rule::SkipNonLowellSchools] end diff --git a/spec/services/survey_item_values_spec.rb b/spec/services/survey_item_values_spec.rb index 608260f9..29950026 100644 --- a/spec/services/survey_item_values_spec.rb +++ b/spec/services/survey_item_values_spec.rb @@ -7,12 +7,7 @@ RSpec.describe SurveyItemValues, type: :model do end let(:genders) do create(:gender, qualtrics_code: 1) - gender_hash = {} - - Gender.all.each do |gender| - gender_hash[gender.qualtrics_code] = gender - end - gender_hash + Gender.by_qualtrics_code end let(:survey_items) { [] } let(:district) { create(:district, name: "Attleboro") } @@ -169,79 +164,90 @@ RSpec.describe SurveyItemValues, type: :model do end context ".income" do - context "when no disaggregation data is provided" do - it "returns an empty string " do - disaggregation_data = {} - values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:, disaggregation_data:) - expect(values.income).to eq "Unknown" - end + before :each do + attleboro + ay_2022_23 end - context "when disaggregation data is provided" do - before :each do - attleboro - ay_2022_23 - end + it "translates Free Lunch to Economically Disadvantaged - Y" do + headers = ["LowIncome"] + row = { "LowIncome" => "Free Lunch" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.income).to eq "Economically Disadvantaged - Y" + end - it "translates Free Lunch to Economically Disadvantaged - Y" do - headers = ["District", "Academic Year", "LASID", "LowIncome"] - row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Free Lunch" } - disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) } + it "translates Reduced Lunch to Economically Disadvantaged - Y" do + headers = ["LowIncome"] + row = { "LowIncome" => "Reduced Lunch" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.income).to eq "Economically Disadvantaged - Y" + end - headers = ["LASID", "Dese Id", "RecordedDate"] - row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } - values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, - disaggregation_data:) - expect(values.income).to eq "Economically Disadvantaged - Y" - end + it "translates LowIncome to Economically Disadvantaged - Y" do + headers = ["LowIncome"] + row = { "LowIncome" => "LowIncome" } - it "translates Reduced Lunch to Economically Disadvantaged - Y" do - headers = ["District", "Academic Year", "LASID", "LowIncome"] - row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Reduced Lunch" } - disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.income).to eq "Economically Disadvantaged - Y" + end - headers = ["LASID", "Dese Id", "RecordedDate"] - row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } - values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, - disaggregation_data:) - expect(values.income).to eq "Economically Disadvantaged - Y" - end + it "translates Not Eligible to Economically Disadvantaged - N" do + headers = ["LowIncome"] + row = { "LowIncome" => "Not Eligible" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.income).to eq "Economically Disadvantaged - N" + end - it "translates LowIncome to Economically Disadvantaged - Y" do - headers = ["District", "Academic Year", "LASID", "LowIncome"] - row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "LowIncome" } - disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) } + it "translates blanks to Unknown" do + headers = ["LowIncome"] + row = { "LowIncome" => "" } - headers = ["LASID", "Dese Id", "RecordedDate"] - row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } - values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, - disaggregation_data:) - expect(values.income).to eq "Economically Disadvantaged - Y" - end + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.income).to eq "Unknown" + end + end - it "translates Not Eligible to Economically Disadvantaged - N" do - headers = ["District", "Academic Year", "LASID", "LowIncome"] - row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Not Eligible" } - disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) } + context ".ell" do + before :each do + attleboro + ay_2022_23 + end - headers = ["LASID", "Dese Id", "RecordedDate"] - row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } - values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, - disaggregation_data:) - expect(values.income).to eq "Economically Disadvantaged - N" - end + it 'translates "LEP Student 1st Year" or "LEP Student Not 1st Year" into ELL' do + headers = ["Raw ELL"] + row = { "Raw ELL" => "LEP Student 1st Year" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.ell).to eq "ELL" - it "translates blanks to Unknown" do - headers = ["District", "Academic Year", "LASID", "LowIncome"] - row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "" } - disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) } + row = { "Raw ELL" => "LEP Student Not 1st Year" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.ell).to eq "ELL" - headers = ["LASID", "Dese Id", "RecordedDate"] - row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } - values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, - disaggregation_data:) - expect(values.income).to eq "Unknown" - end + row = { "Raw ELL" => "LEP Student Not 1st Year" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.ell).to eq "ELL" + end + + it 'translates "Does not Apply" into "Not ELL"' do + headers = ["Raw ELL"] + row = { "Raw ELL" => "Does not apply" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.ell).to eq "Not ELL" + + row = { "Raw ELL" => "Does Not APPLY" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.ell).to eq "Not ELL" + end + + it 'tranlsates blanks into "Unknown"' do + headers = ["Raw ELL"] + row = { "Raw ELL" => "" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.ell).to eq "Unknown" + + row = { "Raw ELL" => "Anything else" } + values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) + expect(values.ell).to eq "Unknown" end end diff --git a/spec/services/survey_responses_data_loader_spec.rb b/spec/services/survey_responses_data_loader_spec.rb index 0558a6ce..d0d70fae 100644 --- a/spec/services/survey_responses_data_loader_spec.rb +++ b/spec/services/survey_responses_data_loader_spec.rb @@ -1,59 +1,62 @@ -require 'rails_helper' +require "rails_helper" describe SurveyResponsesDataLoader do - let(:path_to_teacher_responses) { Rails.root.join('spec', 'fixtures', 'test_2020-21_teacher_survey_responses.csv') } - let(:path_to_student_responses) { Rails.root.join('spec', 'fixtures', 'test_2020-21_student_survey_responses.csv') } + let(:path_to_teacher_responses) { Rails.root.join("spec", "fixtures", "test_2020-21_teacher_survey_responses.csv") } + let(:path_to_student_responses) { Rails.root.join("spec", "fixtures", "test_2020-21_student_survey_responses.csv") } let(:path_to_butler_student_responses) do - Rails.root.join('spec', 'fixtures', 'test_2022-23_butler_student_survey_responses.csv') + Rails.root.join("spec", "fixtures", "test_2022-23_butler_student_survey_responses.csv") end - let(:ay_2020_21) { create(:academic_year, range: '2020-21') } - let(:ay_2022_23) { create(:academic_year, range: '2022-23') } + let(:ay_2020_21) { create(:academic_year, range: "2020-21") } + let(:ay_2022_23) { create(:academic_year, range: "2022-23") } - let(:school) { create(:school, name: 'Lee Elementary School', slug: 'lee-elementary-school', dese_id: 1_500_025) } - let(:lowell) { create(:district, name: 'Lowell', slug: 'lowell') } + let(:school) { create(:school, name: "Lee Elementary School", slug: "lee-elementary-school", dese_id: 1_500_025) } + let(:lowell) { create(:district, name: "Lowell", slug: "lowell") } let(:second_school) do - create(:school, name: 'Lee Middle High School', slug: 'lee-middle-high-school', dese_id: 1_500_505, + create(:school, name: "Lee Middle High School", slug: "lee-middle-high-school", dese_id: 1_500_505, district: lowell) end let(:butler_school) do - create(:school, name: 'Butler Elementary School', slug: 'butler-elementary-school', dese_id: 1_600_310, + create(:school, name: "Butler Elementary School", slug: "butler-elementary-school", dese_id: 1_600_310, district: lowell) 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') } - let(:t_coll_q2) { create(:survey_item, survey_item_id: 't-coll-q2') } - let(:t_coll_q3) { create(:survey_item, survey_item_id: 't-coll-q3') } - let(:t_sach_q1) { create(:survey_item, survey_item_id: 't-sach-q1') } - let(:t_sach_q2) { create(:survey_item, survey_item_id: 't-sach-q2') } - let(:t_sach_q3) { create(:survey_item, survey_item_id: 't-sach-q3') } - - let(:s_phys_q1) { create(:survey_item, survey_item_id: 's-phys-q1') } - let(:s_phys_q2) { create(:survey_item, survey_item_id: 's-phys-q2') } - let(:s_phys_q3) { create(:survey_item, survey_item_id: 's-phys-q3') } - let(:s_phys_q4) { create(:survey_item, survey_item_id: 's-phys-q4') } - let(:s_vale_q1) { create(:survey_item, survey_item_id: 's-phys-q1') } - let(:s_vale_q2) { create(:survey_item, survey_item_id: 's-phys-q2') } - let(:s_vale_q3) { create(:survey_item, survey_item_id: 's-phys-q3') } - let(:s_vale_q4) { create(:survey_item, survey_item_id: 's-phys-q4') } - let(:s_acst_q1) { create(:survey_item, survey_item_id: 's-acst-q1') } - let(:s_acst_q2) { create(:survey_item, survey_item_id: 's-acst-q2') } - let(:s_acst_q3) { create(:survey_item, survey_item_id: 's-acst-q3') } - let(:s_acst_q4) { create(:survey_item, survey_item_id: 's-acst-q4') } - let(:s_emsa_q1) { create(:survey_item, survey_item_id: 's-emsa-q1') } - let(:s_emsa_q2) { create(:survey_item, survey_item_id: 's-emsa-q2') } - let(:s_emsa_q3) { create(:survey_item, survey_item_id: 's-emsa-q3') } + 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") } + let(:t_coll_q2) { create(:survey_item, survey_item_id: "t-coll-q2") } + let(:t_coll_q3) { create(:survey_item, survey_item_id: "t-coll-q3") } + let(:t_sach_q1) { create(:survey_item, survey_item_id: "t-sach-q1") } + let(:t_sach_q2) { create(:survey_item, survey_item_id: "t-sach-q2") } + let(:t_sach_q3) { create(:survey_item, survey_item_id: "t-sach-q3") } + + let(:s_phys_q1) { create(:survey_item, survey_item_id: "s-phys-q1") } + let(:s_phys_q2) { create(:survey_item, survey_item_id: "s-phys-q2") } + let(:s_phys_q3) { create(:survey_item, survey_item_id: "s-phys-q3") } + let(:s_phys_q4) { create(:survey_item, survey_item_id: "s-phys-q4") } + let(:s_vale_q1) { create(:survey_item, survey_item_id: "s-phys-q1") } + let(:s_vale_q2) { create(:survey_item, survey_item_id: "s-phys-q2") } + let(:s_vale_q3) { create(:survey_item, survey_item_id: "s-phys-q3") } + let(:s_vale_q4) { create(:survey_item, survey_item_id: "s-phys-q4") } + let(:s_acst_q1) { create(:survey_item, survey_item_id: "s-acst-q1") } + let(:s_acst_q2) { create(:survey_item, survey_item_id: "s-acst-q2") } + let(:s_acst_q3) { create(:survey_item, survey_item_id: "s-acst-q3") } + let(:s_acst_q4) { create(:survey_item, survey_item_id: "s-acst-q4") } + let(:s_emsa_q1) { create(:survey_item, survey_item_id: "s-emsa-q1") } + let(:s_emsa_q2) { create(:survey_item, survey_item_id: "s-emsa-q2") } + let(:s_emsa_q3) { create(:survey_item, survey_item_id: "s-emsa-q3") } let(:female) { create(:gender, qualtrics_code: 1) } let(:male) { create(:gender, qualtrics_code: 2) } let(:another_gender) { create(:gender, qualtrics_code: 3) } let(:non_binary) { create(:gender, qualtrics_code: 4) } let(:unknown_gender) { create(:gender, qualtrics_code: 99) } - let(:low_income) { create(:income, designation: 'Economically Disadvantaged – Y') } - let(:high_income) { create(:income, designation: 'Economically Disadvantaged – N') } - let(:unknown_income) { create(:income, designation: 'Unknown') } + let(:low_income) { create(:income, designation: "Economically Disadvantaged – Y") } + let(:high_income) { create(:income, designation: "Economically Disadvantaged – N") } + let(:unknown_income) { create(:income, designation: "Unknown") } + let(:yes_ell) { create(:ell, designation: "ELL") } + let(:not_ell) { create(:ell, designation: "Not ELL") } + let(:unknown_ell) { create(:ell, designation: "Unknown") } let(:setup) do ay_2020_21 @@ -92,18 +95,21 @@ describe SurveyResponsesDataLoader do low_income high_income unknown_income + yes_ell + not_ell + unknown_ell end before :each do setup end - describe 'loading teacher survey responses' do + describe "loading teacher survey responses" do before do - SurveyResponsesDataLoader.load_data filepath: path_to_teacher_responses + SurveyResponsesDataLoader.new.load_data filepath: path_to_teacher_responses end - it 'ensures teacher responses load correctly' do + it "ensures teacher responses load correctly" do assigns_academic_year_to_survey_item_responses assigns_school_to_the_survey_item_responses assigns_recorded_date_to_teacher_responses @@ -114,12 +120,12 @@ describe SurveyResponsesDataLoader do end end - describe 'student survey responses' do + describe "student survey responses" do before do - SurveyResponsesDataLoader.load_data filepath: path_to_student_responses + SurveyResponsesDataLoader.new.load_data filepath: path_to_student_responses end - it 'ensures student responses load correctly' do + it "ensures student responses load correctly" do assigns_academic_year_to_student_survey_item_responses assigns_school_to_student_survey_item_responses assigns_recorded_date_to_student_responses @@ -129,85 +135,86 @@ describe SurveyResponsesDataLoader do assigns_grade_level_to_responses assigns_gender_to_responses assigns_income_to_responses + assigns_ell_to_responses is_idempotent_for_students end - context 'when updating student survey responses from another csv file' do + context "when updating student survey responses from another csv file" do before :each do - SurveyResponsesDataLoader.load_data filepath: Rails.root.join('spec', 'fixtures', - 'secondary_test_2020-21_student_survey_responses.csv') + SurveyResponsesDataLoader.new.load_data filepath: Rails.root.join("spec", "fixtures", + "secondary_test_2020-21_student_survey_responses.csv") end - it 'updates the likert score to the score on the new csv file' do - s_emsa_q1 = SurveyItem.find_by_survey_item_id 's-emsa-q1' - expect(SurveyItemResponse.where(response_id: 'student_survey_response_3', + it "updates the likert score to the score on the new csv file" do + s_emsa_q1 = SurveyItem.find_by_survey_item_id "s-emsa-q1" + expect(SurveyItemResponse.where(response_id: "student_survey_response_3", survey_item: s_emsa_q1).first.likert_score).to eq 1 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_4', + expect(SurveyItemResponse.where(response_id: "student_survey_response_4", survey_item: s_emsa_q1).first.likert_score).to eq 1 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_5', + expect(SurveyItemResponse.where(response_id: "student_survey_response_5", survey_item: s_emsa_q1).first.likert_score).to eq 1 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_5', + expect(SurveyItemResponse.where(response_id: "student_survey_response_5", survey_item: s_acst_q3).first.likert_score).to eq 4 end end end # figure out why this is failing - describe 'when using Lowell rules to skip rows in the csv file' do + describe "when using Lowell rules to skip rows in the csv file" do before :each do - SurveyResponsesDataLoader.load_data filepath: path_to_student_responses, - rules: [Rule::SkipNonLowellSchools] + SurveyResponsesDataLoader.new.load_data filepath: path_to_student_responses, + rules: [Rule::SkipNonLowellSchools] end - it 'rejects any non-lowell school' do - expect(SurveyItemResponse.where(response_id: 'student_survey_response_1').count).to eq 0 + it "rejects any non-lowell school" do + expect(SurveyItemResponse.where(response_id: "student_survey_response_1").count).to eq 0 expect(SurveyItemResponse.count).to eq 69 end - it 'loads the correct number of responses for lowell schools' do - expect(SurveyItemResponse.where(response_id: 'student_survey_response_2').count).to eq 0 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_3').count).to eq 12 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_4').count).to eq 15 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_5').count).to eq 14 + it "loads the correct number of responses for lowell schools" do + expect(SurveyItemResponse.where(response_id: "student_survey_response_2").count).to eq 0 + expect(SurveyItemResponse.where(response_id: "student_survey_response_3").count).to eq 12 + expect(SurveyItemResponse.where(response_id: "student_survey_response_4").count).to eq 15 + expect(SurveyItemResponse.where(response_id: "student_survey_response_5").count).to eq 14 end - context 'when loading 22-23 butler survey responses' do + context "when loading 22-23 butler survey responses" do before :each do - SurveyResponsesDataLoader.load_data filepath: path_to_butler_student_responses, - rules: [Rule::SkipNonLowellSchools] + SurveyResponsesDataLoader.new.load_data filepath: path_to_butler_student_responses, + rules: [Rule::SkipNonLowellSchools] end - it 'loads all the responses for Butler' do + it "loads all the responses for Butler" do expect(SurveyItemResponse.where(school: butler_school).count).to eq 56 end - it 'blank entries for grade get loaded as nils, not zero values' do - expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_1').first.grade).to eq 7 - expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_2').first.grade).to eq 7 - expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_3').first.grade).to eq 7 - expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_4').first.grade).to eq 5 - expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_5').first.grade).to eq 7 - expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_6').first.grade).to eq 6 - expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_7').first.grade).to eq nil - expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_8').first.grade).to eq 0 + it "blank entries for grade get loaded as nils, not zero values" do + expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_1").first.grade).to eq 7 + expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_2").first.grade).to eq 7 + expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_3").first.grade).to eq 7 + expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_4").first.grade).to eq 5 + expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_5").first.grade).to eq 7 + expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_6").first.grade).to eq 6 + expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_7").first.grade).to eq nil + expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_8").first.grade).to eq 0 end end end end def assigns_academic_year_to_survey_item_responses - expect(SurveyItemResponse.find_by_response_id('teacher_survey_response_1').academic_year).to eq ay_2020_21 + expect(SurveyItemResponse.find_by_response_id("teacher_survey_response_1").academic_year).to eq ay_2020_21 end def assigns_school_to_the_survey_item_responses - expect(SurveyItemResponse.find_by_response_id('teacher_survey_response_1').school).to eq school + expect(SurveyItemResponse.find_by_response_id("teacher_survey_response_1").school).to eq school end def loads_survey_item_responses_for_a_given_survey_response - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_1').count).to eq 5 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_2').count).to eq 0 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_3').count).to eq 8 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_4').count).to eq 8 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_5').count).to eq 8 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_1").count).to eq 5 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_2").count).to eq 0 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_3").count).to eq 8 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_4").count).to eq 8 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_5").count).to eq 8 end def loads_all_survey_item_responses_for_a_given_survey_item @@ -216,44 +223,44 @@ def loads_all_survey_item_responses_for_a_given_survey_item end def captures_likert_scores_for_survey_item_responses - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_1').where(survey_item: t_pcom_q2)).to be_empty - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_1').where(survey_item: t_pcom_q3).first.likert_score).to eq 3 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_1").where(survey_item: t_pcom_q2)).to be_empty + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_1").where(survey_item: t_pcom_q3).first.likert_score).to eq 3 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_2').where(survey_item: t_pcom_q2)).to be_empty - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_2').where(survey_item: t_pcom_q3)).to be_empty + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_2").where(survey_item: t_pcom_q2)).to be_empty + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_2").where(survey_item: t_pcom_q3)).to be_empty - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_3').where(survey_item: t_pcom_q2).first.likert_score).to eq 5 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_3').where(survey_item: t_pcom_q3).first.likert_score).to eq 5 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_3").where(survey_item: t_pcom_q2).first.likert_score).to eq 5 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_3").where(survey_item: t_pcom_q3).first.likert_score).to eq 5 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_4').where(survey_item: t_pcom_q2).first.likert_score).to eq 4 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_4').where(survey_item: t_pcom_q3).first.likert_score).to eq 4 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_4").where(survey_item: t_pcom_q2).first.likert_score).to eq 4 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_4").where(survey_item: t_pcom_q3).first.likert_score).to eq 4 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_5').where(survey_item: t_pcom_q2).first.likert_score).to eq 2 - expect(SurveyItemResponse.where(response_id: 'teacher_survey_response_5').where(survey_item: t_pcom_q3).first.likert_score).to eq 4 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_5").where(survey_item: t_pcom_q2).first.likert_score).to eq 2 + expect(SurveyItemResponse.where(response_id: "teacher_survey_response_5").where(survey_item: t_pcom_q3).first.likert_score).to eq 4 end def is_idempotent number_of_survey_item_responses = SurveyItemResponse.count - SurveyResponsesDataLoader.load_data filepath: path_to_teacher_responses + SurveyResponsesDataLoader.new.load_data filepath: path_to_teacher_responses expect(SurveyItemResponse.count).to eq number_of_survey_item_responses end def assigns_academic_year_to_student_survey_item_responses - expect(SurveyItemResponse.find_by_response_id('student_survey_response_3').academic_year).to eq ay_2020_21 + expect(SurveyItemResponse.find_by_response_id("student_survey_response_3").academic_year).to eq ay_2020_21 end def assigns_school_to_student_survey_item_responses - expect(SurveyItemResponse.find_by_response_id('student_survey_response_3').school).to eq second_school + expect(SurveyItemResponse.find_by_response_id("student_survey_response_3").school).to eq second_school end def loads_student_survey_item_response_values - expect(SurveyItemResponse.where(response_id: 'student_survey_response_1').count).to eq 3 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_2').count).to eq 0 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_3').count).to eq 12 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_4').count).to eq 15 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_5').count).to eq 14 + expect(SurveyItemResponse.where(response_id: "student_survey_response_1").count).to eq 3 + expect(SurveyItemResponse.where(response_id: "student_survey_response_2").count).to eq 0 + expect(SurveyItemResponse.where(response_id: "student_survey_response_3").count).to eq 12 + expect(SurveyItemResponse.where(response_id: "student_survey_response_4").count).to eq 15 + expect(SurveyItemResponse.where(response_id: "student_survey_response_5").count).to eq 14 end def student_survey_item_response_count_matches_expected @@ -262,37 +269,37 @@ def student_survey_item_response_count_matches_expected end def captures_likert_scores_for_student_survey_item_responses - expect(SurveyItemResponse.where(response_id: 'student_survey_response_1').where(survey_item: s_phys_q1).first.likert_score).to eq 3 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_1').where(survey_item: s_phys_q2)).to be_empty + expect(SurveyItemResponse.where(response_id: "student_survey_response_1").where(survey_item: s_phys_q1).first.likert_score).to eq 3 + expect(SurveyItemResponse.where(response_id: "student_survey_response_1").where(survey_item: s_phys_q2)).to be_empty - expect(SurveyItemResponse.where(response_id: 'student_survey_response_2').where(survey_item: s_phys_q1)).to be_empty - expect(SurveyItemResponse.where(response_id: 'student_survey_response_2').where(survey_item: s_phys_q2)).to be_empty + expect(SurveyItemResponse.where(response_id: "student_survey_response_2").where(survey_item: s_phys_q1)).to be_empty + expect(SurveyItemResponse.where(response_id: "student_survey_response_2").where(survey_item: s_phys_q2)).to be_empty - expect(SurveyItemResponse.where(response_id: 'student_survey_response_3').where(survey_item: s_phys_q1).first.likert_score).to eq 1 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_3').where(survey_item: s_phys_q2).first.likert_score).to eq 3 + expect(SurveyItemResponse.where(response_id: "student_survey_response_3").where(survey_item: s_phys_q1).first.likert_score).to eq 1 + expect(SurveyItemResponse.where(response_id: "student_survey_response_3").where(survey_item: s_phys_q2).first.likert_score).to eq 3 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_4').where(survey_item: s_phys_q1).first.likert_score).to eq 1 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_4').where(survey_item: s_phys_q2).first.likert_score).to eq 1 + expect(SurveyItemResponse.where(response_id: "student_survey_response_4").where(survey_item: s_phys_q1).first.likert_score).to eq 1 + expect(SurveyItemResponse.where(response_id: "student_survey_response_4").where(survey_item: s_phys_q2).first.likert_score).to eq 1 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_5').where(survey_item: s_phys_q1).first.likert_score).to eq 1 - expect(SurveyItemResponse.where(response_id: 'student_survey_response_5').where(survey_item: s_phys_q2).first.likert_score).to eq 2 + expect(SurveyItemResponse.where(response_id: "student_survey_response_5").where(survey_item: s_phys_q1).first.likert_score).to eq 1 + expect(SurveyItemResponse.where(response_id: "student_survey_response_5").where(survey_item: s_phys_q2).first.likert_score).to eq 2 end def is_idempotent_for_students number_of_survey_item_responses = SurveyItemResponse.count - SurveyResponsesDataLoader.load_data filepath: path_to_student_responses + SurveyResponsesDataLoader.new.load_data filepath: path_to_student_responses expect(SurveyItemResponse.count).to eq number_of_survey_item_responses end def assigns_grade_level_to_responses - results = { 'student_survey_response_1' => 11, - 'student_survey_response_3' => 8, - 'student_survey_response_4' => 8, - 'student_survey_response_5' => 7, - 'student_survey_response_6' => 3, - 'student_survey_response_7' => 4 } + results = { "student_survey_response_1" => 11, + "student_survey_response_3" => 8, + "student_survey_response_4" => 8, + "student_survey_response_5" => 7, + "student_survey_response_6" => 3, + "student_survey_response_7" => 4 } results.each do |key, value| expect(SurveyItemResponse.where(response_id: key).all? do |response| response.grade == value @@ -301,12 +308,12 @@ def assigns_grade_level_to_responses end def assigns_gender_to_responses - results = { 'student_survey_response_1' => female, - 'student_survey_response_3' => male, - 'student_survey_response_4' => non_binary, - 'student_survey_response_5' => non_binary, - 'student_survey_response_6' => unknown_gender, - 'student_survey_response_7' => unknown_gender } + results = { "student_survey_response_1" => female, + "student_survey_response_3" => male, + "student_survey_response_4" => non_binary, + "student_survey_response_5" => non_binary, + "student_survey_response_6" => unknown_gender, + "student_survey_response_7" => unknown_gender } results.each do |key, value| expect(SurveyItemResponse.where(response_id: key).first.gender).to eq value @@ -314,36 +321,51 @@ def assigns_gender_to_responses end def assigns_recorded_date_to_student_responses - results = { 'student_survey_response_1' => '2020-09-30T18:48:50', - 'student_survey_response_3' => '2021-03-31T09:59:02', - 'student_survey_response_4' => '2021-03-31T10:00:17', - 'student_survey_response_5' => '2021-03-31T10:01:36', - 'student_survey_response_6' => '2021-03-31T10:01:37', - 'student_survey_response_7' => '2021-03-31T10:01:38' } + results = { "student_survey_response_1" => "2020-09-30T18:48:50", + "student_survey_response_3" => "2021-03-31T09:59:02", + "student_survey_response_4" => "2021-03-31T10:00:17", + "student_survey_response_5" => "2021-03-31T10:01:36", + "student_survey_response_6" => "2021-03-31T10:01:37", + "student_survey_response_7" => "2021-03-31T10:01:38" } results.each do |key, value| expect(SurveyItemResponse.find_by_response_id(key).recorded_date).to eq Date.parse(value) end end def assigns_recorded_date_to_teacher_responses - results = { 'teacher_survey_response_1' => '2020-10-16 11:09:03', - 'teacher_survey_response_3' => '2020-12-06 8:36:52', - 'teacher_survey_response_4' => '2020-12-06 8:51:25', - 'teacher_survey_response_5' => '2020-12-06 8:55:58' } + results = { "teacher_survey_response_1" => "2020-10-16 11:09:03", + "teacher_survey_response_3" => "2020-12-06 8:36:52", + "teacher_survey_response_4" => "2020-12-06 8:51:25", + "teacher_survey_response_5" => "2020-12-06 8:55:58" } results.each do |key, value| expect(SurveyItemResponse.find_by_response_id(key).recorded_date).to eq Date.parse(value) end end def assigns_income_to_responses - results = { 'student_survey_response_1' => low_income, - 'student_survey_response_3' => low_income, - 'student_survey_response_4' => unknown_income, - 'student_survey_response_5' => low_income, - 'student_survey_response_6' => high_income, - 'student_survey_response_7' => low_income } + results = { "student_survey_response_1" => low_income, + "student_survey_response_3" => low_income, + "student_survey_response_4" => unknown_income, + "student_survey_response_5" => low_income, + "student_survey_response_6" => high_income, + "student_survey_response_7" => low_income } results.each do |key, value| - expect(SurveyItemResponse.where(response_id: key).first.income).to eq value + income = SurveyItemResponse.find_by_response_id(key).income + expect(income).to eq value + end +end + +def assigns_ell_to_responses + results = { "student_survey_response_1" => not_ell, + "student_survey_response_3" => unknown_ell, + "student_survey_response_4" => yes_ell, + "student_survey_response_5" => yes_ell, + "student_survey_response_6" => unknown_ell, + "student_survey_response_7" => unknown_ell } + + results.each do |key, value| + ell = SurveyItemResponse.find_by_response_id(key).ell + expect(ell).to eq value end end