feat: Add income table to the database. Add seeder for income. Add a reference to income from survey item response. Update the loader to import income data from the survey response csv. Refactor analyze controller to extract presenter. Add corresponding specs. Add income graph to analyze page

pull/1/head
rebuilt 3 years ago
parent 7e1be4860c
commit 4f035f6a63

@ -12,4 +12,4 @@ Style/Documentation:
Enabled: false Enabled: false
Style/StringLiterals: Style/StringLiterals:
EnforcedStyle: single_quotes EnforcedStyle: double_quotes

@ -1,172 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class AnalyzeController < SqmApplicationController class AnalyzeController < SqmApplicationController
before_action :assign_categories, :assign_subcategories, :assign_measures, :assign_academic_years, def index
:races, :selected_races, :graph, :graphs, :background, :race_score_timestamp, @presenter = Analyze::Presenter.new(params:, school: @school, academic_year: @academic_year)
:source, :sources, :group, :groups, :selected_grades, :grades, :slice, :selected_genders, :genders, only: [:index] @background ||= BackgroundPresenter.new(num_of_columns: @presenter.graph.columns.count)
def index; end
private
def assign_categories
@category ||= Category.find_by_category_id(params[:category])
@category ||= Category.order(:category_id).first
@categories = Category.all.order(:category_id)
end
def assign_subcategories
@subcategories = @category.subcategories.order(:subcategory_id)
@subcategory ||= Subcategory.find_by_subcategory_id(params[:subcategory])
@subcategory ||= @subcategories.first
end
def assign_measures
@measures = @subcategory.measures.order(:measure_id).includes(%i[admin_data_items subcategory])
end
def assign_academic_years
@available_academic_years = AcademicYear.order(:range).all
year_params = params[:academic_years]
@academic_year_params = year_params.split(',') if year_params
@selected_academic_years = []
@academic_year_params ||= []
@academic_year_params.each do |year|
@selected_academic_years << AcademicYear.find_by_range(year)
end
end
def races
@races ||= Race.all.order(designation: :ASC)
end
def selected_races
@selected_races ||= begin
race_params = params[:races]
return @selected_races = races unless race_params
race_list = race_params.split(',') if race_params
if race_list
race_list = race_list.map do |race|
Race.find_by_slug race
end
end
race_list
end
end
def graph
graphs.each do |graph|
@graph = graph if graph.slug == params[:graph]
end
@graph ||= graphs.first
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)]
end
def background
@background ||= BackgroundPresenter.new(num_of_columns: graph.columns.count)
end
def race_score_timestamp
@race_score_timestamp ||= begin
score = RaceScore.where(school: @school,
academic_year: @academic_year).order(updated_at: :DESC).first || Today.new
score.updated_at
end
end
def source
source_param = params[:source]
sources.each do |source|
@source = source if source.slug == source_param
end
@source ||= sources.first
end
def sources
all_data_slices = [Analyze::Slice::AllData.new]
all_data_source = Analyze::Source::AllData.new(slices: all_data_slices)
students_and_teachers = Analyze::Slice::StudentsAndTeachers.new
students_by_group = Analyze::Slice::StudentsByGroup.new(races:, grades:)
survey_data_slices = [students_and_teachers, students_by_group]
survey_data_source = Analyze::Source::SurveyData.new(slices: survey_data_slices)
@sources = [all_data_source, survey_data_source]
end
def slice
slice_param = params[:slice]
slices.each do |slice|
@slice = slice if slice.slug == slice_param
end
@slice ||= slices.first
end
def slices
source.slices
end
def group
groups.each do |group|
@group = group if group.slug == params[:group]
end
@group ||= groups.first
end
def groups
@groups = [Analyze::Group::Race.new, Analyze::Group::Grade.new, Analyze::Group::Gender.new]
end
def selected_grades
@selected_grades ||= begin
grade_params = params[:grades]
return @selected_grades = grades unless grade_params
grade_list = grade_params.split(',') if grade_params
if grade_list
grade_list = grade_list.map do |grade|
grade.to_i
end
end
grade_list
end
end
def grades
@grades ||= SurveyItemResponse.where(school: @school, academic_year: @academic_year)
.where.not(grade: nil)
.group(:grade)
.select(:response_id)
.distinct(:response_id)
.count.reject do |_key, value|
value < 10
end.keys
end
def selected_genders
@selected_genders ||= begin
gender_params = params[:genders]
return @selected_genders = genders unless gender_params
gender_list = gender_params.split(',') if gender_params
if gender_list
gender_list = gender_list.map do |gender|
Gender.find_by_designation(gender)
end
end
gender_list
end
end
def genders
@genders ||= Gender.all
end end
end end

@ -62,7 +62,7 @@ module AnalyzeHelper
end end
def base_url def base_url
analyze_subcategory_link(district: @district, school: @school, academic_year: @academic_year, category: @category, analyze_subcategory_link(district: @district, school: @school, academic_year: @academic_year, category: @presenter.category,
subcategory: @subcategory) subcategory: @presenter.subcategory)
end end
end end

@ -22,16 +22,17 @@ export default class extends Controller {
"&graph=" + "&graph=" +
this.selected_graph(target) + this.selected_graph(target) +
"&races=" + "&races=" +
this.selected_races().join(",") + this.selected_items("race").join(",") +
"&genders=" + "&genders=" +
this.selected_genders().join(",") + this.selected_items("gender").join(",") +
"&incomes=" +
this.selected_items("income").join(",") +
"&grades=" + "&grades=" +
this.selected_grades().join(","); this.selected_items("grade").join(",");
this.go_to(url); this.go_to(url);
} }
go_to(location) { go_to(location) {
window.location = location; window.location = location;
} }
@ -121,58 +122,34 @@ export default class extends Controller {
return item.id; return item.id;
})[0]; })[0];
const groups = new Map([
['gender', 'students-by-gender'],
['grade', 'students-by-grade'],
['income', 'students-by-income'],
['race', 'students-by-race']
])
if (target.name === 'slice' || target.name === 'group') { if (target.name === 'slice' || target.name === 'group') {
if (selected_slice === 'students-and-teachers') { if (selected_slice === 'students-and-teachers') {
return 'students-and-teachers'; return 'students-and-teachers';
} else if (this.selected_group() === 'race') {
return 'students-by-race';
} else if (this.selected_group() === 'gender') {
return 'students-by-gender';
} else if (this.selected_group() === 'grade') {
return 'students-by-grade';
} }
return groups.get(this.selected_group());
} }
return window.graph; return window.graph;
} }
selected_races() { selected_items(type) {
let race_checkboxes = [...document.getElementsByName("race-checkbox")] let checkboxes = [...document.getElementsByName(`${type}-checkbox`)]
let races = race_checkboxes let items = checkboxes
.filter((item) => {
return item.checked;
})
.map((item) => {
return item.id;
});
return races;
}
selected_grades() {
let grade_checkboxes = [...document.getElementsByName("grade-checkbox")]
let grades = grade_checkboxes
.filter((item) => {
return item.checked;
})
.map((item) => {
return item.id.replace('grade-', '');
});
return grades;
}
selected_genders() {
let gender_checkboxes = [...document.getElementsByName("gender-checkbox")]
let genders = gender_checkboxes
.filter((item) => { .filter((item) => {
return item.checked; return item.checked;
}) })
.map((item) => { .map((item) => {
return item.id.replace('gender-', ''); return item.id.replace(`${type}-`, '');
}); });
return genders; return items;
} }
} }

@ -0,0 +1,7 @@
class Income < ApplicationRecord
scope :by_designation, -> { all.map { |income| [income.designation, income] }.to_h }
include FriendlyId
friendly_id :designation, use: [:slugged]
end

@ -9,6 +9,7 @@ class SurveyItemResponse < ActiveRecord::Base
belongs_to :survey_item, counter_cache: true belongs_to :survey_item, counter_cache: true
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
has_one :measure, through: :survey_item has_one :measure, through: :survey_item
@ -25,4 +26,9 @@ class SurveyItemResponse < ActiveRecord::Base
SurveyItemResponse.where(survey_item: survey_items, school:, SurveyItemResponse.where(survey_item: survey_items, school:,
academic_year:, gender:).group(:survey_item).average(:likert_score) academic_year:, gender:).group(:survey_item).average(:likert_score)
} }
scope :averages_for_income, lambda { |survey_items, school, academic_year, income|
SurveyItemResponse.where(survey_item: survey_items, school:,
academic_year:, income:).group(:survey_item).average(:likert_score)
}
end end

@ -0,0 +1,85 @@
# frozen_string_literal: true
module Analyze
class BarPresenter
include AnalyzeHelper
attr_reader :score, :x_position, :academic_year, :measure_id, :measure, :color
MINIMUM_BAR_HEIGHT = 2
def initialize(measure:, academic_year:, score:, x_position:, color:)
@score = score
@x_position = x_position
@academic_year = academic_year
@measure = measure
@measure_id = measure.measure_id
@color = color
end
def y_offset
benchmark_height = analyze_zone_height * 2
case zone.type
when :ideal, :approval
benchmark_height - bar_height_percentage
else
benchmark_height
end
end
def bar_color
"fill-#{zone.type}"
end
def bar_height_percentage
bar_height = send("#{zone.type}_bar_height_percentage") || 0
enforce_minimum_height(bar_height:)
end
def percentage
low_benchmark = zone.low_benchmark
(score.average - low_benchmark) / (zone.high_benchmark - low_benchmark)
end
def zone
zones = Zones.new(
watch_low_benchmark: measure.watch_low_benchmark,
growth_low_benchmark: measure.growth_low_benchmark,
approval_low_benchmark: measure.approval_low_benchmark,
ideal_low_benchmark: measure.ideal_low_benchmark
)
zones.zone_for_score(score.average)
end
def average
average = score.average || 0
average.round(6)
end
private
def enforce_minimum_height(bar_height:)
bar_height < MINIMUM_BAR_HEIGHT ? MINIMUM_BAR_HEIGHT : bar_height
end
def ideal_bar_height_percentage
(percentage * zone_height_percentage + zone_height_percentage) * 100
end
def approval_bar_height_percentage
(percentage * zone_height_percentage) * 100
end
def growth_bar_height_percentage
((1 - percentage) * zone_height_percentage) * 100
end
def watch_bar_height_percentage
((1 - percentage) * zone_height_percentage + zone_height_percentage) * 100
end
def warning_bar_height_percentage
((1 - percentage) * zone_height_percentage + zone_height_percentage + zone_height_percentage) * 100
end
end
end

@ -27,10 +27,10 @@ module Analyze
def bars def bars
@bars ||= yearly_scores.map.each_with_index do |yearly_score, index| @bars ||= yearly_scores.map.each_with_index do |yearly_score, index|
year = yearly_score.year year = yearly_score.year
AnalyzeBarPresenter.new(measure:, academic_year: year, Analyze::BarPresenter.new(measure:, academic_year: year,
score: yearly_score.score, score: yearly_score.score,
x_position: bar_x(index), x_position: bar_x(index),
color: bar_color(year)) color: bar_color(year))
end end
end end

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Analyze
module Graph
module Column
module IncomeColumn
class Disadvantaged < GroupedBarColumnPresenter
include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
def label
"Economically Disadvantaged"
end
def show_irrelevancy_message?
false
end
def show_insufficient_data_message?
false
end
def income
Income.find_by_designation "Economically Disadvantaged - Y"
end
end
end
end
end
end

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Analyze
module Graph
module Column
module IncomeColumn
class NotDisadvantaged < GroupedBarColumnPresenter
include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
def label
"Not Disadvantaged"
end
def show_irrelevancy_message?
false
end
def show_insufficient_data_message?
false
end
def income
Income.find_by_designation "Economically Disadvantaged - N"
end
end
end
end
end
end

@ -0,0 +1,43 @@
module Analyze
module Graph
module Column
module IncomeColumn
module ScoreForIncome
def score(year_index)
academic_year = academic_years[year_index]
averages = SurveyItemResponse.averages_for_income(measure.student_survey_items, school, academic_year,
income)
average = bubble_up_averages(averages:).round(2)
scorify(average:, meets_student_threshold: sufficient_student_responses?(academic_year:))
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 scorify(average:, meets_student_threshold:)
return Score::NIL_SCORE unless meets_student_threshold
Score.new(average:,
meets_teacher_threshold: false,
meets_student_threshold: true,
meets_admin_data_threshold: false)
end
def sufficient_student_responses?(academic_year:)
yearly_counts = SurveyItemResponse.where(school:, academic_year:,
income:, survey_item: measure.student_survey_items).group(:income).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,28 @@
# frozen_string_literal: true
module Analyze
module Graph
module Column
module IncomeColumn
class Unknown < GroupedBarColumnPresenter
include Analyze::Graph::Column::IncomeColumn::ScoreForIncome
def label
"Unknown"
end
def show_irrelevancy_message?
false
end
def show_insufficient_data_message?
false
end
def income
Income.find_by_designation "Unknown"
end
end
end
end
end
end

@ -0,0 +1,40 @@
module Analyze
module Graph
class StudentsByIncome
attr_reader :incomes
def initialize(incomes:)
@incomes = incomes
end
def to_s
"Students by income"
end
def slug
"students-by-income"
end
def columns
[].tap do |array|
incomes.each do |income|
array << column_for_income_code(code: income.slug)
end
array << Analyze::Graph::Column::AllStudent
end
end
private
def column_for_income_code(code:)
CFR[code.to_s]
end
CFR = {
"economically-disadvantaged-y" => Analyze::Graph::Column::IncomeColumn::Disadvantaged,
"economically-disadvantaged-n" => Analyze::Graph::Column::IncomeColumn::NotDisadvantaged,
"unknown" => Analyze::Graph::Column::IncomeColumn::Unknown
}.freeze
end
end
end

