Add disaggregation by ELL

pull/2/head
rebuilt 2 years ago
parent 8d33095a48
commit 060d7aa55a

@ -28,7 +28,9 @@ export default class extends Controller {
"&incomes=" + "&incomes=" +
this.selected_items("income").join(",") + this.selected_items("income").join(",") +
"&grades=" + "&grades=" +
this.selected_items("grade").join(","); this.selected_items("grade").join(",") +
"&ells=" +
this.selected_items("ell").join(",");
this.go_to(url); this.go_to(url);
} }
@ -126,7 +128,8 @@ export default class extends Controller {
['gender', 'students-by-gender'], ['gender', 'students-by-gender'],
['grade', 'students-by-grade'], ['grade', 'students-by-grade'],
['income', 'students-by-income'], ['income', 'students-by-income'],
['race', 'students-by-race'] ['race', 'students-by-race'],
['ell', 'students-by-ell'],
]) ])
if (target.name === 'slice' || target.name === 'group') { if (target.name === 'slice' || target.name === 'group') {

@ -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

@ -1,5 +1,5 @@
class Gender < ApplicationRecord class Gender < ApplicationRecord
scope :gender_hash, lambda { scope :by_qualtrics_code, lambda {
all.map { |gender| [gender.qualtrics_code, gender] }.to_h all.map { |gender| [gender.qualtrics_code, gender] }.to_h
} }
end end

@ -1,5 +1,6 @@
class Income < ApplicationRecord class Income < ApplicationRecord
scope :by_designation, -> { all.map { |income| [income.designation, income] }.to_h } scope :by_designation, -> { all.map { |income| [income.designation, income] }.to_h }
scope :by_slug, -> { all.map { |income| [income.slug, income] }.to_h }
include FriendlyId include FriendlyId

@ -10,6 +10,7 @@ class SurveyItemResponse < ActiveRecord::Base
belongs_to :student, foreign_key: :student_id, optional: true belongs_to :student, foreign_key: :student_id, optional: true
belongs_to :gender belongs_to :gender
belongs_to :income belongs_to :income
belongs_to :ell
has_one :measure, through: :survey_item has_one :measure, through: :survey_item
@ -32,6 +33,11 @@ class SurveyItemResponse < ActiveRecord::Base
academic_year:, income:, grade: school.grades(academic_year:)).group(:survey_item).having("count(*) >= 10").average(:likert_score) 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| 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( 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:) school:, academic_year:, grade: school.grades(academic_year:)

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Analyze module Analyze
module Graph module Graph
class StudentsByGender class StudentsByGender

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Analyze module Analyze
module Graph module Graph
class StudentsByGrade class StudentsByGrade
@ -9,11 +11,11 @@ module Analyze
end end
def to_s def to_s
'Students by Grade' "Students by Grade"
end end
def slug def slug
'students-by-grade' "students-by-grade"
end end
def columns def columns

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Analyze module Analyze
module Graph module Graph
class StudentsByIncome class StudentsByIncome

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Analyze module Analyze
module Graph module Graph
class StudentsByRace class StudentsByRace
@ -8,11 +10,11 @@ module Analyze
end end
def to_s def to_s
'Students by Race' "Students by Race"
end end
def slug def slug
'students-by-race' "students-by-race"
end end
def columns def columns
@ -31,14 +33,14 @@ module Analyze
end end
CFR = { CFR = {
'1' => Analyze::Graph::Column::RaceColumn::AmericanIndian, "1" => Analyze::Graph::Column::RaceColumn::AmericanIndian,
'2' => Analyze::Graph::Column::RaceColumn::Asian, "2" => Analyze::Graph::Column::RaceColumn::Asian,
'3' => Analyze::Graph::Column::RaceColumn::Black, "3" => Analyze::Graph::Column::RaceColumn::Black,
'4' => Analyze::Graph::Column::RaceColumn::Hispanic, "4" => Analyze::Graph::Column::RaceColumn::Hispanic,
'5' => Analyze::Graph::Column::RaceColumn::White, "5" => Analyze::Graph::Column::RaceColumn::White,
'8' => Analyze::Graph::Column::RaceColumn::MiddleEastern, "8" => Analyze::Graph::Column::RaceColumn::MiddleEastern,
'99' => Analyze::Graph::Column::RaceColumn::Unknown, "99" => Analyze::Graph::Column::RaceColumn::Unknown,
'100' => Analyze::Graph::Column::RaceColumn::Multiracial "100" => Analyze::Graph::Column::RaceColumn::Multiracial
}.freeze }.freeze
end end
end end

@ -0,0 +1,13 @@
module Analyze
module Group
class Ell
def name
"ELL"
end
def slug
"ell"
end
end
end
end

@ -54,9 +54,27 @@ module Analyze
end end
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 def graphs
@graphs ||= [Analyze::Graph::AllData.new, Analyze::Graph::StudentsAndTeachers.new, Analyze::Graph::StudentsByRace.new(races: selected_races), @graphs ||= [Analyze::Graph::AllData.new,
Analyze::Graph::StudentsByGrade.new(grades: selected_grades), Analyze::Graph::StudentsByGender.new(genders: selected_genders), Analyze::Graph::StudentsByIncome.new(incomes: selected_incomes)] 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 end
def graph def graph
@ -88,7 +106,7 @@ module Analyze
end end
def groups 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] Analyze::Group::Race.new]
end end