@ -0,0 +1,13 @@
module Analyze
module Group
class Income
def name
'Income'
end
def slug
'income'
end
end
end
end

@ -0,0 +1,159 @@
module Analyze
class Presenter
attr_reader :params, :school, :academic_year
def initialize(params:, school:, academic_year:)
@params = params
@school = school
@academic_year = academic_year
end
def category
@category ||= Category.find_by_category_id(params[:category]) || Category.order(:category_id).first
end
def categories
@categories = Category.all.order(:category_id)
end
def subcategory
@subcategory ||= Subcategory.find_by_subcategory_id(params[:subcategory]) || subcategories.first
end
def subcategories
@subcategories = category.subcategories.order(:subcategory_id)
end
def measures
@measures = subcategory.measures.order(:measure_id).includes(%i[admin_data_items subcategory])
end
def academic_years
@academic_years = AcademicYear.order(:range).all
end
def selected_academic_years
@selected_academic_years ||= begin
year_params = params[:academic_years]
return [] unless year_params
year_params.split(",").map { |year| AcademicYear.find_by_range(year) }.compact
end
end
def races
@races ||= Race.all.order(designation: :ASC)
end
def selected_races
@selected_races ||= begin
race_params = params[:races]
return races unless race_params
race_params.split(",").map { |race| Race.find_by_slug race }.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)]
end
def graph
@graph ||= graphs.reduce(graphs.first) do |acc, graph|
graph.slug == params[:graph] ? graph : acc
end
end
def selected_grades
@selected_grades ||= begin
grade_params = params[:grades]
return grades unless grade_params
grade_params.split(",").map(&:to_i)
end
end
def selected_genders
@selected_genders ||= begin
gender_params = params[:genders]
return genders unless gender_params
gender_params.split(",").map { |gender| Gender.find_by_designation(gender) }.compact
end
end
def genders
@genders ||= Gender.all
end
def groups
@groups = [Analyze::Group::Gender.new, Analyze::Group::Grade.new, Analyze::Group::Income.new,
Analyze::Group::Race.new]
end
def group
@group ||= groups.reduce(groups.first) do |acc, group|
group.slug == params[:group] ? group : acc
end
end
def slice
@slice ||= slices.reduce(slices.first) do |acc, slice|
slice.slug == params[:slice] ? slice : acc
end
end
def slices
source.slices
end
def source
@source ||= sources.reduce(sources.first) do |acc, source|
source.slug == params[:source] ? source : acc
end
end
def sources
all_data_slices = [Analyze::Slice::AllData.new]
all_data_source = Analyze::Source::AllData.new(slices: all_data_slices)
students_and_teachers = Analyze::Slice::StudentsAndTeachers.new
students_by_group = Analyze::Slice::StudentsByGroup.new(races:, grades:)
survey_data_slices = [students_and_teachers, students_by_group]
survey_data_source = Analyze::Source::SurveyData.new(slices: survey_data_slices)
@sources = [all_data_source, survey_data_source]
end
def grades
@grades ||= SurveyItemResponse.where(school:, academic_year:)
.where.not(grade: nil)
.group(:grade)
.select(:response_id)
.distinct(:response_id)
.count.reject do |_key, value|
value < 10
end.keys
end
def race_score_timestamp
score = RaceScore.where(school: @school,
academic_year: @academic_year).order(updated_at: :DESC).first || Today.new
score.updated_at
end
def incomes
@incomes ||= Income.all
end
def selected_incomes
@selected_incomes ||= begin
income_params = params[:incomes]
return incomes unless income_params
income_params.split(",").map { |income| Income.find_by_slug(income) }.compact
end
end
end
end

@ -1,8 +0,0 @@
module Analyze
class Ui
attr_reader :params
def initialize(params:)
@params = params
end
end
end

@ -1,83 +0,0 @@
# frozen_string_literal: true
class AnalyzeBarPresenter
include AnalyzeHelper
attr_reader :score, :x_position, :academic_year, :measure_id, :measure, :color
MINIMUM_BAR_HEIGHT = 2
def initialize(measure:, academic_year:, score:, x_position:, color:)
@score = score
@x_position = x_position
@academic_year = academic_year
@measure = measure
@measure_id = measure.measure_id
@color = color
end
def y_offset
benchmark_height = analyze_zone_height * 2
case zone.type
when :ideal, :approval
benchmark_height - bar_height_percentage
else
benchmark_height
end
end
def bar_color
"fill-#{zone.type}"
end
def bar_height_percentage
bar_height = send("#{zone.type}_bar_height_percentage") || 0
enforce_minimum_height(bar_height:)
end
def percentage
low_benchmark = zone.low_benchmark
(score.average - low_benchmark) / (zone.high_benchmark - low_benchmark)
end
def zone
zones = Zones.new(
watch_low_benchmark: measure.watch_low_benchmark,
growth_low_benchmark: measure.growth_low_benchmark,
approval_low_benchmark: measure.approval_low_benchmark,
ideal_low_benchmark: measure.ideal_low_benchmark
)
zones.zone_for_score(score.average)
end
def average
average = score.average || 0
average.round(6)
end
private
def enforce_minimum_height(bar_height:)
bar_height < MINIMUM_BAR_HEIGHT ? MINIMUM_BAR_HEIGHT : bar_height
end
def ideal_bar_height_percentage
(percentage * zone_height_percentage + zone_height_percentage) * 100
end
def approval_bar_height_percentage
(percentage * zone_height_percentage) * 100
end
def growth_bar_height_percentage
((1 - percentage) * zone_height_percentage) * 100
end
def watch_bar_height_percentage
((1 - percentage) * zone_height_percentage + zone_height_percentage) * 100
end
def warning_bar_height_percentage
((1 - percentage) * zone_height_percentage + zone_height_percentage + zone_height_percentage) * 100
end
end

@ -5,6 +5,7 @@ class DemographicLoader
CSV.parse(File.read(filepath), headers: true) do |row| CSV.parse(File.read(filepath), headers: true) do |row|
process_race(row:) process_race(row:)
process_gender(row:) process_gender(row:)
process_income(row:)
end end
end end
@ -28,6 +29,13 @@ class DemographicLoader
gender = ::Gender.find_or_create_by!(qualtrics_code:, designation:) gender = ::Gender.find_or_create_by!(qualtrics_code:, designation:)
gender.save gender.save
end end
def self.process_income(row:)
designation = row['Income']
return unless designation
Income.find_or_create_by!(designation:)
end
end end
class KnownRace class KnownRace

@ -99,7 +99,9 @@ class SurveyItemValues
end end
def raw_income def raw_income
@raw_income ||= value_from(pattern: /Income|Low\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? return 'Unknown' unless disaggregation_data.present?
disaggregation = disaggregation_data[[lasid, district.name, academic_year.range]] disaggregation = disaggregation_data[[lasid, district.name, academic_year.range]]
@ -109,6 +111,9 @@ class SurveyItemValues
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'

@ -7,12 +7,13 @@ class SurveyResponsesDataLoader
headers_array = CSV.parse(headers).first headers_array = CSV.parse(headers).first
genders = Gender.gender_hash genders = Gender.gender_hash
schools = School.school_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:) rules:, incomes:)
end end
SurveyItemResponse.import survey_item_responses.compact.flatten, batch_size: 500 SurveyItemResponse.import survey_item_responses.compact.flatten, batch_size: 500
end end
@ -24,6 +25,7 @@ class SurveyResponsesDataLoader
headers_array = CSV.parse(headers).first headers_array = CSV.parse(headers).first
genders = Gender.gender_hash genders = Gender.gender_hash
schools = School.school_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 = []
@ -34,7 +36,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:) rules:, incomes:)
end end
row_count += 1 row_count += 1
@ -50,7 +52,7 @@ class SurveyResponsesDataLoader
private private
def self.process_row(row:, rules:) def self.process_row(row:, rules:, incomes:)
return unless row.dese_id? return unless row.dese_id?
return unless row.school.present? return unless row.school.present?
@ -58,10 +60,10 @@ class SurveyResponsesDataLoader
return if rule.new(row:).skip_row? return if rule.new(row:).skip_row?
end end
process_survey_items(row:) process_survey_items(row:, incomes:)
end end
def self.process_survey_items(row:) def self.process_survey_items(row:, incomes:)
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
@ -70,19 +72,20 @@ 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:) create_or_update_response(survey_item_response: response, likert_score:, row:, survey_item:, incomes:)
end.compact end.compact
end end
def self.create_or_update_response(survey_item_response:, likert_score:, row:, survey_item:) def self.create_or_update_response(survey_item_response:, likert_score:, row:, survey_item:, incomes:)
gender = row.gender gender = row.gender
grade = row.grade grade = row.grade
income = incomes[row.income]
if survey_item_response.present? if survey_item_response.present?
survey_item_response.update!(likert_score:, grade:, gender:, recorded_date: row.recorded_date) survey_item_response.update!(likert_score:, grade:, gender:, recorded_date: row.recorded_date, income:)
[] []
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) likert_score:, grade:, gender:, recorded_date: row.recorded_date, income:)
end end
end end

@ -0,0 +1,17 @@
<div class="d-flex align-items-center mx-5">
<input
id="<%= id %>"
class="m-3 <%= name %>-checkbox form-check-input"
type="checkbox"
name="<%= name %>-checkbox"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= selected_items.include?(item) ? "checked" : "" %>
<%= @presenter.graph.slug == 'students-and-teachers' || @presenter.source.slug == 'all-data' ? "disabled" : "" %>
<%= @presenter.group.slug == name ? "" : "hidden" %>>
<label for="<%= id %>"
<%= @presenter.group.slug == name ? "" : "hidden" %>>
<%= label_text %>
</label>
</div>

@ -1,7 +1,7 @@
<h3 class="sub-header-4 mt-5">Data Filters</h3> <h3 class="sub-header-4 mt-5">Data Filters</h3>
<div class="bg-gray p-3" data-controller="analyze"> <div class="bg-gray p-3" data-controller="analyze">
<% @sources.each do |source| %> <% @presenter.sources.each do |source| %>
<input type="radio" <input type="radio"
id="<%= source.slug %>" id="<%= source.slug %>"
@ -9,7 +9,7 @@
name="source" name="source"
value="<%= base_url %>" value="<%= base_url %>"
data-action="click->analyze#refresh" data-action="click->analyze#refresh"
<%= source.slug == @source.slug ? "checked" : "" %>> <%= source.slug == @presenter.source.slug ? "checked" : "" %>>
<label for="<%= source.slug %>"><%= source.to_s %></label> <label for="<%= source.slug %>"><%= source.to_s %></label>
<% source.slices.each do | slice | %> <% source.slices.each do | slice | %>
@ -20,7 +20,7 @@
name="slice" name="slice"
value="<%= base_url %>" value="<%= base_url %>"
data-action="click->analyze#refresh" data-action="click->analyze#refresh"
<%= slice.slug == @slice.slug ? "checked" : "" %> <%= slice.slug == @presenter.slice.slug ? "checked" : "" %>
<%= slice.slug == "all-data" ? "hidden" : "" %>> <%= slice.slug == "all-data" ? "hidden" : "" %>>
<label for="<%= slice.slug %>" <label for="<%= slice.slug %>"
@ -34,8 +34,8 @@
</div> </div>
<script> <script>
window.source = "<%= @source.slug %>"; window.source = "<%= @presenter.source.slug %>";
window.slice = "<%= @slice.slug %>"; window.slice = "<%= @presenter.slice.slug %>";
window.group = "<%= @group.slug %>"; window.group = "<%= @presenter.group.slug %>";
window.graph = "<%= @graph.slug %>"; window.graph = "<%= @presenter.graph.slug %>";
</script> </script>

@ -2,12 +2,12 @@
<p>Select a category & subcategory to analyze measure-level results</p> <p>Select a category & subcategory to analyze measure-level results</p>
<select id="select-category" class="mx-3 form-select" data-id="category-dropdown" data-action="analyze#refresh"> <select id="select-category" class="mx-3 form-select" data-id="category-dropdown" data-action="analyze#refresh">
<% categories.each do |category| %> <% categories.each do |category| %>
<option value="<%= analyze_category_link(district: district, school: school, academic_year: academic_year, category: category) %>" <%= category.id == @category.id ? "selected": "" %>><%= "#{category.category_id}: #{category.name}" %></option> <option value="<%= analyze_category_link(district: district, school: school, academic_year: academic_year, category: category) %>" <%= category.id == @presenter.category.id ? "selected": "" %>><%= "#{category.category_id}: #{category.name}" %></option>
<% end %> <% end %>
</select> </select>
<select id="select-subcategory" class="mx-3 form-select mt-3" data-id="subcategory-dropdown" data-action="analyze#refresh"> <select id="select-subcategory" class="mx-3 form-select mt-3" data-id="subcategory-dropdown" data-action="analyze#refresh">
<% subcategories.each do |subcategory| %> <% subcategories.each do |subcategory| %>
<option value="<%= analyze_subcategory_link(district: district, school: school, academic_year: academic_year, category: category, subcategory: subcategory) %>" <%= subcategory.subcategory_id == @subcategory.subcategory_id ? "selected": "" %>> <option value="<%= analyze_subcategory_link(district: district, school: school, academic_year: academic_year, category: category, subcategory: subcategory) %>" <%= subcategory.subcategory_id == @presenter.subcategory.subcategory_id ? "selected": "" %>>
<%= "#{subcategory.subcategory_id}: #{subcategory.name}" %> <%= "#{subcategory.subcategory_id}: #{subcategory.name}" %>
</option> </option>
<% end %> <% end %>

@ -1,67 +1,23 @@
<select id="select-group" name="group" class="mx-4 form-select" data-id="group-dropdown" data-action="analyze#refresh"> <select id="select-group" name="group" class="mx-4 form-select" data-id="group-dropdown" data-action="analyze#refresh">
<% @groups.each do |group| %> <% @presenter.groups.each do |group| %>
<option id="<%= group.slug %>" name="group-option" value="<%= base_url %>" <%= group.slug == @group.slug ? "Selected": "" %>><%= group.name %> </option> <option id="<%= group.slug %>" name="group-option" value="<%= base_url %>" <%= group.slug == @presenter.group.slug ? "Selected": "" %>><%= group.name %> </option>
<% end %> <% end %>
</select> </select>
<p class="sub-header-5 mx-4 mt-3 font-size-14"> Select a group </p> <p class="sub-header-5 mx-4 mt-3 font-size-14"> Select a group </p>
<% @races.each do |race| %> <% @presenter.races.each do |race| %>
<div class="d-flex align-items-center mx-5"> <%= render(partial: "checkboxes", locals: {id: "race-#{race.slug}", item: race, selected_items: @presenter.selected_races, name: "race", label_text: race.designation}) %>
<input
id="<%= race.slug %>"
class="m-3 race-checkbox form-check-input"
type="checkbox"
name="race-checkbox"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= @selected_races.map(&:slug).include?(race.slug) ? "checked" : "" %>
<%= @graph.slug == 'students-and-teachers' || @source.slug == 'all-data' ? "disabled" : "" %>
<%= @group.slug == 'race' ? "" : "hidden" %>>
<label for="<%= race.qualtrics_code %>"
<%= @group.slug == 'race' ? "" : "hidden" %>>
<%= race.designation %>
</label>
</div>
<% end %> <% end %>
<% @grades.each do |grade| %> <% @presenter.grades.each do |grade| %>
<div class="d-flex align-items-center mx-5"> <%= render(partial: "checkboxes", locals: {id: "grade-#{grade}", item: grade, selected_items: @presenter.selected_grades, name: "grade", label_text: grade}) %>
<input
id="grade-<%= grade %>"
class="m-3 grade-checkbox form-check-input"
type="checkbox"
name="grade-checkbox"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= @selected_grades.include?(grade) ? "checked" : "" %>
<%= @graph.slug == 'students-and-teachers' || @source.slug == 'all-data' ? "disabled" : "" %>
<%= @group.slug == 'grade' ? "" : "hidden" %>>
<label for="grade-<%= grade %>"
<%= @group.slug == 'grade' ? "" : "hidden" %>>
<%= grade %>
</label>
</div>
<% end %> <% end %>
<% @genders.each do |gender| %> <% @presenter.genders.each do |gender| %>
<div class="d-flex align-items-center mx-5"> <%= render(partial: "checkboxes", locals: {id: "gender-#{gender.designation}", item: gender, selected_items: @presenter.selected_genders, name: "gender", label_text: gender.designation}) %>
<input <% end %>
id="gender-<%= gender.designation %>"
class="m-3 gender-checkbox form-check-input"
type="checkbox"
name="gender-checkbox"
value="<%= base_url %>"
data-action="click->analyze#refresh"
<%= @selected_genders.include?(gender) ? "checked" : "" %>
<%= @graph.slug == 'students-and-teachers' || @source.slug == 'all-data' ? "disabled" : "" %>
<%= @group.slug == 'gender' ? "" : "hidden" %>>
<label for="gender-<%= gender %>" <% @presenter.incomes.each do |income| %>
<%= @group.slug == 'gender' ? "" : "hidden" %>> <%= render(partial: "checkboxes", locals: {id: "income-#{income.slug}", item: income, selected_items: @presenter.selected_incomes, name: "income", label_text: income.designation}) %>
<%= gender.designation %>
</label>
</div>
<% end %> <% end %>

@ -1,9 +1,9 @@
<svg width="100%" height="<%= svg_height %>"> <svg width="100%" height="<%= svg_height %>">
<%= render partial: "graph_background", locals: {background: @background} %> <%= render partial: "graph_background", locals: {background: @background} %>
<% number_of_columns = @graph.columns.length %> <% number_of_columns = @presenter.graph.columns.length %>
<% @graph.columns.each_with_index do |column, index| %> <% @presenter.graph.columns.each_with_index do |column, index| %>
<% p = column.new(measure: measure, school: @school, academic_years: @selected_academic_years, position: index , number_of_columns:) %> <% p = column.new(measure: measure, school: @school, academic_years: @presenter.selected_academic_years, position: index , number_of_columns:) %>
<%= render partial: "grouped_bar_column", locals: {column: p} %> <%= render partial: "grouped_bar_column", locals: {column: p} %>
<% end %> <% end %>

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 493 B

@ -10,7 +10,7 @@
data-action="click->analyze#refresh" data-action="click->analyze#refresh"
<% empty_dataset = empty_dataset?(measures: measures, school: school, academic_year: year) %> <% empty_dataset = empty_dataset?(measures: measures, school: school, academic_year: year) %>
<% empty_survey_dataset = empty_survey_dataset?(measures: measures, school: school, academic_year: year) %> <% empty_survey_dataset = empty_survey_dataset?(measures: measures, school: school, academic_year: year) %>
<% if @graph.slug == 'all-data' %> <% if graph.slug == 'all-data' %>
<%= empty_dataset ? "disabled" : "" %> <%= empty_dataset ? "disabled" : "" %>
<% else %> <% else %>
<%= empty_survey_dataset ? "disabled" : "" %> <%= empty_survey_dataset ? "disabled" : "" %>
@ -18,13 +18,13 @@
> >
<label class="px-3" for="<%= year.range %>"><%= year.range %></label><br> <label class="px-3" for="<%= year.range %>"><%= year.range %></label><br>
<div class="bg-color-blue px-3" style="width:20px;height:20px;background-color:<%= colors[index] %>;"></div> <div class="bg-color-blue px-3" style="width:20px;height:20px;background-color:<%= colors[index] %>;"></div>
<% if @graph.slug == 'all-data' && empty_dataset %> <% if graph.slug == 'all-data' && empty_dataset %>
<i class="fa-solid fa-circle-exclamation px-3" <i class="fa-solid fa-circle-exclamation px-3"
data-bs-toggle="popover" data-bs-placement="right" data-bs-toggle="popover" data-bs-placement="right"
data-bs-content="No admin data OR teacher and student survey response rates below <%= ResponseRateCalculator::TEACHER_RATE_THRESHOLD %>%"> data-bs-content="No admin data OR teacher and student survey response rates below <%= ResponseRateCalculator::TEACHER_RATE_THRESHOLD %>%">
</i> </i>
<% end %> <% end %>
<% if @graph.slug != 'all-data' && empty_survey_dataset %> <% if graph.slug != 'all-data' && empty_survey_dataset %>
<i class="fa-solid fa-circle-exclamation px-3" <i class="fa-solid fa-circle-exclamation px-3"
data-bs-toggle="popover" data-bs-placement="right" data-bs-toggle="popover" data-bs-placement="right"
data-bs-content="Teacher and student survey response rates below <%= ResponseRateCalculator::TEACHER_RATE_THRESHOLD %>%"> data-bs-content="Teacher and student survey response rates below <%= ResponseRateCalculator::TEACHER_RATE_THRESHOLD %>%">

@ -3,19 +3,19 @@
<% end %> <% end %>
<div class="graph-content"> <div class="graph-content">
<div class="breadcrumbs sub-header-4"> <div class="breadcrumbs sub-header-4">
<%= @category.category_id %>:<%= @category.name %> > <%= @subcategory.subcategory_id %>:<%= @subcategory.name %> <%= @presenter.category.category_id %>:<%= @presenter.category.name %> > <%= @presenter.subcategory.subcategory_id %>:<%= @presenter.subcategory.name %>
</div> </div>
<hr> <hr>
</div> </div>
<div class="d-flex flex-row pt-5 row"> <div class="d-flex flex-row pt-5 row">
<div class="d-flex flex-column flex-grow-6 bg-color-white col-3 px-5" data-controller="analyze"> <div class="d-flex flex-column flex-grow-6 bg-color-white col-3 px-5" data-controller="analyze">
<%= render partial: "focus_area", locals: {categories: @categories, district: @district, school: @school, academic_year: @academic_year, category: @category, subcategories: @subcategories} %> <%= render partial: "focus_area", locals: {categories: @presenter.categories, district: @district, school: @school, academic_year: @academic_year, category: @presenter.category, subcategories: @presenter.subcategories} %>
<%= render partial: "school_years", locals: {available_academic_years: @available_academic_years, selected_academic_years: @selected_academic_years, district: @district, school: @school, academic_year: @academic_year, category: @category, subcategory: @subcategory, measures: @measures} %> <%= 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: @category, subcategory: @subcategory} %> <%= render partial: "data_filters", locals: {district: @district, school: @school, academic_year: @academic_year, category: @presenter.category, subcategory: @presenter.subcategory} %>
</div> </div>
<% cache [@subcategory, @school, @selected_academic_years, @graph, @selected_races, @race_score_timestamp, @selected_grades, @grades, @selected_genders, @genders] do %> <% 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 %>
<div class="bg-color-white flex-grow-1 col-9"> <div class="bg-color-white flex-grow-1 col-9">
<% @measures.each do |measure| %> <% @presenter.measures.each do |measure| %>
<section class="mb-6"> <section class="mb-6">
<p class="construct-id">Measure <%= measure.measure_id %></p> <p class="construct-id">Measure <%= measure.measure_id %></p>
<h2> <%= measure.name %> </h2> <h2> <%= measure.name %> </h2>

@ -1,11 +1,11 @@
Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income
1,American Indian or Alaskan Native,2,Male 1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged - N
2,Asian or Pacific Islander,1,Female 2,Asian or Pacific Islander,1,Female,Economically Disadvantaged - Y
3,Black or African American,4,Non-Binary 3,Black or African American,4,Non-Binary,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
2 1 American Indian or Alaskan Native 2 Male Economically Disadvantaged - N
3 2 Asian or Pacific Islander 1 Female Economically Disadvantaged - Y
4 3 Black or African American 4 Non-Binary 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,9 @@
class CreateIncomes < ActiveRecord::Migration[7.0]
def change
create_table :incomes do |t|
t.string :designation
t.timestamps
end
end
end

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

@ -0,0 +1,6 @@
class AddSlugToIncome < ActiveRecord::Migration[7.0]
def change
add_column :incomes, :slug, :string
add_index :incomes, :slug, unique: 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_06_22_224103) do ActiveRecord::Schema[7.0].define(version: 2023_06_30_215110) 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"
@ -80,6 +80,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_22_224103) do
t.string "designation" t.string "designation"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.string "slug"
t.index ["slug"], name: "index_incomes_on_slug", unique: true
end end
create_table "legacy_attempts", id: :serial, force: :cascade do |t| create_table "legacy_attempts", id: :serial, force: :cascade do |t|