@ -1,12 +1,11 @@
require "fileutils" require "fileutils"
class Cleaner class Cleaner
attr_reader :input_filepath, :output_filepath, :log_filepath, :disaggregation_filepath attr_reader :input_filepath, :output_filepath, :log_filepath
def initialize(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:) def initialize(input_filepath:, output_filepath:, log_filepath:)
@input_filepath = input_filepath @input_filepath = input_filepath
@output_filepath = output_filepath @output_filepath = output_filepath
@log_filepath = log_filepath @log_filepath = log_filepath
@disaggregation_filepath = disaggregation_filepath
initialize_directories initialize_directories
end end
@ -14,7 +13,7 @@ class Cleaner
Dir.glob(Rails.root.join(input_filepath, "*.csv")).each do |filepath| Dir.glob(Rails.root.join(input_filepath, "*.csv")).each do |filepath|
puts filepath puts filepath
File.open(filepath) do |file| File.open(filepath) do |file|
processed_data = process_raw_file(file:, disaggregation_data:) processed_data = process_raw_file(file:)
processed_data in [headers, clean_csv, log_csv, data] processed_data in [headers, clean_csv, log_csv, data]
return if data.empty? return if data.empty?
@ -25,10 +24,6 @@ class Cleaner
end end
end end
def disaggregation_data
@disaggregation_data ||= DisaggregationLoader.new(path: disaggregation_filepath).load
end
def filename(headers:, data:) def filename(headers:, data:)
survey_item_ids = headers.filter(&:present?).filter do |header| survey_item_ids = headers.filter(&:present?).filter do |header|
header.start_with?("s-", "t-") header.start_with?("s-", "t-")
@ -43,17 +38,16 @@ class Cleaner
districts.join(".").to_s + "." + survey_type.to_s + "." + range + ".csv" districts.join(".").to_s + "." + survey_type.to_s + "." + range + ".csv"
end end
def process_raw_file(file:, disaggregation_data:) def process_raw_file(file:)
clean_csv = [] clean_csv = []
log_csv = [] log_csv = []
data = [] 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")
filtered_headers = include_all_headers(headers:) filtered_headers = include_all_headers(headers:)
filtered_headers = remove_unwanted_headers(headers: filtered_headers) filtered_headers = remove_unwanted_headers(headers: filtered_headers)
log_headers = (filtered_headers + ["Valid Duration?", "Valid Progress?", "Valid Grade?", log_headers = (filtered_headers + ["Valid Duration?", "Valid Progress?", "Valid Grade?",
"Valid Standard Deviation?"]).flatten "Valid Standard Deviation?"]).flatten
clean_csv << filtered_headers clean_csv << filtered_headers
log_csv << log_headers log_csv << log_headers
@ -62,7 +56,7 @@ class Cleaner
file.lazy.each_slice(1000) do |lines| file.lazy.each_slice(1000) do |lines|
CSV.parse(lines.join, headers:).map do |row| CSV.parse(lines.join, headers:).map do |row|
values = SurveyItemValues.new(row:, headers:, genders:, 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? next unless values.valid_school?
data << values data << values
@ -109,7 +103,7 @@ class Cleaner
end end
def genders def genders
@genders ||= Gender.gender_hash @genders ||= Gender.by_qualtrics_code
end end
def survey_items(headers:) def survey_items(headers:)

@ -6,12 +6,13 @@ class DemographicLoader
process_race(row:) process_race(row:)
process_gender(row:) process_gender(row:)
process_income(row:) process_income(row:)
process_ell(row:)
end end
end end
def self.process_race(row:) def self.process_race(row:)
qualtrics_code = row['Race Qualtrics Code'].to_i qualtrics_code = row["Race Qualtrics Code"].to_i
designation = row['Race/Ethnicity'] designation = row["Race/Ethnicity"]
return unless qualtrics_code && designation return unless qualtrics_code && designation
if qualtrics_code.between?(6, 7) if qualtrics_code.between?(6, 7)
@ -22,8 +23,8 @@ class DemographicLoader
end end
def self.process_gender(row:) def self.process_gender(row:)
qualtrics_code = row['Gender Qualtrics Code'].to_i qualtrics_code = row["Gender Qualtrics Code"].to_i
designation = row['Sex/Gender'] designation = row["Sex/Gender"]
return unless qualtrics_code && designation return unless qualtrics_code && designation
gender = ::Gender.find_or_create_by!(qualtrics_code:, designation:) gender = ::Gender.find_or_create_by!(qualtrics_code:, designation:)
@ -31,11 +32,18 @@ class DemographicLoader
end end
def self.process_income(row:) def self.process_income(row:)
designation = row['Income'] designation = row["Income"]
return unless designation return unless designation
Income.find_or_create_by!(designation:) Income.find_or_create_by!(designation:)
end end
def self.process_ell(row:)
designation = row["ELL"]
return unless designation
Ell.find_or_create_by!(designation:)
end
end end
class KnownRace class KnownRace
@ -50,7 +58,7 @@ end
class UnknownRace class UnknownRace
def initialize(qualtrics_code:, designation:) def initialize(qualtrics_code:, designation:)
unknown = Race.find_or_create_by!(qualtrics_code: 99) 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.slug = designation.parameterize
unknown.save unknown.save
end end

@ -14,7 +14,7 @@ class DisaggregationRow
@academic_year ||= value_from(pattern: /Academic\s*Year/i) @academic_year ||= value_from(pattern: /Academic\s*Year/i)
end end
def income def raw_income
@income ||= value_from(pattern: /Low\s*Income/i) @income ||= value_from(pattern: /Low\s*Income/i)
end end
@ -22,6 +22,25 @@ class DisaggregationRow
@lasid ||= value_from(pattern: /LASID/i) @lasid ||= value_from(pattern: /LASID/i)
end 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:) def value_from(pattern:)
output = nil output = nil
matches = headers.select do |header| matches = headers.select do |header|

@ -1,7 +1,7 @@
class SurveyItemValues class SurveyItemValues
attr_reader :row, :headers, :genders, :survey_items, :schools, :disaggregation_data attr_reader :row, :headers, :genders, :survey_items, :schools
def initialize(row:, headers:, genders:, survey_items:, schools:, disaggregation_data: nil) def initialize(row:, headers:, genders:, survey_items:, schools:)
@row = row @row = row
# Remove any newlines in headers # Remove any newlines in headers
headers = headers.map { |item| item.delete("\n") if item.present? } headers = headers.map { |item| item.delete("\n") if item.present? }
@ -9,11 +9,12 @@ class SurveyItemValues
@genders = genders @genders = genders
@survey_items = survey_items @survey_items = survey_items
@schools = schools @schools = schools
@disaggregation_data = disaggregation_data
copy_likert_scores_from_variant_survey_items copy_likert_scores_from_variant_survey_items
row["Income"] = income row["Income"] = income
row["Raw Income"] = raw_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: /Race/i, secondary: /Race Secondary|Race-1/i)
copy_data_to_main_column(main: /Gender/i, secondary: /Gender Secondary|Gender-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 def raw_income
@raw_income ||= value_from(pattern: /Low\s*Income|Raw\s*Income/i) @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 end
def income def income
@income ||= value_from(pattern: /^Income$/i)
return @income if @income.present?
@income ||= case raw_income @income ||= case raw_income
in /Free\s*Lunch|Reduced\s*Lunch|Low\s*Income/i in /Free\s*Lunch|Reduced\s*Lunch|Low\s*Income/i
"Economically Disadvantaged - Y" "Economically Disadvantaged - Y"
@ -158,6 +148,21 @@ class SurveyItemValues
end end
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:) def value_from(pattern:)
output = nil output = nil
matches = headers.select do |header| matches = headers.select do |header|

@ -1,31 +1,25 @@
# frozen_string_literal: true # frozen_string_literal: true
class SurveyResponsesDataLoader class SurveyResponsesDataLoader
def self.load_data(filepath:, rules: [Rule::NoRule]) def load_data(filepath:, rules: [Rule::NoRule])
File.open(filepath) do |file| File.open(filepath) do |file|
headers = file.first headers = file.first
headers_array = CSV.parse(headers).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:) all_survey_items = survey_items(headers:)
file.lazy.each_slice(500) do |lines| file.lazy.each_slice(500) do |lines|
survey_item_responses = CSV.parse(lines.join, headers:).map do |row| 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:), process_row(row: SurveyItemValues.new(row:, headers: headers_array, genders:, survey_items: all_survey_items, schools:),
rules:, incomes:) rules:)
end end
SurveyItemResponse.import survey_item_responses.compact.flatten, batch_size: 500 SurveyItemResponse.import survey_item_responses.compact.flatten, batch_size: 500
end end
end end
end end
def self.from_file(file:, rules: []) def from_file(file:, rules: [])
headers = file.gets headers = file.gets
headers_array = CSV.parse(headers).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:) all_survey_items = survey_items(headers:)
survey_item_responses = [] survey_item_responses = []
@ -36,7 +30,7 @@ class SurveyResponsesDataLoader
CSV.parse(line, headers:).map do |row| 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:), survey_item_responses << process_row(row: SurveyItemValues.new(row:, headers: headers_array, genders:, survey_items: all_survey_items, schools:),
rules:, incomes:) rules:)
end end
row_count += 1 row_count += 1
@ -52,7 +46,23 @@ class SurveyResponsesDataLoader
private 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.dese_id?
return unless row.school.present? return unless row.school.present?
@ -60,10 +70,10 @@ class SurveyResponsesDataLoader
return if rule.new(row:).skip_row? return if rule.new(row:).skip_row?
end end
process_survey_items(row:, incomes:) process_survey_items(row:)
end end
def self.process_survey_items(row:, incomes:) def process_survey_items(row:)
row.survey_items.map do |survey_item| row.survey_items.map do |survey_item|
likert_score = row.likert_score(survey_item_id: survey_item.survey_item_id) || next likert_score = row.likert_score(survey_item_id: survey_item.survey_item_id) || next
@ -72,38 +82,33 @@ class SurveyResponsesDataLoader
next next
end end
response = row.survey_item_response(survey_item:) 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.compact
end 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 gender = row.gender
grade = row.grade grade = row.grade
income = incomes[row.income] income = incomes[row.income.parameterize]
ell = ells[row.ell]
if survey_item_response.present? 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 else
SurveyItemResponse.new(response_id: row.response_id, academic_year: row.academic_year, school: row.school, survey_item:, 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
end end
def self.survey_items(headers:) def survey_items(headers:)
SurveyItem.where(survey_item_id: get_survey_item_ids_from_headers(headers:)) SurveyItem.where(survey_item_id: get_survey_item_ids_from_headers(headers:))
end end
def self.get_survey_item_ids_from_headers(headers:) def get_survey_item_ids_from_headers(headers:)
CSV.parse(headers).first CSV.parse(headers).first
.filter(&:present?) .filter(&:present?)
.filter { |header| header.start_with? "t-", "s-" } .filter { |header| header.start_with? "t-", "s-" }
end 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 end
module StringMonkeyPatches module StringMonkeyPatches

@ -21,3 +21,7 @@
<% @presenter.incomes.each do |income| %> <% @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.label}) %> <%= render(partial: "checkboxes", locals: {id: "income-#{income.slug}", item: income, selected_items: @presenter.selected_incomes, name: "income", label_text: income.label}) %>
<% end %> <% 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 %>

@ -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: "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} %> <%= render partial: "data_filters", locals: {district: @district, school: @school, academic_year: @academic_year, category: @presenter.category, subcategory: @presenter.subcategory} %>
</div> </div>
<% cache [@presenter.subcategory, @school, @presenter.selected_academic_years, @presenter.graph, @presenter.selected_races, @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 %>
<div class="bg-color-white flex-grow-1 col-9"> <div class="bg-color-white flex-grow-1 col-9">
<% @presenter.measures.each do |measure| %> <% @presenter.measures.each do |measure| %>
<section class="mb-6"> <section class="mb-6">

@ -1,11 +1,11 @@
Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income,ELL
1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged - N 1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged - N,ELL
2,Asian or Pacific Islander,1,Female,Economically Disadvantaged - Y 2,Asian or Pacific Islander,1,Female,Economically Disadvantaged - Y,Not ELL
3,Black or African American,4,Non-Binary,Unknown 3,Black or African American,4,Non-Binary,Unknown,Unknown
4,Hispanic or Latinx,99,Unknown, 4,Hispanic or Latinx,99,Unknown,,
5,White or Caucasian,,, 5,White or Caucasian,,,,
6,Prefer not to disclose,,, 6,Prefer not to disclose,,,,
7,Prefer to self-describe,,, 7,Prefer to self-describe,,,,
8,Middle Eastern,,, 8,Middle Eastern,,,,
99,Race/Ethnicity Not Listed,,, 99,Race/Ethnicity Not Listed,,,,
100,Multiracial,,, 100,Multiracial,,,,

1 Race Qualtrics Code Race/Ethnicity Gender Qualtrics Code Sex/Gender Income ELL
2 1 American Indian or Alaskan Native 2 Male Economically Disadvantaged - N ELL
3 2 Asian or Pacific Islander 1 Female Economically Disadvantaged - Y Not ELL
4 3 Black or African American 4 Non-Binary Unknown Unknown
5 4 Hispanic or Latinx 99 Unknown
6 5 White or Caucasian
7 6 Prefer not to disclose
8 7 Prefer to self-describe
9 8 Middle Eastern
10 99 Race/Ethnicity Not Listed
11 100 Multiracial

@ -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

@ -0,0 +1,5 @@
class AddEllToSurveyItemResponse < ActiveRecord::Migration[7.0]
def change
add_reference :survey_item_responses, :ell, foreign_key: true
end
end

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_08_07_222503) 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -69,6 +69,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_07_222503) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end 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| create_table "genders", force: :cascade do |t|
t.integer "qualtrics_code" t.integer "qualtrics_code"
t.string "designation" t.string "designation"
@ -439,7 +447,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_07_222503) do
t.bigint "gender_id" t.bigint "gender_id"
t.bigint "income_id" t.bigint "income_id"
t.datetime "recorded_date" t.datetime "recorded_date"
t.bigint "ell_id"
t.index ["academic_year_id"], name: "index_survey_item_responses_on_academic_year_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 ["gender_id"], name: "index_survey_item_responses_on_gender_id"
t.index ["income_id"], name: "index_survey_item_responses_on_income_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 ["response_id"], name: "index_survey_item_responses_on_response_id"
@ -491,6 +501,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_07_222503) do
add_foreign_key "student_races", "students" add_foreign_key "student_races", "students"
add_foreign_key "subcategories", "categories" add_foreign_key "subcategories", "categories"
add_foreign_key "survey_item_responses", "academic_years" 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", "genders"
add_foreign_key "survey_item_responses", "incomes" add_foreign_key "survey_item_responses", "incomes"
add_foreign_key "survey_item_responses", "schools" add_foreign_key "survey_item_responses", "schools"