@ -183,10 +183,6 @@ namespace :one_off do
end end
puts "=====================> Completed loading #{Student.count - student_count} students. #{Student.count} total students" puts "=====================> Completed loading #{Student.count - student_count} students. #{Student.count} total students"
puts 'Resetting response rates'
ResponseRateLoader.reset(academic_years: [AcademicYear.find_by_range('2022-23')], schools:)
puts "=====================> Completed loading #{ResponseRate.count} response rates"
puts 'Resetting race scores' puts 'Resetting race scores'
RaceScoreLoader.reset(fast_processing: false, academic_years: [AcademicYear.find_by_range('2022-23')], schools:) RaceScoreLoader.reset(fast_processing: false, academic_years: [AcademicYear.find_by_range('2022-23')], schools:)
puts "=====================> Completed loading #{RaceScore.count} race scores" puts "=====================> Completed loading #{RaceScore.count} race scores"
@ -228,30 +224,30 @@ namespace :one_off do
sum+=c.values.first sum+=c.values.first
nof_si+=1 nof_si+=1
end end
end end
avg = sum.to_f/ nof_si avg = sum.to_f/ nof_si
counts.each do |key, value| counts.each do |key, value|
if key[0] == sc.id && key[1] == ay.id if key[0] == sc.id && key[1] == ay.id
count = value.values.first count = value.values.first
percentage_diff = ((count-avg) / avg) * 100 percentage_diff = ((count-avg) / avg) * 100
counts[key] = { count: count, percentage_diff: percentage_diff } counts[key] = { count: count, percentage_diff: percentage_diff }
end end
end end
end end end end
counts.each do |key, value| counts.each do |key, value|
output_rows << [School.where(id:key[0]).pluck(:name) , AcademicYear.where(id:key[1]).pluck(:range) , SurveyItem.where(id:key[2]).pluck(:survey_item_id), SurveyItem.where(id:key[2]).pluck(:prompt), value[:count], value[:percentage_diff]] output_rows << [School.where(id:key[0]).pluck(:name) , AcademicYear.where(id:key[1]).pluck(:range) , SurveyItem.where(id:key[2]).pluck(:survey_item_id), SurveyItem.where(id:key[2]).pluck(:prompt), value[:count], value[:percentage_diff]]
end end
file = File.new('teacher_survey_questions.csv', 'w') file = File.new('teacher_survey_questions.csv', 'w')
CSV.open(file, 'w', write_headers: true, headers: headers) do |csv| CSV.open(file, 'w', write_headers: true, headers: headers) do |csv|
output_rows.each do |row| output_rows.each do |row|
csv << row csv << row
end end
end end
file.close file.close
puts "CSV report of teacher survey item responses with removed stray responses.csv" puts "CSV report of teacher survey item responses with removed stray responses.csv"
end end
@ -288,31 +284,31 @@ namespace :one_off do
sum+=c.values.first sum+=c.values.first
nof_si+=1 nof_si+=1
end end
end
end end
end
avg = sum.to_f/ nof_si avg = sum.to_f/ nof_si
counts.each do |key, value| counts.each do |key, value|
if key[0] == sc.id && key[1] == ay.id if key[0] == sc.id && key[1] == ay.id
count = value.values.first count = value.values.first
percentage_diff = ((count-avg) / avg) * 100 percentage_diff = ((count-avg) / avg) * 100
counts[key] = { count: count, percentage_diff: percentage_diff } counts[key] = { count: count, percentage_diff: percentage_diff }
end end
end end
end end end end end end
counts.each do |key, value| counts.each do |key, value|
output_rows << [School.where(id:key[0]).pluck(:name) , AcademicYear.where(id:key[1]).pluck(:range) , SurveyItem.where(id:key[2]).pluck(:survey_item_id), SurveyItem.where(id:key[2]).pluck(:prompt), value[:count], value[:percentage_diff]] output_rows << [School.where(id:key[0]).pluck(:name) , AcademicYear.where(id:key[1]).pluck(:range) , SurveyItem.where(id:key[2]).pluck(:survey_item_id), SurveyItem.where(id:key[2]).pluck(:prompt), value[:count], value[:percentage_diff]]
end end
file = File.new('short_survey_questions.csv', 'w') file = File.new('short_survey_questions.csv', 'w')
CSV.open(file, 'w', write_headers: true, headers: headers) do |csv| CSV.open(file, 'w', write_headers: true, headers: headers) do |csv|
output_rows.each do |row| output_rows.each do |row|
csv << row csv << row
end end
end end
file.close file.close
puts "CSV report of short survey item responses with removed stray responses.csv" puts "CSV report of short survey item responses with removed stray responses.csv"
end end
@ -349,31 +345,31 @@ namespace :one_off do
sum+=c.values.first sum+=c.values.first
nof_si+=1 nof_si+=1
end end
end end
end end
avg = sum.to_f/ nof_si avg = sum.to_f/ nof_si
counts.each do |key, value| counts.each do |key, value|
if key[0] == sc.id && key[1] == ay.id if key[0] == sc.id && key[1] == ay.id
count = value.values.first count = value.values.first
percentage_diff = ((count-avg) / avg) * 100 percentage_diff = ((count-avg) / avg) * 100
counts[key] = { count: count, percentage_diff: percentage_diff } counts[key] = { count: count, percentage_diff: percentage_diff }
end end
end end
end end end end end end
counts.each do |key, value| counts.each do |key, value|
output_rows << [School.where(id:key[0]).pluck(:name) , AcademicYear.where(id:key[1]).pluck(:range) , SurveyItem.where(id:key[2]).pluck(:survey_item_id), SurveyItem.where(id:key[2]).pluck(:prompt), value[:count], value[:percentage_diff]] output_rows << [School.where(id:key[0]).pluck(:name) , AcademicYear.where(id:key[1]).pluck(:range) , SurveyItem.where(id:key[2]).pluck(:survey_item_id), SurveyItem.where(id:key[2]).pluck(:prompt), value[:count], value[:percentage_diff]]
end end
file = File.new('early_education_surveys_questions.csv', 'w') file = File.new('early_education_surveys_questions.csv', 'w')
CSV.open(file, 'w', write_headers: true, headers: headers) do |csv| CSV.open(file, 'w', write_headers: true, headers: headers) do |csv|
output_rows.each do |row| output_rows.each do |row|
csv << row csv << row
end end
end end
file.close file.close
puts "CSV report of early_education_surveys items with removed stray responses.csv" puts "CSV report of early_education_surveys items with removed stray responses.csv"
end end
@ -381,19 +377,19 @@ namespace :one_off do
desc "Generate CSV report of survey item responses" desc "Generate CSV report of survey item responses"
task stray_responses: :environment do task stray_responses: :environment do
headers = ['School ID', 'Academic Year', 'Survey Item', 'Count','SurveyItemResponse ids'] headers = ['School ID', 'Academic Year', 'Survey Item', 'Count','SurveyItemResponse ids']
output_rows = [] output_rows = []
sir_ids=[] sir_ids=[]
School.all.each do |sc| School.all.each do |sc|
AcademicYear.all.each do |ay| AcademicYear.all.each do |ay|
SurveyItem.all.each do |si| SurveyItem.all.each do |si|
count = SurveyItemResponse.where(school: sc, academic_year: ay, survey_item: si).count count = SurveyItemResponse.where(school: sc, academic_year: ay, survey_item: si).count
sir_ids= SurveyItemResponse.where(school: sc, academic_year: ay, survey_item: si).pluck(:response_id) sir_ids= SurveyItemResponse.where(school: sc, academic_year: ay, survey_item: si).pluck(:response_id)
if count > 0 && count < 10 if count > 0 && count < 10
@ -403,7 +399,7 @@ namespace :one_off do
end end
end end
file = File.new('stray_responses.csv', 'w') file = File.new('stray_responses.csv', 'w')
CSV.open(file, 'w', write_headers: true, headers: headers) do |csv| CSV.open(file, 'w', write_headers: true, headers: headers) do |csv|
@ -412,7 +408,7 @@ namespace :one_off do
end end
end end
file.close file.close
puts "CSV report of survey item responses created in stray_responses.csv" puts "CSV report of survey item responses created in stray_responses.csv"

@ -21,7 +21,7 @@
"debounce": "^1.2.1", "debounce": "^1.2.1",
"esbuild": "^0.17.12", "esbuild": "^0.17.12",
"sass": "^1.43.4", "sass": "^1.43.4",
"semver": "7.5.2" "semver": "^7.5.2"
}, },
"scripts": { "scripts": {
"build": "esbuild app/javascript/*.* --bundle --outdir=app/assets/builds", "build": "esbuild app/javascript/*.* --bundle --outdir=app/assets/builds",

@ -1,4 +1,8 @@
FactoryBot.define do FactoryBot.define do
factory :income do
designation { "MyString" }
end
factory :gender do factory :gender do
qualtrics_code { 1 } qualtrics_code { 1 }
designation { 'MyString' } designation { 'MyString' }

@ -1,11 +1,11 @@
Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender Race Qualtrics Code,Race/Ethnicity,Gender Qualtrics Code,Sex/Gender,Income
1,American Indian or Alaskan Native,2,Male 1,American Indian or Alaskan Native,2,Male,Economically Disadvantaged N
2,Asian or Pacific Islander,1,Female 2,Asian or Pacific Islander,1,Female,Economically Disadvantaged Y
3,Black or African American,4,Non-Binary 3,Black or African American,4,Non-Binary,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
2 1 American Indian or Alaskan Native 2 Male Economically Disadvantaged – N
3 2 Asian or Pacific Islander 1 Female Economically Disadvantaged – Y
4 3 Black or African American 4 Non-Binary 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,9 +1,9 @@
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 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 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, 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 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 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 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 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, 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",,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"1,2,3,4,5,8",,,,,

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

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Income, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

@ -0,0 +1,468 @@
require 'rails_helper'
describe Analyze::Presenter do
let(:category) { create(:category, category_id: '1', name: 'first category') }
let(:category_2) { create(:category, category_id: '2', name: 'second category') }
let(:wrong_category) { create(:category, category_id: '999', name: 'wrong category') }
let(:subcategory) { create(:subcategory, subcategory_id: '1A', name: 'first subcategory', category:) }
let(:subcategory_2) { create(:subcategory, subcategory_id: '2A', name: 'second subcategory', category: category_2) }
let(:wrong_subcategory) do
create(:subcategory, subcategory_id: '99A', name: 'wrong subcategory', category: wrong_category)
end
let(:measure) { create(:measure, measure_id: '1A-i', name: 'first measure', subcategory:) }
let(:measure_2) { create(:measure, measure_id: '2A-i', name: 'second measure', subcategory: subcategory_2) }
let(:wrong_measure) do
create(:wrong_measure, measure_id: '99A', name: 'wrong measure', subcategory: wrong_subcategory)
end
let(:scale) { create(:student_scale, measure:) }
let(:survey_item) { create(:student_survey_item, scale:) }
let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) }
let(:ay_2021_22) { create(:academic_year, range: '2021-22') }
let(:ay_2022_23) { create(:academic_year, range: '2022-23') }
let(:wrong_ay) { create(:academic_year, range: '9999-99') }
let(:black) { create(:race, designation: 'black', qualtrics_code: 1) }
let(:white) { create(:race, designation: 'white', qualtrics_code: 2) }
let(:wrong_race) { create(:race, designation: 'wrong race', qualtrics_code: 9999) }
let(:female) { create(:gender, designation: 'female', qualtrics_code: 1) }
let(:male) { create(:gender, designation: 'male', qualtrics_code: 2) }
let(:wrong_gender) { create(:gender, designation: 'wrong_gender', qualtrics_code: 2) }
context '.category' do
before :each do
category
category_2
wrong_category
end
context 'when a category is provided in the params hash' do
it 'returns the category with the given id' do
params = { category: '1' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.category).to eq category
params = { category: '2' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.category).to eq category_2
end
end
context 'when no category is provided in the params hash' do
it 'returns the first category' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.category).to eq category
end
end
context 'when a category that doesnt exist in the database is passed' do
it 'returns the first category' do
params = { category: '4' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.category).to eq category
end
end
end
context '.categories' do
it 'returns all categories' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.categories).to eq Category.all.order(:category_id)
end
end
context '.subcategory' do
before :each do
category
category_2
wrong_category
subcategory
subcategory_2
wrong_subcategory
end
context 'when a subcategory is provided in the params hash' do
it 'returns the subcategory with the given id' do
params = { subcategory: '1A' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.subcategory).to eq subcategory
params = { subcategory: '2A' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.subcategory).to eq subcategory_2
end
end
context 'when no subcategory is provided in the params hash' do
it 'returns the first subcategory' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.subcategory).to eq subcategory
end
end
context 'when a subcategory that doesnt exist in the database is passed' do
it 'returns the first subcategory' do
params = { subcategory: '4A' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.subcategory).to eq subcategory
end
end
end
context '.subcategories' do
before :each do
category
wrong_category
subcategory
wrong_subcategory
end
it 'returns all subcategories for a given category' do
params = { category: '1' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.subcategories).to eq category.subcategories.all.order(:subcategory_id)
end
end
context '.measures' do
before :each do
category
subcategory
measure
end
it 'returns all measures for a given subcategory' do
params = { category: '1' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.measures).to eq category.subcategories.flat_map(&:measures)
end
end
context '.academic_years' do
before :each do
ay_2021_22
ay_2022_23
end
it 'returns all academic years' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.academic_years).to eq AcademicYear.all.order(:range)
end
end
context '.selected_academic_years' do
before :each do
ay_2021_22
ay_2022_23
wrong_ay
end
context 'when no academic years are provided in the params hash' do
it 'returns an empty array if no params are provided' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_academic_years).to eq []
end
end
context 'when a single academic year is provided in the params hash' do
it 'returns the academic year with the given id' do
params = { academic_years: '2021-22' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_academic_years).to eq [AcademicYear.find_by_range('2021-22')]
params = { academic_years: '2022-23' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_academic_years).to eq [AcademicYear.find_by_range('2022-23')]
end
end
context 'when multiple academic years are provided in the params hash' do
it 'returns the academic year with the given ids' do
params = { academic_years: '2021-22,2022-23' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_academic_years).to eq [AcademicYear.find_by_range('2021-22'),
AcademicYear.find_by_range('2022-23')]
end
end
end
context '.races' do
before :each do
black
white
wrong_race
end
it 'returns all races' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.races).to eq Race.all.order(designation: :ASC)
end
end
context '.selected_races' do
before :each do
black
white
wrong_race
end
context 'when no races are provided in the params hash' do
it 'returns an array with all races if no params are provided' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_races).to eq presenter.races
end
end
context 'when one race is provided in the params hash' do
it 'returns a single race with the given slug' do
params = { races: 'white' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_races).to eq [Race.find_by_slug('white')]
params = { races: 'black' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_races).to eq [Race.find_by_slug('black')]
end
end
context 'when multiple races are provided in the params hash' do
it 'returns multiple races with the given slugs' do
params = { races: 'white,black' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_races).to eq [Race.find_by_slug('white'), Race.find_by_slug('black')]
end
end
end
context '.graph' do
context 'when no params are provided' do
it 'returns the default(first) graph object' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.graph.to_s).to eq Analyze::Graph::AllData.new.to_s
end
end
context 'when a graph is provided in the params hash' do
it 'returns the graph object with the given slug' do
params = { graph: 'all_data' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.graph.to_s).to eq Analyze::Graph::AllData.new.to_s
params = { graph: 'students-and-teachers' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.graph.to_s).to eq Analyze::Graph::StudentsAndTeachers.new.to_s
params = { graph: 'students-by-race' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.graph.to_s).to eq Analyze::Graph::StudentsByRace.new(races: nil).to_s
params = { graph: 'students-by-grade' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.graph.to_s).to eq Analyze::Graph::StudentsByGrade.new(grades: nil).to_s
params = { graph: 'students-by-gender' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.graph.to_s).to eq Analyze::Graph::StudentsByGender.new(genders: nil).to_s
end
end
context 'when a parameter that does not match a graph is provided' do
it 'returns the default(first) graph object' do
params = { graph: 'invalid parameter' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.graph.to_s).to eq Analyze::Graph::AllData.new.to_s
end
end
end
context '.grades' do
before :each do
school
academic_year
create_list(:survey_item_response, 20, school:, academic_year:, grade: 1, survey_item:)
create_list(:survey_item_response, 20, school:, academic_year:, grade: 2, survey_item:)
create_list(:survey_item_response, 20, school:, academic_year:, grade: 3, survey_item:)
create_list(:survey_item_response, 20, school:, academic_year:, grade: 4, survey_item:)
end
context 'when no grades are provided in the params hash' do
it 'returns the set of grades for which there are more than ten responses' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.grades).to eq [1, 2, 3, 4]
end
end
end
context '.selected_grades' do
before :each do
school
academic_year
create_list(:survey_item_response, 20, school:, academic_year:, grade: 1, survey_item:)
create_list(:survey_item_response, 20, school:, academic_year:, grade: 2, survey_item:)
create_list(:survey_item_response, 20, school:, academic_year:, grade: 3, survey_item:)
create_list(:survey_item_response, 20, school:, academic_year:, grade: 4, survey_item:)
end
context 'when no grades are provided in the params hash' do
it 'returns only the set of grades selected even if other grades have sufficient responses' do
params = { grades: '1,2,3' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_grades).to eq [1, 2, 3]
end
end
end
context '.selected_genders' do
before :each do
male
female
wrong_gender
end
context 'when no parameters are provided' do
it 'returns all genders' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_genders).to eq Gender.all
end
end
context 'when a single gender is provided in the params hash' do
it 'returns the gender with the given designation' do
params = { genders: 'female' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_genders).to eq [Gender.find_by_designation('female')]
params = { genders: 'male' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_genders).to eq [Gender.find_by_designation('male')]
end
end
context 'when multiple genders are provided in the params hash' do
it 'returns multilple genders with the given designations' do
params = { genders: 'female,male' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.selected_genders).to eq [Gender.find_by_designation('female'),
Gender.find_by_designation('male')]
end
end
end
context '.genders' do
before :each do
female
male
end
it 'returns all genders' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.genders).to eq Gender.all
end
end
context '.group' do
context 'when no parameters are provided' do
it 'returns the first item in the list of groups' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.group.slug).to eq presenter.groups.first.slug
end
end
context 'when a group is provided in the params hash' do
it 'returns the group with the given slug' do
params = { group: 'gender' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.group.slug).to eq 'gender'
params = { group: 'grade' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.group.slug).to eq 'grade'
params = { group: 'race' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.group.slug).to eq 'race'
# Not yet implemented
# params = { group: 'income' }
# presenter = Analyze::Presenter.new(params:, school:, academic_year:)
# expect(presenter.group.slug).to eq 'income'
end
end
context 'when a parameter that does not match a group is provided' do
it 'returns the first item in the list of groups' do
params = { group: 'invalid group' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.group.slug).to eq presenter.groups.first.slug
end
end
end
context '.slice' do
context 'when no parameters are provided' do
it 'returns the first item in the list of slices' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.slice.slug).to eq 'all-data'
end
end
context 'when a slice is provided in the params hash' do
it 'returns the slice with the given slug' do
params = { source: 'survey-data-only', slice: 'students-and-teachers' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.slice.slug).to eq 'students-and-teachers'
end
end
context 'when a slice is provided but the source is left blank ' do
it 'returns the slice from the default source (all-data)' do
params = { slice: 'students-and-teachers' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.slice.slug).to eq 'all-data'
end
end
context 'when a parameter that does not match a slice is provided' do
it 'it returns the first slice from the chosen source' do
params = { source: 'survey-data-only', slice: 'invalid-slice' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.slice.slug).to eq 'students-and-teachers'
end
end
end
context '.source' do
context 'when no parameters are provided' do
it 'returns the first item in the list of sources' do
params = {}
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.slice.slug).to eq 'all-data'
end
end
context 'when a source is provided in the params hash' do
it 'returns the source with the given slug' do
params = { source: 'all-data' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.source.slug).to eq 'all-data'
params = { source: 'survey-data-only' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.source.slug).to eq 'survey-data-only'
end
end
context 'when a parameter that does not match a source is provided' do
it 'returns the first item in the list of sources' do
params = { source: 'invalid-source' }
presenter = Analyze::Presenter.new(params:, school:, academic_year:)
expect(presenter.slice.slug).to eq 'all-data'
end
end
end
end

@ -227,12 +227,12 @@ describe GroupedBarColumnPresenter do
# it_behaves_like 'y_offset' # it_behaves_like 'y_offset'
context 'and the score is right at the approval low benchmark' do context 'and the score is right at the approval low benchmark' do
it "where bar would normally have a height of 0, we inflate the height to be at least the minimum bar height of #{AnalyzeBarPresenter::MINIMUM_BAR_HEIGHT}" do it "where bar would normally have a height of 0, we inflate the height to be at least the minimum bar height of #{Analyze::BarPresenter::MINIMUM_BAR_HEIGHT}" do
expect(student_presenter.bars[year_index].bar_height_percentage).to be_within(0.01).of(AnalyzeBarPresenter::MINIMUM_BAR_HEIGHT) expect(student_presenter.bars[year_index].bar_height_percentage).to be_within(0.01).of(Analyze::BarPresenter::MINIMUM_BAR_HEIGHT)
end end
it "where the bar would normally start at the approval low benchmark, it shifts up to accomodate it being grown to the minimum bar height of #{AnalyzeBarPresenter::MINIMUM_BAR_HEIGHT}" do it "where the bar would normally start at the approval low benchmark, it shifts up to accomodate it being grown to the minimum bar height of #{Analyze::BarPresenter::MINIMUM_BAR_HEIGHT}" do
expect(student_presenter.bars[year_index].y_offset).to be_within(0.01).of(analyze_zone_height * 2 - AnalyzeBarPresenter::MINIMUM_BAR_HEIGHT) expect(student_presenter.bars[year_index].y_offset).to be_within(0.01).of(analyze_zone_height * 2 - Analyze::BarPresenter::MINIMUM_BAR_HEIGHT)
end end
end end
end end
@ -257,8 +257,8 @@ describe GroupedBarColumnPresenter do
academic_year:, likert_score: 4) academic_year:, likert_score: 4)
end end
it "it rounds to the the minimum bar height of #{AnalyzeBarPresenter::MINIMUM_BAR_HEIGHT} " do it "it rounds to the the minimum bar height of #{Analyze::BarPresenter::MINIMUM_BAR_HEIGHT} " do
expect(student_presenter.bars[year_index].bar_height_percentage).to be_within(0.01).of(AnalyzeBarPresenter::MINIMUM_BAR_HEIGHT) expect(student_presenter.bars[year_index].bar_height_percentage).to be_within(0.01).of(Analyze::BarPresenter::MINIMUM_BAR_HEIGHT)
end end
end end
end end

@ -13,6 +13,10 @@ describe DemographicLoader do
} }
end end
let(:incomes) do
['Economically Disadvantaged N', 'Economically Disadvantaged Y', 'Unknown']
end
before :each do before :each do
DemographicLoader.load_data(filepath:) DemographicLoader.load_data(filepath:)
end end
@ -46,5 +50,12 @@ describe DemographicLoader do
expect(Gender.find_by_designation(key).qualtrics_code).to eq value expect(Gender.find_by_designation(key).qualtrics_code).to eq value
end end
end end
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
end end
end end