@ -1,37 +1,33 @@
namespace :clean do namespace :clean do
# These tasks must be run in their respective project so the correct schools are in the database # 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 task ecp: :environment do
input_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'raw') input_filepath = Rails.root.join("tmp", "data", "ecp_data", "raw")
output_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'clean') output_filepath = Rails.root.join("tmp", "data", "ecp_data", "clean")
log_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'removed') log_filepath = Rails.root.join("tmp", "data", "ecp_data", "removed")
disaggregation_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'disaggregation') Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean
Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).clean
end end
desc 'clean prepped data' desc "clean prepped data"
task prepped: :environment do task prepped: :environment do
input_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'prepped') input_filepath = Rails.root.join("tmp", "data", "ecp_data", "prepped")
output_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'prepped', 'clean') output_filepath = Rails.root.join("tmp", "data", "ecp_data", "prepped", "clean")
log_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'prepped', 'removed') log_filepath = Rails.root.join("tmp", "data", "ecp_data", "prepped", "removed")
disaggregation_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'disaggregation') Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean
Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).clean
end end
desc 'clean mciea data' desc "clean mciea data"
task mciea: :environment do task mciea: :environment do
input_filepath = Rails.root.join('tmp', 'data', 'mciea_data', 'raw') input_filepath = Rails.root.join("tmp", "data", "mciea_data", "raw")
output_filepath = Rails.root.join('tmp', 'data', 'mciea_data', 'clean') output_filepath = Rails.root.join("tmp", "data", "mciea_data", "clean")
log_filepath = Rails.root.join('tmp', 'data', 'mciea_data', 'removed') log_filepath = Rails.root.join("tmp", "data", "mciea_data", "removed")
disaggregation_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'disaggregation') Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean
Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).clean
end end
desc 'clean rpp data' desc "clean rpp data"
task rpp: :environment do task rpp: :environment do
input_filepath = Rails.root.join('tmp', 'data', 'rpp_data', 'raw') input_filepath = Rails.root.join("tmp", "data", "rpp_data", "raw")
output_filepath = Rails.root.join('tmp', 'data', 'rpp_data', 'clean') output_filepath = Rails.root.join("tmp", "data", "rpp_data", "clean")
log_filepath = Rails.root.join('tmp', 'data', 'rpp_data', 'removed') log_filepath = Rails.root.join("tmp", "data", "rpp_data", "removed")
disaggregation_filepath = Rails.root.join('tmp', 'data', 'ecp_data', 'disaggregation') Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean
Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).clean
end end
end end

@ -5,7 +5,7 @@ namespace :data do
student_count = Student.count student_count = Student.count
path = "/data/survey_responses/clean/" path = "/data/survey_responses/clean/"
Sftp::Directory.open(path:) do |file| Sftp::Directory.open(path:) do |file|
SurveyResponsesDataLoader.from_file(file:) SurveyResponsesDataLoader.new.from_file(file:)
end end
puts "=====================> Completed loading #{SurveyItemResponse.count - survey_item_response_count} survey responses. #{SurveyItemResponse.count} total responses in the database" puts "=====================> Completed loading #{SurveyItemResponse.count - survey_item_response_count} survey responses. #{SurveyItemResponse.count} total responses in the database"

@ -40,38 +40,6 @@ namespace :one_off do
end end
end end
desc 'load stoklosa results for 2022-23'
task load_stoklosa: :environment do
survey_item_response_count = SurveyItemResponse.count
school = School.find_by_dese_id(1_600_360)
academic_year = AcademicYear.find_by_range('2022-23')
['2022-23_stoklosa_student_survey_responses.csv',
'2022-23_stoklosa_teacher_survey_responses.csv'].each do |filepath|
filepath = Rails.root.join('data', 'survey_responses', filepath)
puts "=====================> Loading data from csv at path: #{filepath}"
SurveyResponsesDataLoader.load_data filepath:
end
puts "=====================> Completed loading #{SurveyItemResponse.count - survey_item_response_count} survey responses. #{SurveyItemResponse.count} total responses in the database"
Dir.glob(Rails.root.join('data', 'survey_responses',
'2022-23_stoklosa_student_survey_responses.csv')).each do |file|
puts "=====================> Loading student data from csv at path: #{file}"
StudentLoader.load_data filepath: file, rules: [Rule::SkipNonLowellSchools]
end
end
desc 'load butler results for 2022-23'
task load_butler: :environment do
['2022-23_butler_student_survey_responses.csv',
'2022-23_butler_teacher_survey_responses.csv'].each do |filepath|
filepath = Rails.root.join('data', 'survey_responses', filepath)
puts "=====================> Loading data from csv at path: #{filepath}"
SurveyResponsesDataLoader.load_data filepath:
end
end
desc 'list scales that have no survey responses' desc 'list scales that have no survey responses'
task list_scales_that_lack_survey_responses: :environment do task list_scales_that_lack_survey_responses: :environment do
output = AcademicYear.all.map do |academic_year| output = AcademicYear.all.map do |academic_year|
@ -114,23 +82,6 @@ namespace :one_off do
puts values puts values
end 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"
Rails.cache.clear
end
desc 'delete 2022-23 survey responses' desc 'delete 2022-23 survey responses'
task delete_survey_responses_2022_23: :environment do task delete_survey_responses_2022_23: :environment do
response_count = SurveyItemResponse.all.count response_count = SurveyItemResponse.all.count
@ -148,7 +99,7 @@ namespace :one_off do
schools = District.find_by_slug('maynard-public-schools').schools schools = District.find_by_slug('maynard-public-schools').schools
Sftp::Directory.open(path:) do |file| Sftp::Directory.open(path:) do |file|
SurveyResponsesDataLoader.from_file(file:) SurveyResponsesDataLoader.new.from_file(file:)
end end
puts "=====================> Completed loading #{SurveyItemResponse.count - survey_item_response_count} survey responses. #{SurveyItemResponse.count} total responses in the database" puts "=====================> Completed loading #{SurveyItemResponse.count - survey_item_response_count} survey responses. #{SurveyItemResponse.count} total responses in the database"

@ -1,11 +1,15 @@
FactoryBot.define do FactoryBot.define do
factory :income do factory :income do
designation { "MyString" } designation { "DefaultIncome" }
end
factory :ell do
designation { "DefaultEll" }
end end
factory :gender do factory :gender do
qualtrics_code { 1 } qualtrics_code { 1 }
designation { 'MyString' } designation { "MyString" }
end end
factory :race_score do factory :race_score do
@ -68,22 +72,22 @@ FactoryBot.define do
end end
factory :academic_year do factory :academic_year do
range { '2050-51' } range { "2050-51" }
initialize_with { AcademicYear.find_or_initialize_by(range:) } initialize_with { AcademicYear.find_or_initialize_by(range:) }
end end
factory :category, class: 'Category' do factory :category, class: "Category" do
name { "A #{rand} category" } name { "A #{rand} category" }
category_id { rand.to_s } category_id { rand.to_s }
description { 'A description of a category' } description { "A description of a category" }
slug { name.parameterize } slug { name.parameterize }
sort_index { 1 } sort_index { 1 }
end end
factory :subcategory do factory :subcategory do
name { 'A subcategory' } name { "A subcategory" }
subcategory_id { rand.to_s } subcategory_id { rand.to_s }
description { 'A description of a subcategory' } description { "A description of a subcategory" }
category category
factory :subcategory_with_measures do factory :subcategory_with_measures do
@ -102,7 +106,7 @@ FactoryBot.define do
factory :measure do factory :measure do
measure_id { rand.to_s } measure_id { rand.to_s }
name { 'A Measure' } name { "A Measure" }
subcategory subcategory
trait :with_student_survey_items do trait :with_student_survey_items do
after(:create) do |measure| after(:create) do |measure|
@ -136,7 +140,7 @@ FactoryBot.define do
factory :survey_item do factory :survey_item do
scale scale
prompt { 'What do YOU think?' } prompt { "What do YOU think?" }
factory :teacher_survey_item do factory :teacher_survey_item do
survey_item_id { "t-#{rand}" } survey_item_id { "t-#{rand}" }
watch_low_benchmark { 2.0 } watch_low_benchmark { 2.0 }

@ -1,11 +1,11 @@
Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income,ELL
1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged N 1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged N,ELL
2,Asian or Pacific Islander,1,Female,Economically Disadvantaged Y 2,Asian or Pacific Islander,1,Female,Economically Disadvantaged Y,Not ELL
3,Black or African American,4,Non-Binary,Unknown 3,Black or African American,4,Non-Binary,Unknown,Unknown
4,Hispanic or Latinx,99,Unknown, 4,Hispanic or Latinx,99,Unknown,,
5,White or Caucasian,,, 5,White or Caucasian,,,,
6,Prefer not to disclose,,, 6,Prefer not to disclose,,,,
7,Prefer to self-describe,,, 7,Prefer to self-describe,,,,
8,Middle Eastern,,, 8,Middle Eastern,,,,
99,Race/Ethnicity Not Listed,,, 99,Race/Ethnicity Not Listed,,,,
100,Multiracial,,, 100,Multiracial,,,,

1 Race Qualtrics Code Race/Ethnicity Gender Qualtrics Code Sex/Gender Income ELL
2 1 American Indian or Alaskan Native 2 Male Economically Disadvantaged – N ELL
3 2 Asian or Pacific Islander 1 Female Economically Disadvantaged – Y Not ELL
4 3 Black or African American 4 Non-Binary Unknown Unknown
5 4 Hispanic or Latinx 99 Unknown
6 5 White or Caucasian
7 6 Prefer not to disclose
8 7 Prefer to self-describe
9 8 Middle Eastern
10 99 Race/Ethnicity Not Listed
11 100 Multiracial

@ -1,8 +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 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 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 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 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 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 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 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 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

1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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

@ -27,14 +27,6 @@ RSpec.describe Cleaner do
Rails.root.join("tmp", "spec", "removed") Rails.root.join("tmp", "spec", "removed")
end end
let(:disaggregation_filepath) do
Rails.root.join("spec", "fixtures", "disaggregation")
end
let(:path_to_sample_disaggregation_file) do
File.open(Rails.root.join("spec", "fixtures", "disaggregation", "sample_maynard_disaggregation_data.csv"))
end
let(:path_to_sample_raw_file) do let(:path_to_sample_raw_file) do
File.open(Rails.root.join("spec", "fixtures", "raw", "sample_maynard_raw_student_survey.csv")) File.open(Rails.root.join("spec", "fixtures", "raw", "sample_maynard_raw_student_survey.csv"))
end end
@ -99,21 +91,21 @@ RSpec.describe Cleaner do
context "Creating a new Cleaner" do context "Creating a new Cleaner" do
it "creates a directory for the clean data" do it "creates a directory for the clean data" do
Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).clean Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean
expect(output_filepath).to exist expect(output_filepath).to exist
end end
it "creates a directory for the removed data" do it "creates a directory for the removed data" do
Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).clean Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean
expect(log_filepath).to exist expect(log_filepath).to exist
end end
end end
context ".process_raw_file" do context ".process_raw_file" do
it "sorts data into valid and invalid csvs" 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( 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] processed_data in [headers, clean_csv, log_csv, data]
@ -140,22 +132,6 @@ RSpec.describe Cleaner do
csv_contains_the_correct_rows(log_csv, invalid_rows) csv_contains_the_correct_rows(log_csv, invalid_rows)
invalid_rows_are_rejected_for_the_correct_reasons(data) invalid_rows_are_rejected_for_the_correct_reasons(data)
end 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 end
context ".filename" do context ".filename" do
@ -166,7 +142,7 @@ RSpec.describe Cleaner do
data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: standard_survey_items, genders: nil, survey_items:, data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: standard_survey_items, genders: nil, survey_items:,
schools: School.school_hash)] schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename(
headers: standard_survey_items, data: headers: standard_survey_items, data:
) )
expect(filename).to eq "maynard.standard.2022-23.csv" expect(filename).to eq "maynard.standard.2022-23.csv"
@ -178,7 +154,7 @@ RSpec.describe Cleaner do
data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: short_form_survey_items, genders: nil, survey_items:, data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: short_form_survey_items, genders: nil, survey_items:,
schools: School.school_hash)] schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename(
headers: short_form_survey_items, data: headers: short_form_survey_items, data:
) )
expect(filename).to eq "maynard.short_form.2022-23.csv" expect(filename).to eq "maynard.short_form.2022-23.csv"
@ -191,7 +167,7 @@ RSpec.describe Cleaner do
data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: early_education_survey_items, genders: nil, survey_items:, data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: early_education_survey_items, genders: nil, survey_items:,
schools: School.school_hash)] schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename(
headers: early_education_survey_items, data: headers: early_education_survey_items, data:
) )
expect(filename).to eq "maynard.early_education.2022-23.csv" expect(filename).to eq "maynard.early_education.2022-23.csv"
@ -203,7 +179,7 @@ RSpec.describe Cleaner do
data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: teacher_survey_items, genders: nil, survey_items:, data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: teacher_survey_items, genders: nil, survey_items:,
schools: School.school_hash)] schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename(
headers: teacher_survey_items, data: headers: teacher_survey_items, data:
) )
expect(filename).to eq "maynard.teacher.2022-23.csv" expect(filename).to eq "maynard.teacher.2022-23.csv"
@ -217,7 +193,7 @@ RSpec.describe Cleaner do
data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: teacher_survey_items, genders: nil, survey_items:, schools: School.school_hash), data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: teacher_survey_items, genders: nil, survey_items:, schools: School.school_hash),
SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "222_222" }, SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "222_222" },
headers: teacher_survey_items, genders: nil, survey_items:, schools: School.school_hash)] headers: teacher_survey_items, genders: nil, survey_items:, schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename(
headers: teacher_survey_items, data: headers: teacher_survey_items, data:
) )
expect(filename).to eq "maynard.district2.teacher.2022-23.csv" expect(filename).to eq "maynard.district2.teacher.2022-23.csv"
@ -230,7 +206,7 @@ end
def reads_headers_from_raw_csv(processed_data) def reads_headers_from_raw_csv(processed_data)
processed_data in [headers, clean_csv, log_csv, 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", "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", "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", "s-tint-q2", "s-tint-q3", "s-tint-q4", "s-tint-q5", "s-acpr-q1", "s-acpr-q2",
@ -243,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-grit-q1", "s-grit-q2", "s-grit-q3", "s-grit-q4", "s-expa-q1", "s-poaf-q1", "s-poaf-q2", "s-poaf-q3",
"s-poaf-q4", "s-tint-q1-1", "s-tint-q2-1", "s-tint-q3-1", "s-tint-q4-1", "s-tint-q5-1", "s-acpr-q1-1", "s-poaf-q4", "s-tint-q1-1", "s-tint-q2-1", "s-tint-q3-1", "s-tint-q4-1", "s-tint-q5-1", "s-acpr-q1-1",
"s-acpr-q2-1", "s-acpr-q3-1", "s-acpr-q4-1", "s-peff-q1-1", "s-peff-q2-1", "s-peff-q3-1", "s-peff-q4-1", "s-acpr-q2-1", "s-acpr-q3-1", "s-acpr-q4-1", "s-peff-q1-1", "s-peff-q2-1", "s-peff-q3-1", "s-peff-q4-1",
"s-peff-q5-1", "s-peff-q6-1", "Raw Income", "Income"].to_set.sort "s-peff-q5-1", "s-peff-q6-1", "Raw Income", "Income", "Raw ELL", "ELL"].to_set.sort
end end
def invalid_rows_are_rejected_for_the_correct_reasons(data) def invalid_rows_are_rejected_for_the_correct_reasons(data)