@ -1,40 +1,40 @@
require "rails_helper" require 'rails_helper'
RSpec.describe SurveyItemValues, type: :model do RSpec.describe SurveyItemValues, type: :model do
let(:headers) do let(:headers) do
["StartDate", "EndDate", "Status", "IPAddress", "Progress", "Duration (in seconds)", "Finished", "RecordedDate", ['StartDate', 'EndDate', 'Status', 'IPAddress', 'Progress', 'Duration (in seconds)', 'Finished', 'RecordedDate',
"ResponseId", "RecipientLastName", "RecipientFirstName", "RecipientEmail", "ExternalReference", "LocationLatitude", "LocationLongitude", "DistributionChannel", "UserLanguage", "District", "School- Lee", "School- Maynard", "LASID", "Grade", "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-acpr-q3", "s-acpr-q4", "s-cure-q1", "s-cure-q2", "s-cure-q3", "s-cure-q4", "s-sten-q1", "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", "s-grmi-q2", "s-grmi-q3", "s-grmi-q4", "s-appa-q1", "s-appa-q2", "s-appa-q3", "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-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-sust-q1", "s-sust-q2", "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", "Gender", "Race"] 'ResponseId', 'RecipientLastName', 'RecipientFirstName', 'RecipientEmail', 'ExternalReference', 'LocationLatitude', 'LocationLongitude', 'DistributionChannel', 'UserLanguage', 'District', 'School- Lee', 'School- Maynard', 'LASID', 'Grade', '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-acpr-q3', 's-acpr-q4', 's-cure-q1', 's-cure-q2', 's-cure-q3', 's-cure-q4', 's-sten-q1', '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', 's-grmi-q2', 's-grmi-q3', 's-grmi-q4', 's-appa-q1', 's-appa-q2', 's-appa-q3', '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-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-sust-q1', 's-sust-q2', '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', 'Gender', 'Race']
end end
let(:genders) do let(:genders) do
create(:gender, qualtrics_code: 1) create(:gender, qualtrics_code: 1)
Gender.gender_hash Gender.gender_hash
end end
let(:survey_items) { [] } let(:survey_items) { [] }
let(:district) { create(:district, name: "Attleboro") } let(:district) { create(:district, name: 'Attleboro') }
let(:attleboro) do let(:attleboro) do
create(:school, name: "Attleboro", dese_id: 1234, district:) create(:school, name: 'Attleboro', dese_id: 1234, district:)
end end
let(:attleboro_respondents) do let(:attleboro_respondents) do
create(:respondent, school: attleboro, academic_year: ay_2022_23, nine: 40, ten: 40, eleven: 40, twelve: 40) create(:respondent, school: attleboro, academic_year: ay_2022_23, nine: 40, ten: 40, eleven: 40, twelve: 40)
end end
let(:schools) { School.school_hash } let(:schools) { School.school_hash }
let(:recorded_date) { "2023-04-01" } let(:recorded_date) { '2023-04-01' }
let(:ay_2022_23) do let(:ay_2022_23) do
create(:academic_year, range: "2022-23") create(:academic_year, range: '2022-23')
end end
let(:common_headers) do let(:common_headers) do
["Recorded Date", "DeseID", "ResponseID", "Duration (in seconds)", "Gender", "Grade"] ['Recorded Date', 'DeseID', 'ResponseID', 'Duration (in seconds)', 'Gender', 'Grade']
end end
let(:standard_survey_items) do let(:standard_survey_items) do
survey_item_ids = %w[s-peff-q1 s-peff-q2 s-peff-q3 s-peff-q4 s-peff-q5 s-peff-q6 s-phys-q1 s-phys-q2 s-phys-q3 s-phys-q4 survey_item_ids = %w[s-peff-q1 s-peff-q2 s-peff-q3 s-peff-q4 s-peff-q5 s-peff-q6 s-phys-q1 s-phys-q2 s-phys-q3 s-phys-q4
s-emsa-q1 s-emsa-q2 s-emsa-q3 s-sbel-q1 s-sbel-q2 s-sbel-q3 s-sbel-q4 s-sbel-q5 s-tint-q1 s-tint-q2 s-emsa-q1 s-emsa-q2 s-emsa-q3 s-sbel-q1 s-sbel-q2 s-sbel-q3 s-sbel-q4 s-sbel-q5 s-tint-q1 s-tint-q2
s-tint-q3 s-tint-q4 s-tint-q5 s-vale-q1 s-vale-q2 s-vale-q3 s-vale-q4 s-acpr-q1 s-acpr-q2 s-acpr-q3 s-tint-q3 s-tint-q4 s-tint-q5 s-vale-q1 s-vale-q2 s-vale-q3 s-vale-q4 s-acpr-q1 s-acpr-q2 s-acpr-q3
s-acpr-q4 s-sust-q1 s-sust-q2 s-cure-q1 s-cure-q2 s-cure-q3 s-cure-q4 s-sten-q1 s-sten-q2 s-sten-q3 s-acpr-q4 s-sust-q1 s-sust-q2 s-cure-q1 s-cure-q2 s-cure-q3 s-cure-q4 s-sten-q1 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-grit-q1 s-grit-q2 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-grit-q1 s-grit-q2
s-grit-q3 s-grit-q4 s-grmi-q1 s-grmi-q2 s-grmi-q3 s-grmi-q4 s-expa-q1 s-appa-q1 s-appa-q2 s-appa-q3 s-grit-q3 s-grit-q4 s-grmi-q1 s-grmi-q2 s-grmi-q3 s-grmi-q4 s-expa-q1 s-appa-q1 s-appa-q2 s-appa-q3
s-acst-q1 s-acst-q2 s-acst-q3 s-poaf-q1 s-poaf-q2 s-poaf-q3 s-poaf-q4] s-acst-q1 s-acst-q2 s-acst-q3 s-poaf-q1 s-poaf-q2 s-poaf-q3 s-poaf-q4]
survey_item_ids.map do |survey_item_id| survey_item_ids.map do |survey_item_id|
create(:survey_item, survey_item_id:) create(:survey_item, survey_item_id:)
end end
@ -42,10 +42,10 @@ RSpec.describe SurveyItemValues, type: :model do
end end
let(:short_form_survey_items) do let(:short_form_survey_items) do
survey_item_ids = [create(:survey_item, survey_item_id: "s-phys-q1", on_short_form: true), survey_item_ids = [create(:survey_item, survey_item_id: 's-phys-q1', on_short_form: true),
create(:survey_item, survey_item_id: "s-phys-q2", on_short_form: true), create(:survey_item, survey_item_id: 's-phys-q2', on_short_form: true),
create(:survey_item, survey_item_id: "s-phys-q3", create(:survey_item, survey_item_id: 's-phys-q3',
on_short_form: true)].map(&:survey_item_id) on_short_form: true)].map(&:survey_item_id)
survey_item_ids.map do |survey_item_id| survey_item_ids.map do |survey_item_id|
create(:survey_item, survey_item_id:) create(:survey_item, survey_item_id:)
end end
@ -53,9 +53,9 @@ RSpec.describe SurveyItemValues, type: :model do
end end
let(:early_education_survey_items) do let(:early_education_survey_items) do
survey_item_ids = [create(:survey_item, survey_item_id: "s-emsa-es1"), survey_item_ids = [create(:survey_item, survey_item_id: 's-emsa-es1'),
create(:survey_item, survey_item_id: "s-emsa-es2"), create(:survey_item, survey_item_id: 's-emsa-es2'),
create(:survey_item, survey_item_id: "s-emsa-es3")].map(&:survey_item_id) create(:survey_item, survey_item_id: 's-emsa-es3')].map(&:survey_item_id)
survey_item_ids.map do |survey_item_id| survey_item_ids.map do |survey_item_id|
create(:survey_item, survey_item_id:) create(:survey_item, survey_item_id:)
end end
@ -64,12 +64,12 @@ RSpec.describe SurveyItemValues, type: :model do
let(:teacher_survey_items) do let(:teacher_survey_items) do
survey_item_ids = %w[t-prep-q1 t-prep-q2 t-prep-q3 t-ieff-q1 t-ieff-q2 t-ieff-q3 t-ieff-q4 t-pcom-q1 t-pcom-q2 t-pcom-q3 survey_item_ids = %w[t-prep-q1 t-prep-q2 t-prep-q3 t-ieff-q1 t-ieff-q2 t-ieff-q3 t-ieff-q4 t-pcom-q1 t-pcom-q2 t-pcom-q3
t-pcom-q4 t-pcom-q5 t-inle-q1 t-inle-q2 t-inle-q3 t-prtr-q1 t-prtr-q2 t-prtr-q3 t-coll-q1 t-coll-q2 t-pcom-q4 t-pcom-q5 t-inle-q1 t-inle-q2 t-inle-q3 t-prtr-q1 t-prtr-q2 t-prtr-q3 t-coll-q1 t-coll-q2
t-coll-q3 t-qupd-q1 t-qupd-q2 t-qupd-q3 t-qupd-q4 t-pvic-q1 t-pvic-q2 t-pvic-q3 t-psup-q1 t-psup-q2 t-coll-q3 t-qupd-q1 t-qupd-q2 t-qupd-q3 t-qupd-q4 t-pvic-q1 t-pvic-q2 t-pvic-q3 t-psup-q1 t-psup-q2
t-psup-q3 t-psup-q4 t-acch-q1 t-acch-q2 t-acch-q3 t-reso-q1 t-reso-q2 t-reso-q3 t-reso-q4 t-reso-q5 t-psup-q3 t-psup-q4 t-acch-q1 t-acch-q2 t-acch-q3 t-reso-q1 t-reso-q2 t-reso-q3 t-reso-q4 t-reso-q5
t-sust-q1 t-sust-q2 t-sust-q3 t-sust-q4 t-curv-q1 t-curv-q2 t-curv-q3 t-curv-q4 t-cure-q1 t-cure-q2 t-sust-q1 t-sust-q2 t-sust-q3 t-sust-q4 t-curv-q1 t-curv-q2 t-curv-q3 t-curv-q4 t-cure-q1 t-cure-q2
t-cure-q3 t-cure-q4 t-peng-q1 t-peng-q2 t-peng-q3 t-peng-q4 t-ceng-q1 t-ceng-q2 t-ceng-q3 t-ceng-q4 t-cure-q3 t-cure-q4 t-peng-q1 t-peng-q2 t-peng-q3 t-peng-q4 t-ceng-q1 t-ceng-q2 t-ceng-q3 t-ceng-q4
t-sach-q1 t-sach-q2 t-sach-q3 t-psol-q1 t-psol-q2 t-psol-q3 t-expa-q2 t-expa-q3 t-phya-q2 t-phya-q3] t-sach-q1 t-sach-q2 t-sach-q3 t-psol-q1 t-psol-q2 t-psol-q3 t-expa-q2 t-expa-q3 t-phya-q2 t-phya-q3]
survey_item_ids.map do |survey_item_id| survey_item_ids.map do |survey_item_id|
create(:survey_item, survey_item_id:) create(:survey_item, survey_item_id:)
@ -77,49 +77,49 @@ RSpec.describe SurveyItemValues, type: :model do
(survey_item_ids << common_headers).flatten (survey_item_ids << common_headers).flatten
end end
context ".recorded_date" do context '.recorded_date' do
it "returns the recorded date" do it 'returns the recorded date' do
row = {"RecordedDate" => "2017-01-01"} row = { 'RecordedDate' => '2017-01-01' }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.recorded_date).to eq Date.parse("2017-01-01") expect(values.recorded_date).to eq Date.parse('2017-01-01')
headers = ["Recorded Date"] headers = ['Recorded Date']
row = {"Recorded Date" => "2017-01-02"} row = { 'Recorded Date' => '2017-01-02' }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.recorded_date).to eq Date.parse("2017-01-02") expect(values.recorded_date).to eq Date.parse('2017-01-02')
end end
end end
context ".school" do context '.school' do
it "returns the school that maps to the dese id provided" do it 'returns the school that maps to the dese id provided' do
attleboro attleboro
row = {"Dese ID" => "1234"} row = { 'Dese ID' => '1234' }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.school).to eq attleboro expect(values.school).to eq attleboro
row = {"DeseID" => "1234"} row = { 'DeseID' => '1234' }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.school).to eq attleboro expect(values.school).to eq attleboro
end end
end end
context ".grade" do context '.grade' do
it "returns the grade that maps to the grade provided" do it 'returns the grade that maps to the grade provided' do
row = {"Grade" => "1"} row = { 'Grade' => '1' }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.grade).to eq 1 expect(values.grade).to eq 1
end end
end end
context ".gender" do context '.gender' do
it "returns the grade that maps to the grade provided" do it 'returns the grade that maps to the grade provided' do
row = {"Gender" => "1"} row = { 'Gender' => '1' }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.gender.qualtrics_code).to eq 1 expect(values.gender.qualtrics_code).to eq 1
end end
end end
context ".respondent_type" do context '.respondent_type' do
it "reads header to find the survey type" do it 'reads header to find the survey type' do
headers = %w[s-sbel-q5 s-phys-q2 RecordedDate] headers = %w[s-sbel-q5 s-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:)
expect(values.respondent_type).to eq :student expect(values.respondent_type).to eq :student
@ -130,32 +130,32 @@ RSpec.describe SurveyItemValues, type: :model do
end end
end end
context ".survey_type" do context '.survey_type' do
context "when survey type is standard form" do context 'when survey type is standard form' do
it "returns the survey type" do it 'returns the survey type' do
headers = standard_survey_items headers = standard_survey_items
values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:)
expect(values.survey_type).to eq :standard expect(values.survey_type).to eq :standard
end end
end end
context "when survey type is teacher form" do context 'when survey type is teacher form' do
it "returns the survey type" do it 'returns the survey type' do
headers = teacher_survey_items headers = teacher_survey_items
values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:)
expect(values.survey_type).to eq :teacher expect(values.survey_type).to eq :teacher
end end
end end
context "when survey type is short form" do context 'when survey type is short form' do
it "returns the survey type" do it 'returns the survey type' do
headers = short_form_survey_items headers = short_form_survey_items
values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:)
expect(values.survey_type).to eq :short_form expect(values.survey_type).to eq :short_form
end end
end end
context "when survey type is early education" do context 'when survey type is early education' do
it "returns the survey type" do it 'returns the survey type' do
headers = early_education_survey_items headers = early_education_survey_items
values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:) values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:)
expect(values.survey_type).to eq :early_education expect(values.survey_type).to eq :early_education
@ -163,236 +163,236 @@ RSpec.describe SurveyItemValues, type: :model do
end end
end end
context ".income" do context '.income' do
context "when no disaggregation data is provided" do context 'when no disaggregation data is provided' do
it "returns an empty string " do it 'returns an empty string ' do
disaggregation_data = {} disaggregation_data = {}
values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:, disaggregation_data:) values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:, disaggregation_data:)
expect(values.income).to eq "Unknown" expect(values.income).to eq 'Unknown'
end end
end end
context "when disaggregation data is provided" do context 'when disaggregation data is provided' do
before :each do before :each do
attleboro attleboro
ay_2022_23 ay_2022_23
end end
it "translates Free Lunch to Economically Disadvantaged - Y" do it 'translates Free Lunch to Economically Disadvantaged - Y' do
headers = ["District", "Academic Year", "LASID", "LowIncome"] headers = ['District', 'Academic Year', 'LASID', 'LowIncome']
row = {"District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Free Lunch"} row = { 'District' => 'Attleboro', 'AcademicYear' => '2022-23', 'LASID' => '1', 'LowIncome' => 'Free Lunch' }
disaggregation_data = {%w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:)} disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"] headers = ['LASID', 'Dese Id', 'RecordedDate']
row = {"LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1"} 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:,
disaggregation_data:) disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - Y" expect(values.income).to eq 'Economically Disadvantaged - Y'
end end
it "translates Reduced Lunch to Economically Disadvantaged - Y" do it 'translates Reduced Lunch to Economically Disadvantaged - Y' do
headers = ["District", "Academic Year", "LASID", "LowIncome"] headers = ['District', 'Academic Year', 'LASID', 'LowIncome']
row = {"District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Reduced Lunch"} row = { 'District' => 'Attleboro', 'AcademicYear' => '2022-23', 'LASID' => '1', 'LowIncome' => 'Reduced Lunch' }
disaggregation_data = {%w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:)} disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"] headers = ['LASID', 'Dese Id', 'RecordedDate']
row = {"LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1"} 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:,
disaggregation_data:) disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - Y" expect(values.income).to eq 'Economically Disadvantaged - Y'
end end
it "translates LowIncome to Economically Disadvantaged - Y" do it 'translates LowIncome to Economically Disadvantaged - Y' do
headers = ["District", "Academic Year", "LASID", "LowIncome"] headers = ['District', 'Academic Year', 'LASID', 'LowIncome']
row = {"District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "LowIncome"} row = { 'District' => 'Attleboro', 'AcademicYear' => '2022-23', 'LASID' => '1', 'LowIncome' => 'LowIncome' }
disaggregation_data = {%w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:)} disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"] headers = ['LASID', 'Dese Id', 'RecordedDate']
row = {"LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1"} 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:,
disaggregation_data:) disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - Y" expect(values.income).to eq 'Economically Disadvantaged - Y'
end end
it "translates Not Eligible to Economically Disadvantaged - N" do it 'translates Not Eligible to Economically Disadvantaged - N' do
headers = ["District", "Academic Year", "LASID", "LowIncome"] headers = ['District', 'Academic Year', 'LASID', 'LowIncome']
row = {"District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Not Eligible"} row = { 'District' => 'Attleboro', 'AcademicYear' => '2022-23', 'LASID' => '1', 'LowIncome' => 'Not Eligible' }
disaggregation_data = {%w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:)} disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"] headers = ['LASID', 'Dese Id', 'RecordedDate']
row = {"LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1"} 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:,
disaggregation_data:) disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - N" expect(values.income).to eq 'Economically Disadvantaged - N'
end end
it "translates blanks to Unknown" do it 'translates blanks to Unknown' do
headers = ["District", "Academic Year", "LASID", "LowIncome"] headers = ['District', 'Academic Year', 'LASID', 'LowIncome']
row = {"District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => ""} row = { 'District' => 'Attleboro', 'AcademicYear' => '2022-23', 'LASID' => '1', 'LowIncome' => '' }
disaggregation_data = {%w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:)} disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"] headers = ['LASID', 'Dese Id', 'RecordedDate']
row = {"LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1"} 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:,
disaggregation_data:) disaggregation_data:)
expect(values.income).to eq "Unknown" expect(values.income).to eq 'Unknown'
end end
end end
end end
context ".valid_duration" do context '.valid_duration' do
context "when duration is valid" do context 'when duration is valid' do
it "returns true" do it 'returns true' do
headers = standard_survey_items headers = standard_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => "240", "Gender" => "Male"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => '240', 'Gender' => 'Male' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq true expect(values.valid_duration?).to eq true
headers = teacher_survey_items headers = teacher_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => "300"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => '300' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq true expect(values.valid_duration?).to eq true
headers = short_form_survey_items headers = short_form_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => "100"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => '100' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq true expect(values.valid_duration?).to eq true
# When duration is blank or N/A or NA, we don't have enough information to kick out the row as invalid so we keep it in # When duration is blank or N/A or NA, we don't have enough information to kick out the row as invalid so we keep it in
headers = short_form_survey_items headers = short_form_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => ""}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => '' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq true expect(values.valid_duration?).to eq true
headers = short_form_survey_items headers = short_form_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => "N/A"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => 'N/A' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq true expect(values.valid_duration?).to eq true
headers = short_form_survey_items headers = short_form_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => "NA"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => 'NA' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq true expect(values.valid_duration?).to eq true
end end
end end
context "when duration is invalid" do context 'when duration is invalid' do
it "returns false" do it 'returns false' do
headers = standard_survey_items headers = standard_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => "239"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => '239' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq false expect(values.valid_duration?).to eq false
headers = teacher_survey_items headers = teacher_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => "299"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => '299' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq false expect(values.valid_duration?).to eq false
headers = short_form_survey_items headers = short_form_survey_items
values = SurveyItemValues.new(row: {"Duration (in seconds)" => "99"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Duration (in seconds)' => '99' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_duration?).to eq false expect(values.valid_duration?).to eq false
end end
end end
end end
context ".valid_progress" do context '.valid_progress' do
context "when progress is valid" do context 'when progress is valid' do
it "returns true" do it 'returns true' do
headers = %w[s-sbel-q5 s-phys-q2 RecordedDate] headers = %w[s-sbel-q5 s-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {"Progress" => "25"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Progress' => '25' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_progress?).to eq true expect(values.valid_progress?).to eq true
# When progress is blank or N/A or NA, we don't have enough information to kick out the row as invalid so we keep it in # When progress is blank or N/A or NA, we don't have enough information to kick out the row as invalid so we keep it in
headers = %w[s-sbel-q5 s-phys-q2 RecordedDate] headers = %w[s-sbel-q5 s-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {"Progress" => ""}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Progress' => '' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_progress?).to eq true expect(values.valid_progress?).to eq true
headers = %w[s-sbel-q5 s-phys-q2 RecordedDate] headers = %w[s-sbel-q5 s-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {"Progress" => "N/A"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Progress' => 'N/A' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_progress?).to eq true expect(values.valid_progress?).to eq true
headers = %w[s-sbel-q5 s-phys-q2 RecordedDate] headers = %w[s-sbel-q5 s-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {"Progress" => "NA"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Progress' => 'NA' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_progress?).to eq true expect(values.valid_progress?).to eq true
end end
end end
context "when progress is invalid" do context 'when progress is invalid' do
it "returns false" do it 'returns false' do
headers = %w[s-sbel-q5 s-phys-q2 RecordedDate] headers = %w[s-sbel-q5 s-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {"Progress" => "24"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'Progress' => '24' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_progress?).to eq false expect(values.valid_progress?).to eq false
end end
end end
end end
context ".valid_grade?" do context '.valid_grade?' do
context "when grade is valid" do context 'when grade is valid' do
before :each do before :each do
attleboro attleboro
attleboro_respondents attleboro_respondents
end end
it "returns true for students" do it 'returns true for students' do
headers = %w[s-sbel-q5 s-phys-q2 grade RecordedDate] headers = %w[s-sbel-q5 s-phys-q2 grade RecordedDate]
values = SurveyItemValues.new(row: {"grade" => "9", "RecordedDate" => recorded_date, "Dese ID" => "1234"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'grade' => '9', 'RecordedDate' => recorded_date, 'Dese ID' => '1234' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_grade?).to eq true expect(values.valid_grade?).to eq true
end end
it "returns true for teachers" do it 'returns true for teachers' do
headers = %w[t-sbel-q5 t-phys-q2 grade RecordedDate] headers = %w[t-sbel-q5 t-phys-q2 grade RecordedDate]
values = SurveyItemValues.new(row: {"RecordedDate" => recorded_date, "Dese ID" => "1234"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'RecordedDate' => recorded_date, 'Dese ID' => '1234' }, headers:, genders:, survey_items:,
schools:) schools:)
expect(values.valid_grade?).to eq true expect(values.valid_grade?).to eq true
end end
end end
context "when grade is invalid" do context 'when grade is invalid' do
before :each do before :each do
attleboro attleboro
attleboro_respondents attleboro_respondents
end end
it "returns false" do it 'returns false' do
headers = %w[s-sbel-q5 s-phys-q2 grade RecordedDate] headers = %w[s-sbel-q5 s-phys-q2 grade RecordedDate]
values = SurveyItemValues.new(row: {"grade" => "2", "RecordedDate" => recorded_date, "Dese ID" => "1234"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'grade' => '2', 'RecordedDate' => recorded_date, 'Dese ID' => '1234' }, headers:, genders:, survey_items:,
schools: School.school_hash) schools: School.school_hash)
expect(values.valid_grade?).to eq false expect(values.valid_grade?).to eq false
end end
end end
end end
context ".valid_sd?" do context '.valid_sd?' do
context "when the standard deviation is valid" do context 'when the standard deviation is valid' do
it "returns true for student questions" do it 'returns true for student questions' do
headers = %w[s-sbel-q5 s-phys-q1 s-phys-q2 RecordedDate] headers = %w[s-sbel-q5 s-phys-q1 s-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {"RecordedDate" => recorded_date, "Dese ID" => "1234", "s-sbel-q5" => "1", "s-phys-q1" => "", "s-phys-q2" => "5"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'RecordedDate' => recorded_date, 'Dese ID' => '1234', 's-sbel-q5' => '1', 's-phys-q1' => '', 's-phys-q2' => '5' }, headers:, genders:, survey_items:,
schools: School.school_hash) schools: School.school_hash)
expect(values.valid_sd?).to eq true expect(values.valid_sd?).to eq true
end end
it "returns true for teacher questions" do it 'returns true for teacher questions' do
headers = %w[t-sbel-q5 t-phys-q2] headers = %w[t-sbel-q5 t-phys-q2]
values = SurveyItemValues.new(row: {"RecordedDate" => recorded_date, "Dese ID" => "1234", "t-sbel-q5" => "1", "t-phys-q2" => "5"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'RecordedDate' => recorded_date, 'Dese ID' => '1234', 't-sbel-q5' => '1', 't-phys-q2' => '5' }, headers:, genders:, survey_items:,
schools: School.school_hash) schools: School.school_hash)
expect(values.valid_sd?).to eq true expect(values.valid_sd?).to eq true
end end
end end
context "when the standard deviation is invalid" do context 'when the standard deviation is invalid' do
it "returns false for student questions" do it 'returns false for student questions' do
headers = %w[s-sbel-q5 s-phys-q1 s-phys-q2 RecordedDate] headers = %w[s-sbel-q5 s-phys-q1 s-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {"RecordedDate" => recorded_date, "Dese ID" => "1234", "s-sbel-q5" => "1", "s-phys-q2" => "", "s-phys-q2" => "1"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'RecordedDate' => recorded_date, 'Dese ID' => '1234', 's-sbel-q5' => '1', 's-phys-q2' => '1' }, headers:, genders:, survey_items:,
schools: School.school_hash) schools: School.school_hash)
expect(values.valid_sd?).to eq false expect(values.valid_sd?).to eq false
end end
it "returns false for teacher questions" do it 'returns false for teacher questions' do
headers = %w[t-sbel-q5 t-phys-q1 t-phys-q2 RecordedDate] headers = %w[t-sbel-q5 t-phys-q1 t-phys-q2 RecordedDate]
values = SurveyItemValues.new(row: {"RecordedDate" => recorded_date, "Dese ID" => "1234", "t-sbel-q5" => "1", "t-phys-q2" => "", "t-phys-q2" => "1"}, headers:, genders:, survey_items:, values = SurveyItemValues.new(row: { 'RecordedDate' => recorded_date, 'Dese ID' => '1234', 't-sbel-q5' => '1', 't-phys-q2' => '1' }, headers:, genders:, survey_items:,
schools: School.school_hash) schools: School.school_hash)
expect(values.valid_sd?).to eq false expect(values.valid_sd?).to eq false
end end
end end

@ -1,53 +1,59 @@
require "rails_helper" require 'rails_helper'
describe SurveyResponsesDataLoader do describe SurveyResponsesDataLoader do
let(:path_to_teacher_responses) { Rails.root.join("spec", "fixtures", "test_2020-21_teacher_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_student_responses) { Rails.root.join('spec', 'fixtures', 'test_2020-21_student_survey_responses.csv') }
let(:path_to_butler_student_responses) do 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 end
let(:ay_2020_21) { create(:academic_year, range: "2020-21") } let(:ay_2020_21) { create(:academic_year, range: '2020-21') }
let(:ay_2022_23) { create(:academic_year, range: "2022-23") } 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(: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(:lowell) { create(:district, name: 'Lowell', slug: 'lowell') }
let(:second_school) { create(:school, name: "Lee Middle High School", slug: "lee-middle-high-school", dese_id: 1_500_505, district: lowell) } let(:second_school) do
create(:school, name: 'Lee Middle High School', slug: 'lee-middle-high-school', dese_id: 1_500_505,
district: lowell)
end
let(:butler_school) do 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) district: lowell)
end end
let(:t_pcom_q3) { create(:survey_item, survey_item_id: "t-pcom-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_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_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_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_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_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_q2) { create(:survey_item, survey_item_id: 't-sach-q2') }
let(:t_sach_q3) { create(:survey_item, survey_item_id: "t-sach-q3") } 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_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_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_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_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_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_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_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_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_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_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_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_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_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_q2) { create(:survey_item, survey_item_id: 's-emsa-q2') }
let(:s_emsa_q3) { create(:survey_item, survey_item_id: "s-emsa-q3") } let(:s_emsa_q3) { create(:survey_item, survey_item_id: 's-emsa-q3') }
let(:female) { create(:gender, qualtrics_code: 1) } let(:female) { create(:gender, qualtrics_code: 1) }
let(:male) { create(:gender, qualtrics_code: 2) } let(:male) { create(:gender, qualtrics_code: 2) }
let(:another_gender) { create(:gender, qualtrics_code: 3) } let(:another_gender) { create(:gender, qualtrics_code: 3) }
let(:non_binary) { create(:gender, qualtrics_code: 4) } let(:non_binary) { create(:gender, qualtrics_code: 4) }
let(:unknown_gender) { create(:gender, qualtrics_code: 99) } 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(:setup) do let(:setup) do
ay_2020_21 ay_2020_21
@ -83,18 +89,21 @@ describe SurveyResponsesDataLoader do
another_gender another_gender
non_binary non_binary
unknown_gender unknown_gender
low_income
high_income
unknown_income
end end
before :each do before :each do
setup setup
end end
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.load_data filepath: path_to_teacher_responses
end end
it "ensures teacher responses load correctly" do it 'ensures teacher responses load correctly' do
assigns_academic_year_to_survey_item_responses assigns_academic_year_to_survey_item_responses
assigns_school_to_the_survey_item_responses assigns_school_to_the_survey_item_responses
assigns_recorded_date_to_teacher_responses assigns_recorded_date_to_teacher_responses
@ -105,12 +114,12 @@ describe SurveyResponsesDataLoader do
end end
end end
describe "student survey responses" do describe 'student survey responses' do
before do before do
SurveyResponsesDataLoader.load_data filepath: path_to_student_responses SurveyResponsesDataLoader.load_data filepath: path_to_student_responses
end end
it "ensures student responses load correctly" do it 'ensures student responses load correctly' do
assigns_academic_year_to_student_survey_item_responses assigns_academic_year_to_student_survey_item_responses
assigns_school_to_student_survey_item_responses assigns_school_to_student_survey_item_responses
assigns_recorded_date_to_student_responses assigns_recorded_date_to_student_responses
@ -119,85 +128,86 @@ describe SurveyResponsesDataLoader do
captures_likert_scores_for_student_survey_item_responses captures_likert_scores_for_student_survey_item_responses
assigns_grade_level_to_responses assigns_grade_level_to_responses
assigns_gender_to_responses assigns_gender_to_responses
assigns_income_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.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'
expect(SurveyItemResponse.where(response_id: "student_survey_response_3", expect(SurveyItemResponse.where(response_id: 'student_survey_response_3',
survey_item: s_emsa_q1).first.likert_score).to eq 1 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 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 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 survey_item: s_acst_q3).first.likert_score).to eq 4
end end
end end
end end
# 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.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
expect(SurveyItemResponse.where(response_id: "student_survey_response_1").count).to eq 0 expect(SurveyItemResponse.where(response_id: 'student_survey_response_1').count).to eq 0
expect(SurveyItemResponse.count).to eq 69 expect(SurveyItemResponse.count).to eq 69
end end
it "loads the correct number of responses for lowell schools" do 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_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_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_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_5').count).to eq 14
end end
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.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
expect(SurveyItemResponse.where(school: butler_school).count).to eq 56 expect(SurveyItemResponse.where(school: butler_school).count).to eq 56
end end
it "blank entries for grade get loaded as nils, not zero values" do 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_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_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_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_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_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_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_7').first.grade).to eq nil
expect(SurveyItemResponse.where(response_id: "butler_student_survey_response_8").first.grade).to eq 0 expect(SurveyItemResponse.where(response_id: 'butler_student_survey_response_8').first.grade).to eq 0
end end
end end
end end
end end
def assigns_academic_year_to_survey_item_responses 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 end
def assigns_school_to_the_survey_item_responses 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 end
def loads_survey_item_responses_for_a_given_survey_response 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_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_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_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_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_5').count).to eq 8
end end
def loads_all_survey_item_responses_for_a_given_survey_item def loads_all_survey_item_responses_for_a_given_survey_item
@ -206,20 +216,20 @@ def loads_all_survey_item_responses_for_a_given_survey_item
end end
def captures_likert_scores_for_survey_item_responses 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_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_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_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_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_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_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_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_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_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_q3).first.likert_score).to eq 4
end end
def is_idempotent def is_idempotent
@ -231,19 +241,19 @@ def is_idempotent
end end
def assigns_academic_year_to_student_survey_item_responses 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 end
def assigns_school_to_student_survey_item_responses 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 end
def loads_student_survey_item_response_values 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_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_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_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_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_5').count).to eq 14
end end
def student_survey_item_response_count_matches_expected def student_survey_item_response_count_matches_expected
@ -252,20 +262,20 @@ def student_survey_item_response_count_matches_expected
end end
def captures_likert_scores_for_student_survey_item_responses 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_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_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_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_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_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_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_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_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_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_q2).first.likert_score).to eq 2
end end
def is_idempotent_for_students def is_idempotent_for_students
@ -277,26 +287,26 @@ def is_idempotent_for_students
end end
def assigns_grade_level_to_responses def assigns_grade_level_to_responses
results = {"student_survey_response_1" => 11, results = { 'student_survey_response_1' => 11,
"student_survey_response_3" => 8, 'student_survey_response_3' => 8,
"student_survey_response_4" => 8, 'student_survey_response_4' => 8,
"student_survey_response_5" => 7, 'student_survey_response_5' => 7,
"student_survey_response_6" => 3, 'student_survey_response_6' => 3,
"student_survey_response_7" => 4} 'student_survey_response_7' => 4 }
results.each do |key, value| results.each do |key, value|
expect(SurveyItemResponse.where(response_id: key).all? do |response| expect(SurveyItemResponse.where(response_id: key).all? do |response|
response.grade == value response.grade == value
end).to eq true end).to eq true
end end
end end
def assigns_gender_to_responses def assigns_gender_to_responses
results = {"student_survey_response_1" => female, results = { 'student_survey_response_1' => female,
"student_survey_response_3" => male, 'student_survey_response_3' => male,
"student_survey_response_4" => non_binary, 'student_survey_response_4' => non_binary,
"student_survey_response_5" => non_binary, 'student_survey_response_5' => non_binary,
"student_survey_response_6" => unknown_gender, 'student_survey_response_6' => unknown_gender,
"student_survey_response_7" => unknown_gender} 'student_survey_response_7' => unknown_gender }
results.each do |key, value| results.each do |key, value|
expect(SurveyItemResponse.where(response_id: key).first.gender).to eq value expect(SurveyItemResponse.where(response_id: key).first.gender).to eq value
@ -304,23 +314,36 @@ def assigns_gender_to_responses
end end
def assigns_recorded_date_to_student_responses def assigns_recorded_date_to_student_responses
results = {"student_survey_response_1" => "2020-09-30T18:48:50", results = { 'student_survey_response_1' => '2020-09-30T18:48:50',
"student_survey_response_3" => "2021-03-31T09:59:02", 'student_survey_response_3' => '2021-03-31T09:59:02',
"student_survey_response_4" => "2021-03-31T10:00:17", 'student_survey_response_4' => '2021-03-31T10:00:17',
"student_survey_response_5" => "2021-03-31T10:01:36", 'student_survey_response_5' => '2021-03-31T10:01:36',
"student_survey_response_6" => "2021-03-31T10:01:37", 'student_survey_response_6' => '2021-03-31T10:01:37',
"student_survey_response_7" => "2021-03-31T10:01:38"} 'student_survey_response_7' => '2021-03-31T10:01:38' }
results.each do |key, value| results.each do |key, value|
expect(SurveyItemResponse.find_by_response_id(key).recorded_date).to eq Date.parse(value) expect(SurveyItemResponse.find_by_response_id(key).recorded_date).to eq Date.parse(value)
end end
end end
def assigns_recorded_date_to_teacher_responses def assigns_recorded_date_to_teacher_responses
results = {"teacher_survey_response_1" => "2020-10-16 11:09:03", results = { 'teacher_survey_response_1' => '2020-10-16 11:09:03',
"teacher_survey_response_3" => "2020-12-06 8:36:52", 'teacher_survey_response_3' => '2020-12-06 8:36:52',
"teacher_survey_response_4" => "2020-12-06 8:51:25", 'teacher_survey_response_4' => '2020-12-06 8:51:25',
"teacher_survey_response_5" => "2020-12-06 8:55:58"} 'teacher_survey_response_5' => '2020-12-06 8:55:58' }
results.each do |key, value| results.each do |key, value|
expect(SurveyItemResponse.find_by_response_id(key).recorded_date).to eq Date.parse(value) expect(SurveyItemResponse.find_by_response_id(key).recorded_date).to eq Date.parse(value)
end end
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.each do |key, value|
expect(SurveyItemResponse.where(response_id: key).first.income).to eq value
end
end

@ -1,25 +1,20 @@
require 'rails_helper' require "rails_helper"
include AnalyzeHelper include AnalyzeHelper
include Analyze::Graph include Analyze::Graph
describe 'analyze/index' do describe "analyze/index" do
subject { Nokogiri::HTML(rendered) } subject { Nokogiri::HTML(rendered) }
let(:category) { create(:category) } let(:category) { create(:category) }
let(:subcategory) { create(:subcategory, category:) } let(:subcategory) { create(:subcategory, category:) }
let(:school) { create(:school) } let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) } let(:academic_year) { create(:academic_year) }
let(:races) do let(:races) do
DemographicLoader.load_data(filepath: 'spec/fixtures/sample_demographics.csv') DemographicLoader.load_data(filepath: "spec/fixtures/sample_demographics.csv")
Race.all Race.all
end end
let(:graph) { StudentsAndTeachers.new }
let(:graphs) do
[StudentsAndTeachers.new, StudentsByRace.new(races:)]
end
let(:background) { BackgroundPresenter.new(num_of_columns: graph.columns.count) } let(:background) { BackgroundPresenter.new(num_of_columns: graph.columns.count) }
let(:selected_races) { races }
let(:support_for_teaching) do let(:support_for_teaching) do
measure = create(:measure, name: 'Support For Teaching Development & Growth', measure_id: '1A-I', subcategory:) measure = create(:measure, name: "Support For Teaching Development & Growth", measure_id: "1A-I", subcategory:)
scale = create(:scale, measure:) scale = create(:scale, measure:)
create(:student_survey_item, create(:student_survey_item,
scale:, scale:,
@ -31,7 +26,7 @@ describe 'analyze/index' do
end end
let(:effective_leadership) do let(:effective_leadership) do
measure = create(:measure, name: 'Effective Leadership', measure_id: '1A-II', subcategory:) measure = create(:measure, name: "Effective Leadership", measure_id: "1A-II", subcategory:)
scale = create(:scale, measure:) scale = create(:scale, measure:)
create(:teacher_survey_item, create(:teacher_survey_item,
scale:, scale:,
@ -43,7 +38,7 @@ describe 'analyze/index' do
end end
let(:professional_qualifications) do let(:professional_qualifications) do
measure = create(:measure, name: 'Professional Qualifications', measure_id: '1A-III', subcategory:) measure = create(:measure, name: "Professional Qualifications", measure_id: "1A-III", subcategory:)
scale = create(:scale, measure:) scale = create(:scale, measure:)
create(:admin_data_item, create(:admin_data_item,
scale:, scale:,
@ -54,73 +49,30 @@ describe 'analyze/index' do
measure measure
end end
let(:sources) do
[Analyze::Source::SurveyData.new(slices:)]
end
let(:slices) do
students_and_teachers = Analyze::Slice::StudentsAndTeachers.new
students_by_group = Analyze::Slice::StudentsByGroup.new(races:, grades:)
[students_and_teachers, students_by_group]
end
let(:slice) do
slices.first
end
let(:groups) do
[Analyze::Group::Race.new, Analyze::Group::Grade.new]
end
let(:group) do
groups.first
end
let(:grades) do
(1..12).to_a
end
let(:genders) do let(:genders) do
DemographicLoader.load_data(filepath: 'spec/fixtures/sample_demographics.csv') DemographicLoader.load_data(filepath: "spec/fixtures/sample_demographics.csv")
Gender.all Gender.all
end end
let(:selected_genders) do let(:respondent) { create(:respondent, school:, academic_year:) }
genders
end
let(:selected_grades) do
grades
end
before :each do before :each do
assign :races, races races
assign :selected_races, selected_races category
assign :graph, graph subcategory
assign :graphs, graphs support_for_teaching
assign :background, background effective_leadership
professional_qualifications
respondent
assign :academic_year, academic_year assign :academic_year, academic_year
assign :available_academic_years, [academic_year]
assign :selected_academic_years, [academic_year]
assign :district, create(:district) assign :district, create(:district)
assign :school, school assign :school, school
assign :category, category assign :presenter,
assign :categories, [category] Analyze::Presenter.new(school:, academic_year:,
assign :subcategory, subcategory params: { category: category.category_id, subcategory: subcategory.subcategory_id, races: "american-indian-or-alaskan-native,asian-or-pacific-islander,black-or-african-american,hispanic-or-latinx,middle-eastern,multiracial,race-ethnicity-not-listed,white-or-caucasian", source: "survey-data-only", slice: "students-and-teachers", group: "race", graph: "students-by-race" })
assign :subcategories, category.subcategories assign :background, BackgroundPresenter.new(num_of_columns: 4)
assign :measures, [support_for_teaching, effective_leadership, professional_qualifications]
assign :sources, sources
assign :source, sources.first
assign :groups, groups
assign :group, group
assign :slice, slice
assign :grades, grades
assign :selected_grades, selected_grades
assign :genders, genders
assign :selected_genders, selected_genders
create(:respondent, school:, academic_year:)
end end
context 'when all the presenters have a nil score' do context "when all the presenters have a nil score" do
before do before do
render render
end end
@ -139,63 +91,72 @@ describe 'analyze/index' do
# ] # ]
# end # end
it 'displays a set of grouped bars for each presenter' do it "displays a set of grouped bars for each presenter" do
displayed_variance_columns = subject.css('.grouped-bar-column') displayed_variance_columns = subject.css(".grouped-bar-column")
expect(displayed_variance_columns.count).to eq 9 expect(displayed_variance_columns.count).to eq 27
displayed_variance_rows = subject.css('[data-for-measure-id]') displayed_variance_rows = subject.css("[data-for-measure-id]")
expect(displayed_variance_rows.first.attribute('data-for-measure-id').value).to eq '1A-I' expect(displayed_variance_rows.first.attribute("data-for-measure-id").value).to eq "1A-I"
displayed_academic_years = subject.css('[data-for-academic-year]') displayed_academic_years = subject.css("[data-for-academic-year]")
expect(displayed_academic_years.count).to eq 0 expect(displayed_academic_years.count).to eq 0
displayed_variance_labels = subject.css('[data-grouped-bar-label]') displayed_variance_labels = subject.css("[data-grouped-bar-label]")
expect(displayed_variance_labels.count).to eq 18
expect(displayed_variance_labels.first.inner_text).to include 'All' expect(displayed_variance_labels.count).to eq 39
expect(displayed_variance_labels[1].inner_text).to include 'Students' expect(displayed_variance_labels.first.inner_text).to include "American"
expect(displayed_variance_labels.last.inner_text).to include 'Data' expect(displayed_variance_labels.text).to include "Indian"
expect(displayed_variance_labels.text).to include "Asian"
expect(displayed_variance_labels.text).to include "Black"
expect(displayed_variance_labels.text).to include "White"
expect(displayed_variance_labels.text).to include "Hispanic"
expect(displayed_variance_labels.text).to include "Middle"
expect(displayed_variance_labels.text).to include "Eastern"
expect(displayed_variance_labels.text).to include "Multiracial"
expect(displayed_variance_labels.text).to include "Not"
expect(displayed_variance_labels.text).to include "Listed"
end end
it 'displays all measures for the first subcategory' do it "displays all measures for the first subcategory" do
expect(rendered).to have_text '1A-I' expect(rendered).to have_text "1A-I"
expect(rendered).to have_text '1A-II' expect(rendered).to have_text "1A-II"
expect(rendered).to have_text '1A-III' expect(rendered).to have_text "1A-III"
end end
it 'displays user interface controls' do it "displays user interface controls" do
expect(subject).to have_text 'Focus Area' expect(subject).to have_text "Focus Area"
expect(subject).to have_css '#select-category' expect(subject).to have_css "#select-category"
expect(subject).to have_css '#select-subcategory' expect(subject).to have_css "#select-subcategory"
expect(subject).to have_css "##{academic_year.range}" expect(subject).to have_css "##{academic_year.range}"
end end
it 'displays disabled checkboxes for years that dont have data' do it "displays disabled checkboxes for years that dont have data" do
year_checkbox = subject.css("##{academic_year.range}").first year_checkbox = subject.css("##{academic_year.range}").first
expect(year_checkbox.name).to eq 'input' expect(year_checkbox.name).to eq "input"
expect(academic_year.range).to eq '2050-51' expect(academic_year.range).to eq "2050-51"
expect(year_checkbox).to have_attribute 'disabled' expect(year_checkbox).to have_attribute "disabled"
end end
it 'displays a radio box selector for each type of data filter' do it "displays a radio box selector for each type of data filter" do
expect(subject).to have_css '#students-and-teachers' expect(subject).to have_css "#students-and-teachers"
expect(subject).to have_css '#students-by-group' expect(subject).to have_css "#students-by-group"
end end
it 'displays a checkbox for each race designation' do it "displays a checkbox for each race designation" do
race_slugs = %w[american-indian-or-alaskan-native asian-or-pacific-islander black-or-african-american race_slugs = %w[race-american-indian-or-alaskan-native race-asian-or-pacific-islander race-black-or-african-american
hispanic-or-latinx middle-eastern multiracial race-ethnicity-not-listed white-or-caucasian] race-hispanic-or-latinx race-middle-eastern race-multiracial race-race-ethnicity-not-listed race-white-or-caucasian]
race_slugs.each do |slug| race_slugs.each do |slug|
expect(subject).to have_css("//input[@type='checkbox'][@id='#{slug}']") expect(subject).to have_css("//input[@type='checkbox'][@id='#{slug}']")
end end
end end
end end
context 'when presenters have a displayable score' do context "when presenters have a displayable score" do
before do before do
render render
end end
context 'when displaying a student and teacher graph' do context "when displaying a student and teacher graph" do
end end
end end
end end

@ -3254,7 +3254,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.2: semver@^7.3.2, semver@^7.5.2:
version "7.5.3" version "7.5.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e"
integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==

Loading…
Cancel
Save