@ -1,20 +1,24 @@
require 'rails_helper' require "rails_helper"
describe DemographicLoader do describe DemographicLoader do
let(:filepath) { 'spec/fixtures/sample_demographics.csv' } let(:filepath) { "spec/fixtures/sample_demographics.csv" }
let(:race_codes) do let(:race_codes) do
{ 'American Indian or Alaskan Native' => 1, 'Asian or Pacific Islander' => 2, 'Black or African American' => 3, { "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 } "Hispanic or Latinx" => 4, "White or Caucasian" => 5, "Race/Ethnicity Not Listed" => 99, "Middle Eastern" => 8, "Multiracial" => 100 }
end end
let(:gender_codes) do let(:gender_codes) do
{ {
'Female' => 1, 'Male' => 2, 'Non-Binary' => 4, 'Unknown' => 99 "Female" => 1, "Male" => 2, "Non-Binary" => 4, "Unknown" => 99
} }
end end
let(:incomes) do 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 end
before :each do before :each do
@ -25,12 +29,12 @@ describe DemographicLoader do
DatabaseCleaner.clean DatabaseCleaner.clean
end end
describe 'self.load_data' do describe "self.load_data" do
it 'does not load qualtrics categories for `prefer not to disclose` or `prefer to self-describe`' 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 expect(Race.find_by_qualtrics_code(6)).to be nil
end end
it 'loads all racial designations' do it "loads all racial designations" do
expect(Race.all.count).to eq 8 expect(Race.all.count).to eq 8
race_codes.each do |key, value| race_codes.each do |key, value|
expect(Race.find_by_qualtrics_code(value)).not_to eq nil expect(Race.find_by_qualtrics_code(value)).not_to eq nil
@ -40,7 +44,7 @@ describe DemographicLoader do
end end
end end
it 'loads all gender designations' do it "loads all gender designations" do
expect(Gender.all.count).to eq 4 expect(Gender.all.count).to eq 4
gender_codes.each do |key, value| gender_codes.each do |key, value|
@ -51,11 +55,18 @@ describe DemographicLoader do
end end
end end
it 'loads all the income designations' do it "loads all the income designations" do
expect(Income.all.count).to eq 3 expect(Income.all.count).to eq 3
incomes.each do |income| incomes.each do |income|
expect(Income.find_by_designation(income).designation).to eq income expect(Income.find_by_designation(income).designation).to eq income
end end
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
end end

@ -1,33 +1,37 @@
require 'rails_helper' require "rails_helper"
require 'fileutils' require "fileutils"
RSpec.describe DisaggregationLoader do RSpec.describe DisaggregationLoader do
let(:path) do let(:path) do
Rails.root.join('spec', 'fixtures', 'disaggregation') Rails.root.join("spec", "fixtures", "disaggregation")
end end
let(:academic_year) { create(:academic_year, range: '2022-23') } let(:academic_year) { create(:academic_year, range: "2022-23") }
let(:district) { create(:district, name: 'Maynard Public Schools') } let(:district) { create(:district, name: "Maynard Public Schools") }
context '.load' do context ".load" do
it 'loads data from the file into a hash' do it "loads data from the file into a hash" do
data = DisaggregationLoader.new(path:).load data = DisaggregationLoader.new(path:).load
expect(data.values.first.lasid).to eq('1') expect(data.values.first.lasid).to eq("1")
expect(data.values.first.academic_year).to eq('2022-23') expect(data.values.first.academic_year).to eq("2022-23")
expect(data.values.first.district).to eq('Maynard Public Schools') expect(data.values.first.district).to eq("Maynard Public Schools")
expect(data.values.first.income).to eq('Free Lunch')
expect(data.values.last.lasid).to eq('500') expect(data.values.last.lasid).to eq("500")
expect(data.values.last.academic_year).to eq('2022-23') expect(data.values.last.academic_year).to eq("2022-23")
expect(data.values.last.district).to eq('Maynard Public Schools') expect(data.values.last.district).to eq("Maynard Public Schools")
expect(data.values.last.income).to eq('Not Eligible') 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']].income).to eq('Free Lunch') expect(data[["1", "Maynard Public Schools", "2022-23"]].raw_income).to eq("Free Lunch")
expect(data[['2', 'Maynard Public Schools', '2022-23']].income).to eq('Not Eligible') expect(data[["2", "Maynard Public Schools", "2022-23"]].raw_income).to eq("Not Eligible")
expect(data[['3', 'Maynard Public Schools', '2022-23']].income).to eq('Reduced Lunch') expect(data[["3", "Maynard Public Schools", "2022-23"]].raw_income).to eq("Reduced Lunch")
end end
end end
context 'Creating a new loader' do context "Creating a new loader" do
it 'creates a directory for the loader file' do it "creates a directory for the loader file" do
DisaggregationLoader.new(path:) DisaggregationLoader.new(path:)
expect(path).to exist expect(path).to exist
end end

@ -1,73 +1,107 @@
require 'rails_helper' require "rails_helper"
RSpec.describe DisaggregationRow do RSpec.describe DisaggregationRow do
let(:headers) do let(:headers) do
['District', 'Academic Year', 'LASID', 'HispanicLatino', 'Race', 'Gender', 'SpecialEdStatus', 'In 504 Plan', ["District", "Academic Year", "LASID", "HispanicLatino", "Race", "Gender", "SpecialEdStatus", "In 504 Plan",
'LowIncome', 'EL Student First Year'] "LowIncome", "EL Student First Year"]
end end
context '.district' do context ".district" do
context 'when the column heading is any upper or lowercase variant of the word 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 it "returns the correct value for district" do
row = { 'District' => 'Maynard Public Schools' } row = { "District" => "Maynard Public Schools" }
expect(DisaggregationRow.new(row:, headers:).district).to eq 'Maynard Public Schools' expect(DisaggregationRow.new(row:, headers:).district).to eq "Maynard Public Schools"
headers = ['dISTRICT'] headers = ["dISTRICT"]
headers in [district] headers in [district]
row = { district => 'Maynard Public Schools' } row = { district => "Maynard Public Schools" }
expect(DisaggregationRow.new(row:, headers:).district).to eq 'Maynard Public Schools' expect(DisaggregationRow.new(row:, headers:).district).to eq "Maynard Public Schools"
end end
end end
end end
context '.academic_year' do context ".academic_year" do
context 'when the column heading is any upper or lowercase variant of the words 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 it "returns the correct value for district" do
row = { 'Academic Year' => '2022-23' } row = { "Academic Year" => "2022-23" }
expect(DisaggregationRow.new(row:, headers:).academic_year).to eq '2022-23' expect(DisaggregationRow.new(row:, headers:).academic_year).to eq "2022-23"
headers = ['aCADEMIC yEAR'] headers = ["aCADEMIC yEAR"]
headers in [academic_year] headers in [academic_year]
row = { academic_year => '2022-23' } row = { academic_year => "2022-23" }
expect(DisaggregationRow.new(row:, headers:).academic_year).to eq '2022-23' expect(DisaggregationRow.new(row:, headers:).academic_year).to eq "2022-23"
headers = ['AcademicYear'] headers = ["AcademicYear"]
headers in [academic_year] headers in [academic_year]
row = { academic_year => '2022-23' } row = { academic_year => "2022-23" }
expect(DisaggregationRow.new(row:, headers:).academic_year).to eq '2022-23' expect(DisaggregationRow.new(row:, headers:).academic_year).to eq "2022-23"
end end
end end
end end
context '.income' do context ".raw_income" do
context 'when the column heading is any upper or lowercase variant of the words low 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 it "returns the correct value for low_income" do
row = { 'LowIncome' => 'Free Lunch' } row = { "LowIncome" => "Free Lunch" }
expect(DisaggregationRow.new(row:, headers:).income).to eq 'Free Lunch' expect(DisaggregationRow.new(row:, headers:).raw_income).to eq "Free Lunch"
headers = ['Low income'] headers = ["Low income"]
headers in [income] headers in [income]
row = { income => 'Free Lunch' } row = { income => "Free Lunch" }
expect(DisaggregationRow.new(row:, headers:).income).to eq 'Free Lunch' expect(DisaggregationRow.new(row:, headers:).raw_income).to eq "Free Lunch"
headers = ['LoW InCOme'] headers = ["LoW InCOme"]
headers in [income] headers in [income]
row = { income => 'Free Lunch' } row = { income => "Free Lunch" }
expect(DisaggregationRow.new(row:, headers:).income).to eq 'Free Lunch' expect(DisaggregationRow.new(row:, headers:).raw_income).to eq "Free Lunch"
end end
end end
end end
context '.lasid' do context ".lasid" do
context 'when the column heading is any upper or lowercase variant of the words 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 it "returns the correct value for lasid" do
row = { 'LASID' => '2366' } row = { "LASID" => "2366" }
expect(DisaggregationRow.new(row:, headers:).lasid).to eq '2366' expect(DisaggregationRow.new(row:, headers:).lasid).to eq "2366"
headers = ['LaSiD'] headers = ["LaSiD"]
headers in [lasid] headers in [lasid]
row = { lasid => '2366' } row = { lasid => "2366" }
expect(DisaggregationRow.new(row:, headers:).lasid).to eq '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
end end

@ -88,7 +88,7 @@ describe StudentLoader do
describe "self.load_data" do describe "self.load_data" do
context "load student data for all schools" do context "load student data for all schools" do
before :each 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 StudentLoader.load_data filepath: path_to_student_responses
end 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 # 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 xcontext "When using the rule to skip non Lowell schools" do
before :each 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] StudentLoader.load_data filepath: path_to_student_responses, rules: [Rule::SkipNonLowellSchools]
end end

@ -7,7 +7,7 @@ RSpec.describe SurveyItemValues, type: :model do
end end
let(:genders) do let(:genders) do
create(:gender, qualtrics_code: 1) create(:gender, qualtrics_code: 1)
Gender.gender_hash Gender.by_qualtrics_code
end end
let(:survey_items) { [] } let(:survey_items) { [] }
let(:district) { create(:district, name: "Attleboro") } let(:district) { create(:district, name: "Attleboro") }
@ -165,79 +165,90 @@ RSpec.describe SurveyItemValues, type: :model do
end end
context ".income" do context ".income" do
context "when no disaggregation data is provided" do before :each do
it "returns an empty string " do attleboro
disaggregation_data = {} ay_2022_23
values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:, disaggregation_data:)
expect(values.income).to eq "Unknown"
end
end end
context "when disaggregation data is provided" do it "translates Free Lunch to Economically Disadvantaged - Y" do
before :each do headers = ["LowIncome"]
attleboro row = { "LowIncome" => "Free Lunch" }
ay_2022_23 values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
end expect(values.income).to eq "Economically Disadvantaged - Y"
end
it "translates Free Lunch to Economically Disadvantaged - Y" do it "translates Reduced Lunch to Economically Disadvantaged - Y" do
headers = ["District", "Academic Year", "LASID", "LowIncome"] headers = ["LowIncome"]
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Free Lunch" } row = { "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"] it "translates LowIncome to Economically Disadvantaged - Y" do
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } headers = ["LowIncome"]
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, row = { "LowIncome" => "LowIncome" }
disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - Y"
end
it "translates Reduced Lunch to Economically Disadvantaged - Y" do values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
headers = ["District", "Academic Year", "LASID", "LowIncome"] expect(values.income).to eq "Economically Disadvantaged - Y"
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Reduced Lunch" } end
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"] it "translates Not Eligible to Economically Disadvantaged - N" do
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } headers = ["LowIncome"]
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, row = { "LowIncome" => "Not Eligible" }
disaggregation_data:) values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.income).to eq "Economically Disadvantaged - Y" expect(values.income).to eq "Economically Disadvantaged - N"
end end
it "translates LowIncome to Economically Disadvantaged - Y" do it "translates blanks to Unknown" do
headers = ["District", "Academic Year", "LASID", "LowIncome"] headers = ["LowIncome"]
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "LowIncome" } row = { "LowIncome" => "" }
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"] values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } expect(values.income).to eq "Unknown"
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, end
disaggregation_data:) end
expect(values.income).to eq "Economically Disadvantaged - Y"
end
it "translates Not Eligible to Economically Disadvantaged - N" do context ".ell" do
headers = ["District", "Academic Year", "LASID", "LowIncome"] before :each do
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Not Eligible" } attleboro
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) } ay_2022_23
end
headers = ["LASID", "Dese Id", "RecordedDate"] it 'translates "LEP Student 1st Year" or "LEP Student Not 1st Year" into ELL' do
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } headers = ["Raw ELL"]
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, row = { "Raw ELL" => "LEP Student 1st Year" }
disaggregation_data:) values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.income).to eq "Economically Disadvantaged - N" expect(values.ell).to eq "ELL"
end
it "translates blanks to Unknown" do row = { "Raw ELL" => "LEP Student Not 1st Year" }
headers = ["District", "Academic Year", "LASID", "LowIncome"] values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "" } expect(values.ell).to eq "ELL"
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"] row = { "Raw ELL" => "LEP Student Not 1st Year" }
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" } values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:, expect(values.ell).to eq "ELL"
disaggregation_data:) end
expect(values.income).to eq "Unknown"
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
end end

@ -54,6 +54,9 @@ describe SurveyResponsesDataLoader do
let(:low_income) { create(:income, designation: "Economically Disadvantaged Y") } let(:low_income) { create(:income, designation: "Economically Disadvantaged Y") }
let(:high_income) { create(:income, designation: "Economically Disadvantaged N") } let(:high_income) { create(:income, designation: "Economically Disadvantaged N") }
let(:unknown_income) { create(:income, designation: "Unknown") } 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 let(:setup) do
ay_2020_21 ay_2020_21
@ -92,6 +95,9 @@ describe SurveyResponsesDataLoader do
low_income low_income
high_income high_income
unknown_income unknown_income
yes_ell
not_ell
unknown_ell
end end
before :each do before :each do
@ -100,7 +106,7 @@ describe SurveyResponsesDataLoader do
describe "loading teacher survey responses" do describe "loading teacher survey responses" do
before do before do
SurveyResponsesDataLoader.load_data filepath: path_to_teacher_responses SurveyResponsesDataLoader.new.load_data filepath: path_to_teacher_responses
end end
it "ensures teacher responses load correctly" do it "ensures teacher responses load correctly" do
@ -116,7 +122,7 @@ describe SurveyResponsesDataLoader do
describe "student survey responses" do describe "student survey responses" do
before do before do
SurveyResponsesDataLoader.load_data filepath: path_to_student_responses SurveyResponsesDataLoader.new.load_data filepath: path_to_student_responses
end end
it "ensures student responses load correctly" do it "ensures student responses load correctly" do
@ -129,13 +135,14 @@ describe SurveyResponsesDataLoader do
assigns_grade_level_to_responses assigns_grade_level_to_responses
assigns_gender_to_responses assigns_gender_to_responses
assigns_income_to_responses assigns_income_to_responses
assigns_ell_to_responses
is_idempotent_for_students is_idempotent_for_students
end 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 before :each do
SurveyResponsesDataLoader.load_data filepath: Rails.root.join("spec", "fixtures", SurveyResponsesDataLoader.new.load_data filepath: Rails.root.join("spec", "fixtures",
"secondary_test_2020-21_student_survey_responses.csv") "secondary_test_2020-21_student_survey_responses.csv")
end end
it "updates the likert score to the score on the new csv file" do 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" s_emsa_q1 = SurveyItem.find_by_survey_item_id "s-emsa-q1"
@ -154,8 +161,8 @@ describe SurveyResponsesDataLoader do
# figure out why this is failing # 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 before :each do
SurveyResponsesDataLoader.load_data filepath: path_to_student_responses, SurveyResponsesDataLoader.new.load_data filepath: path_to_student_responses,
rules: [Rule::SkipNonLowellSchools] rules: [Rule::SkipNonLowellSchools]
end end
it "rejects any non-lowell school" do it "rejects any non-lowell school" do
@ -172,8 +179,8 @@ describe SurveyResponsesDataLoader do
context "when loading 22-23 butler survey responses" do context "when loading 22-23 butler survey responses" do
before :each do before :each do
SurveyResponsesDataLoader.load_data filepath: path_to_butler_student_responses, SurveyResponsesDataLoader.new.load_data filepath: path_to_butler_student_responses,
rules: [Rule::SkipNonLowellSchools] rules: [Rule::SkipNonLowellSchools]
end end
it "loads all the responses for Butler" do it "loads all the responses for Butler" do
@ -235,7 +242,7 @@ end
def is_idempotent def is_idempotent
number_of_survey_item_responses = SurveyItemResponse.count 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 expect(SurveyItemResponse.count).to eq number_of_survey_item_responses
end end
@ -281,7 +288,7 @@ end
def is_idempotent_for_students def is_idempotent_for_students
number_of_survey_item_responses = SurveyItemResponse.count 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 expect(SurveyItemResponse.count).to eq number_of_survey_item_responses
end end
@ -344,6 +351,21 @@ def assigns_income_to_responses
"student_survey_response_7" => low_income } "student_survey_response_7" => low_income }
results.each do |key, value| 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
end end

Loading…
Cancel
Save