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

mciea-main
rebuilt 3 years ago
parent 7373e2e52f
commit 76ebcc6ef3

@ -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
@ -31,4 +32,9 @@ class SurveyItemResponse < ActiveRecord::Base
SurveyItemResponse.where(survey_item: survey_items, school:, SurveyItemResponse.where(survey_item: survey_items, school:,
academic_year:, income:, grade: school.grades(academic_year:)).group(:survey_item).average(:likert_score) academic_year:, income:, grade: school.grades(academic_year:)).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

@ -27,3 +27,4 @@ module Analyze
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

@ -27,3 +27,4 @@ module Analyze
end end
end end
end end

@ -3,16 +3,64 @@ module Analyze
module Column module Column
module ScoreForRace module ScoreForRace
def score(year_index) def score(year_index)
s = ::RaceScore.find_by(measure:, school:, academic_year: academic_years[year_index], race:) academic_year = academic_year_for_year_index(year_index)
average = s.average.round(2) unless s.nil? rate = response_rate(school:, academic_year:, measure:)
average ||= 0 return Score::NIL_SCORE unless rate.meets_student_threshold
meets_student_threshold = s.meets_student_threshold? unless s.nil?
meets_student_threshold ||= false survey_items = measure.student_survey_items
Score.new(average:,
meets_teacher_threshold: false, averages = grouped_responses(school:, academic_year:, survey_items:, race:)
meets_student_threshold:, meets_student_threshold = sufficient_responses(school:, academic_year:, race:)
scorify(responses: averages, meets_student_threshold:, measure:)
end
def grouped_responses(school:, academic_year:, survey_items:, race:)
@grouped_responses ||= Hash.new do |memo, (school, academic_year, survey_items, race)|
memo[[school, academic_year, survey_items, race]] =
SurveyItemResponse.joins("JOIN student_races on survey_item_responses.student_id = student_races.student_id JOIN students on students.id = student_races.student_id").where(
school:, academic_year:, grade: school.grades(academic_year:)
).where("student_races.race_id": race.id).group(:survey_item_id).having("count(*) >= 10").average(:likert_score)
end
@grouped_responses[[school, academic_year, survey_items, race]]
end
def response_rate(school:, academic_year:, measure:)
subcategory = measure.subcategory
@response_rate ||= Hash.new do |memo, (school, academic_year, subcategory)|
memo[[school, academic_year, subcategory]] = subcategory.response_rate(school:, academic_year:)
end
@response_rate[[school, academic_year, subcategory]]
end
def scorify(responses:, meets_student_threshold:, measure:)
averages = bubble_up_averages(responses:, measure:)
average = averages.average.round(2)
average = 0 unless meets_student_threshold
Score.new(average:, meets_teacher_threshold: false, meets_student_threshold:,
meets_admin_data_threshold: false) meets_admin_data_threshold: false)
end end
def sufficient_responses(school:, academic_year:, race:)
@sufficient_responses ||= Hash.new do |memo, (school, academic_year, race)|
number_of_students_for_a_racial_group = SurveyItemResponse.joins("JOIN student_races on survey_item_responses.student_id = student_races.student_id JOIN students on students.id = student_races.student_id").where(
school:, academic_year:
).where("student_races.race_id": race.id).distinct.pluck(:student_id).count
memo[[school, academic_year, race]] = number_of_students_for_a_racial_group >= 10
end
@sufficient_responses[[school, academic_year, race]]
end
def bubble_up_averages(responses:, measure:)
measure.student_scales.map do |scale|
scale.survey_items.map do |survey_item|
responses[survey_item.id]
end.remove_blanks.average
end.remove_blanks
end
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,153 @@
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 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

@ -1,57 +1,34 @@
require "fileutils" require "fileutils"
class Cleaner class Cleaner
attr_reader :input_filepath, :output_filepath, :log_filepath, :clean_csv, :log_csv attr_reader :input_filepath, :output_filepath, :log_filepath, :disaggregation_filepath
def initialize(input_filepath:, output_filepath:, log_filepath:) def initialize(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:)
@input_filepath = input_filepath @input_filepath = input_filepath
@output_filepath = output_filepath @output_filepath = output_filepath
@log_filepath = log_filepath @log_filepath = log_filepath
@disaggregation_filepath = disaggregation_filepath
initialize_directories initialize_directories
end end
def initialize_directories
create_ouput_directory
create_log_directory
end
def clean def clean
Dir.glob(Rails.root.join(input_filepath, "*.csv")).each do |filepath| Dir.glob(Rails.root.join(input_filepath, "*.csv")).each do |filepath|
puts filepath puts filepath
File.open(filepath) do |file| File.open(filepath) do |file|
clean_csv = [] processed_data = process_raw_file(file:, disaggregation_data:)
log_csv = [] processed_data in [headers, clean_csv, log_csv, data]
data = [] return if data.empty?
headers = CSV.parse(file.first).first filename = filename(headers:, data:)
filtered_headers = remove_unwanted_headers(headers:) write_csv(data: clean_csv, output_filepath:, filename:)
log_headers = (filtered_headers + ["Valid Duration?", "Valid Progress?", "Valid Grade?", write_csv(data: log_csv, output_filepath: log_filepath, prefix: "removed.", filename:)
"Valid Standard Deviation?"]).flatten
clean_csv << filtered_headers
log_csv << log_headers
all_survey_items = survey_items(headers:)
file.lazy.each_slice(1000) do |lines|
CSV.parse(lines.join, headers:).map do |row|
values = SurveyItemValues.new(row:, headers:, genders:,
survey_items: all_survey_items, schools:)
next unless values.valid_school?
data << values
values.valid? ? clean_csv << values.to_a : log_csv << (values.to_a << values.valid_duration?.to_s << values.valid_progress?.to_s << values.valid_grade?.to_s << values.valid_sd?.to_s)
end
end
unless data.empty?
filename = filename(headers:, data:)
write_csv(data: clean_csv, output_filepath:, filename:)
write_csv(data: log_csv, output_filepath: log_filepath, prefix: "removed.", filename:)
end
end end
end end
end end
def disaggregation_data
@disaggregation_data ||= DisaggregationLoader.new(path: disaggregation_filepath).load
end
def filename(headers:, data:) def filename(headers:, data:)
survey_item_ids = headers.filter(&:present?).filter do |header| survey_item_ids = headers.filter(&:present?).filter do |header|
header.start_with?("s-", "t-") header.start_with?("s-", "t-")
@ -66,6 +43,41 @@ class Cleaner
districts.join(".").to_s + "." + survey_type.to_s + "." + range + ".csv" districts.join(".").to_s + "." + survey_type.to_s + "." + range + ".csv"
end end
def process_raw_file(file:, disaggregation_data:)
clean_csv = []
log_csv = []
data = []
headers = (CSV.parse(file.first).first << "Raw Income") << "Income"
filtered_headers = remove_unwanted_headers(headers:)
log_headers = (filtered_headers + ["Valid Duration?", "Valid Progress?", "Valid Grade?",
"Valid Standard Deviation?"]).flatten
clean_csv << filtered_headers
log_csv << log_headers
all_survey_items = survey_items(headers:)
file.lazy.each_slice(1000) do |lines|
CSV.parse(lines.join, headers:).map do |row|
values = SurveyItemValues.new(row:, headers:, genders:,
survey_items: all_survey_items, schools:, disaggregation_data:)
next unless values.valid_school?
data << values
values.valid? ? clean_csv << values.to_a : log_csv << (values.to_a << values.valid_duration?.to_s << values.valid_progress?.to_s << values.valid_grade?.to_s << values.valid_sd?.to_s)
end
end
[headers, clean_csv, log_csv, data]
end
private
def initialize_directories
create_ouput_directory
create_log_directory
end
def remove_unwanted_headers(headers:) def remove_unwanted_headers(headers:)
headers.to_set.to_a.compact.reject do |item| headers.to_set.to_a.compact.reject do |item|
item.start_with? "Q" item.start_with? "Q"
@ -81,34 +93,19 @@ class Cleaner
File.write(output_filepath.join(prefix + filename), csv) File.write(output_filepath.join(prefix + filename), csv)
end end
def process_row(row:)
clean_csv << row.to_csv
log_csv << row.to_csv
end
def schools def schools
@schools ||= School.school_hash @schools ||= School.school_hash
end end
def genders def genders
@genders ||= begin @genders ||= Gender.gender_hash
gender_hash = {}
Gender.all.each do |gender|
gender_hash[gender.qualtrics_code] = gender
end
gender_hash
end
end end
def survey_items(headers:) def survey_items(headers:)
@survey_items ||= SurveyItem.where(survey_item_id: get_survey_item_ids_from_headers(headers:)) survey_item_ids = headers
end .filter(&:present?)
.filter { |header| header.start_with? "t-", "s-" }
def get_survey_item_ids_from_headers(headers:) @survey_items ||= SurveyItem.where(survey_item_id: survey_item_ids)
headers
.filter(&:present?)
.filter { |header| header.start_with? "t-", "s-" }
end end
def create_ouput_directory def create_ouput_directory
@ -118,8 +115,4 @@ class Cleaner
def create_log_directory def create_log_directory
FileUtils.mkdir_p log_filepath FileUtils.mkdir_p log_filepath
end end
def create_file(path:, filename:)
FileUtils.touch path.join(filename)
end
end end

@ -7,6 +7,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
@ -30,6 +31,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

@ -0,0 +1,30 @@
class DisaggregationLoader
attr_reader :path
def initialize(path:)
@path = path
initialize_directory
end
def load
data = {}
Dir.glob(Rails.root.join(path, "*.csv")).each do |filepath|
puts filepath
File.open(filepath) do |file|
headers = CSV.parse(file.first).first
file.lazy.each_slice(1000) do |lines|
CSV.parse(lines.join, headers:).map do |row|
values = DisaggregationRow.new(row:, headers:)
data[[values.lasid, values.district, values.academic_year]] = values
end
end
end
end
data
end
def initialize_directory
FileUtils.mkdir_p(path)
end
end

@ -0,0 +1,35 @@
class DisaggregationRow
attr_reader :row, :headers
def initialize(row:, headers:)
@row = row
@headers = headers
end
def district
@district ||= value_from(pattern: /District/i)
end
def academic_year
@academic_year ||= value_from(pattern: /Academic\s*Year/i)
end
def income
@income ||= value_from(pattern: /Low\s*Income/i)
end
def lasid
@lasid ||= value_from(pattern: /LASID/i)
end
def value_from(pattern:)
output = nil
matches = headers.select do |header|
pattern.match(header)
end.map { |item| item.delete("\n") }
matches.each do |match|
output ||= row[match]
end
output
end
end

@ -1,12 +1,13 @@
class SurveyItemValues class SurveyItemValues
attr_reader :row, :headers, :genders, :survey_items, :schools attr_reader :row, :headers, :genders, :survey_items, :schools, :disaggregation_data
def initialize(row:, headers:, genders:, survey_items:, schools:) def initialize(row:, headers:, genders:, survey_items:, schools:, disaggregation_data: nil)
@row = row @row = row
@headers = headers @headers = headers
@genders = genders @genders = genders
@survey_items = survey_items @survey_items = survey_items
@schools = schools @schools = schools
@disaggregation_data = disaggregation_data
end end
def dese_id? def dese_id?
@ -93,6 +94,37 @@ class SurveyItemValues
genders[gender_code] genders[gender_code]
end end
def lasid
@lasid ||= value_from(pattern: /LASID/i)
end
def raw_income
@raw_income ||= value_from(pattern: /Low\s*Income|Raw\s*Income/i)
return @raw_income if @raw_income.present?
return "Unknown" unless disaggregation_data.present?
disaggregation = disaggregation_data[[lasid, district.name, academic_year.range]]
return "Unknown" unless disaggregation.present?
@raw_income ||= disaggregation.income
end
# TODO: - rename these cases
def income
@income ||= value_from(pattern: /^Income$/i)
return @income if @income.present?
@income ||= case raw_income
in /Free\s*Lunch|Reduced\s*Lunch|Low\s*Income/i
"Economically Disadvantaged - Y"
in /Not\s*Eligible/i
"Economically Disadvantaged - N"
else
"Unknown"
end
end
def value_from(pattern:) def value_from(pattern:)
output = nil output = nil
matches = headers.select do |header| matches = headers.select do |header|
@ -106,10 +138,12 @@ class SurveyItemValues
def to_a def to_a
copy_likert_scores_from_variant_survey_items copy_likert_scores_from_variant_survey_items
row["Income"] = income
row["Raw Income"] = raw_income
headers.select(&:present?) headers.select(&:present?)
.reject { |key, _value| key.start_with? "Q" } .reject { |key, _value| key.start_with? "Q" }
.reject { |key, _value| key.end_with? "-1" } .reject { |key, _value| key.end_with? "-1" }
.map { |header| row[header] } .map { |header| row[header] }
end end
def duration def duration
@ -122,17 +156,17 @@ class SurveyItemValues
def respondent_type def respondent_type
return :teacher if headers return :teacher if headers
.filter(&:present?) .filter(&:present?)
.filter { |header| header.start_with? "t-" }.count > 0 .filter { |header| header.start_with? "t-" }.count > 0
:student :student
end end
def survey_type def survey_type
survey_item_ids = headers survey_item_ids = headers
.filter(&:present?) .filter(&:present?)
.reject { |header| header.end_with?("-1") } .reject { |header| header.end_with?("-1") }
.filter { |header| header.start_with?("t-", "s-") } .filter { |header| header.start_with?("t-", "s-") }
SurveyItem.survey_type(survey_item_ids:) SurveyItem.survey_type(survey_item_ids:)
end end
@ -200,3 +234,4 @@ class SurveyItemValues
end end
end end
end end

@ -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.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_08_07_222503) 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 "pg_stat_statements" enable_extension "pg_stat_statements"
enable_extension "plpgsql" enable_extension "plpgsql"
@ -77,6 +77,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_22_224103) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
create_table "incomes", force: :cascade do |t|
t.string "designation"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug"
t.index ["slug"], name: "index_incomes_on_slug", unique: true
end
create_table "legacy_attempts", id: :serial, force: :cascade do |t| create_table "legacy_attempts", id: :serial, force: :cascade do |t|
t.integer "recipient_id" t.integer "recipient_id"
t.integer "schedule_id" t.integer "schedule_id"
@ -297,21 +305,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_22_224103) do
t.index ["subcategory_id"], name: "index_measures_on_subcategory_id" t.index ["subcategory_id"], name: "index_measures_on_subcategory_id"
end end
create_table "race_scores", force: :cascade do |t|
t.bigint "measure_id", null: false
t.bigint "school_id", null: false
t.bigint "academic_year_id", null: false
t.bigint "race_id", null: false
t.float "average"
t.boolean "meets_student_threshold"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["academic_year_id"], name: "index_race_scores_on_academic_year_id"
t.index ["measure_id"], name: "index_race_scores_on_measure_id"
t.index ["race_id"], name: "index_race_scores_on_race_id"
t.index ["school_id"], name: "index_race_scores_on_school_id"
end
create_table "races", force: :cascade do |t| create_table "races", force: :cascade do |t|
t.string "designation" t.string "designation"
t.integer "qualtrics_code" t.integer "qualtrics_code"
@ -445,8 +438,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_22_224103) do
t.integer "grade" t.integer "grade"
t.bigint "gender_id" t.bigint "gender_id"
t.datetime "recorded_date" t.datetime "recorded_date"
t.bigint "income_id"
t.index ["academic_year_id"], name: "index_survey_item_responses_on_academic_year_id" t.index ["academic_year_id"], name: "index_survey_item_responses_on_academic_year_id"
t.index ["gender_id"], name: "index_survey_item_responses_on_gender_id" t.index ["gender_id"], name: "index_survey_item_responses_on_gender_id"
t.index ["income_id"], name: "index_survey_item_responses_on_income_id"
t.index ["response_id"], name: "index_survey_item_responses_on_response_id" t.index ["response_id"], name: "index_survey_item_responses_on_response_id"
t.index ["school_id", "academic_year_id", "survey_item_id"], name: "by_school_year_and_survey_item" t.index ["school_id", "academic_year_id", "survey_item_id"], name: "by_school_year_and_survey_item"
t.index ["school_id", "academic_year_id"], name: "index_survey_item_responses_on_school_id_and_academic_year_id" t.index ["school_id", "academic_year_id"], name: "index_survey_item_responses_on_school_id_and_academic_year_id"
@ -480,10 +475,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_22_224103) do
add_foreign_key "legacy_school_categories", "legacy_categories", column: "category_id" add_foreign_key "legacy_school_categories", "legacy_categories", column: "category_id"
add_foreign_key "legacy_school_categories", "legacy_schools", column: "school_id" add_foreign_key "legacy_school_categories", "legacy_schools", column: "school_id"
add_foreign_key "measures", "subcategories" add_foreign_key "measures", "subcategories"
add_foreign_key "race_scores", "academic_years"
add_foreign_key "race_scores", "measures"
add_foreign_key "race_scores", "races"
add_foreign_key "race_scores", "schools"
add_foreign_key "respondents", "academic_years" add_foreign_key "respondents", "academic_years"
add_foreign_key "respondents", "schools" add_foreign_key "respondents", "schools"
add_foreign_key "response_rates", "academic_years" add_foreign_key "response_rates", "academic_years"
@ -500,6 +491,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_22_224103) do
add_foreign_key "subcategories", "categories" add_foreign_key "subcategories", "categories"
add_foreign_key "survey_item_responses", "academic_years" add_foreign_key "survey_item_responses", "academic_years"
add_foreign_key "survey_item_responses", "genders" add_foreign_key "survey_item_responses", "genders"
add_foreign_key "survey_item_responses", "incomes"
add_foreign_key "survey_item_responses", "schools" add_foreign_key "survey_item_responses", "schools"
add_foreign_key "survey_item_responses", "students" add_foreign_key "survey_item_responses", "students"
add_foreign_key "survey_item_responses", "survey_items" add_foreign_key "survey_item_responses", "survey_items"

@ -43,7 +43,7 @@ namespace :one_off do
desc "load a single file" desc "load a single file"
task load_single_file: :environment do task load_single_file: :environment do
filepath = Rails.root.join("data", "survey_responses", filepath = Rails.root.join("data", "survey_responses",
"2021-22_revere_somerville_wareham_student_survey_responses.csv") "2021-22_revere_somerville_wareham_student_survey_responses.csv")
puts "=====================> Loading data from csv at path: #{filepath}" puts "=====================> Loading data from csv at path: #{filepath}"
SurveyResponsesDataLoader.load_data(filepath:) SurveyResponsesDataLoader.load_data(filepath:)
puts "=====================> Completed loading #{SurveyItemResponse.count} survey responses" puts "=====================> Completed loading #{SurveyItemResponse.count} survey responses"
@ -55,7 +55,7 @@ namespace :one_off do
desc "load butler results for 2021-22" desc "load butler results for 2021-22"
task load_butler: :environment do task load_butler: :environment do
["2022-23_butler_student_survey_responses.csv", ["2022-23_butler_student_survey_responses.csv",
"2022-23_butler_teacher_survey_responses.csv"].each do |filepath| "2022-23_butler_teacher_survey_responses.csv"].each do |filepath|
filepath = Rails.root.join("data", "survey_responses", filepath) filepath = Rails.root.join("data", "survey_responses", filepath)
puts "=====================> Loading data from csv at path: #{filepath}" puts "=====================> Loading data from csv at path: #{filepath}"
SurveyResponsesDataLoader.load_data filepath: SurveyResponsesDataLoader.load_data filepath:
@ -68,7 +68,7 @@ namespace :one_off do
desc "load winchester results for 2021-22" desc "load winchester results for 2021-22"
task load_winchester: :environment do task load_winchester: :environment do
["2021-22_winchester_student_survey_responses.csv", ["2021-22_winchester_student_survey_responses.csv",
"2021-22_winchester_teacher_survey_responses.csv"].each do |filepath| "2021-22_winchester_teacher_survey_responses.csv"].each do |filepath|
filepath = Rails.root.join("data", "survey_responses", filepath) filepath = Rails.root.join("data", "survey_responses", filepath)
puts "=====================> Loading data from csv at path: #{filepath}" puts "=====================> Loading data from csv at path: #{filepath}"
SurveyResponsesDataLoader.load_data filepath: SurveyResponsesDataLoader.load_data filepath:
@ -114,7 +114,7 @@ namespace :one_off do
District.all.map do |district| District.all.map do |district|
SurveyItem.all.map do |survey_item| SurveyItem.all.map do |survey_item|
[academic_year.range, survey_item.survey_item_id, [academic_year.range, survey_item.survey_item_id,
survey_item.survey_item_responses.joins(:school).where("school.district": district, academic_year:).count, district.name] survey_item.survey_item_responses.joins(:school).where("school.district": district, academic_year:).count, district.name]
end end
end end
end end
@ -164,23 +164,23 @@ namespace :one_off do
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 race scores" puts "Resetting race scores"
RaceScoreLoader.reset(fast_processing: true, academic_years:, schools: District.find_by_name("Wareham").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"
District.all.each do |district| District.all.each do |district|
num_of_respondents = SurveyItemResponse.joins(school: :district).where(academic_year:, num_of_respondents = SurveyItemResponse.joins(school: :district).where(academic_year:,
"schools.district": district).pluck(:response_id).uniq.count "schools.district": district).pluck(:response_id).uniq.count
teacher_respondents = SurveyItemResponse.joins(school: :district).where(academic_year:, teacher_respondents = SurveyItemResponse.joins(school: :district).where(academic_year:,
survey_item: SurveyItem.where("survey_item_id like ? ", "t-%"), "schools.district": district).pluck(:response_id).uniq.count survey_item: SurveyItem.where("survey_item_id like ? ", "t-%"), "schools.district": district).pluck(:response_id).uniq.count
student_respondents = SurveyItemResponse.joins(school: :district).where(academic_year:, student_respondents = SurveyItemResponse.joins(school: :district).where(academic_year:,
survey_item: SurveyItem.where("survey_item_id like ? ", "s-%"), "schools.district": district).pluck(:response_id).uniq.count survey_item: SurveyItem.where("survey_item_id like ? ", "s-%"), "schools.district": district).pluck(:response_id).uniq.count
response_count = SurveyItemResponse.joins(school: :district).where(academic_year:, response_count = SurveyItemResponse.joins(school: :district).where(academic_year:,
"schools.district": district).count "schools.district": district).count
student_response_count = SurveyItemResponse.joins(school: :district).joins(:survey_item).where(academic_year:, student_response_count = SurveyItemResponse.joins(school: :district).joins(:survey_item).where(academic_year:,
survey_item: SurveyItem.where("survey_item_id like ? ", "s-%"), "schools.district": district).count survey_item: SurveyItem.where("survey_item_id like ? ", "s-%"), "schools.district": district).count
teacher_response_count = SurveyItemResponse.joins(school: :district).joins(:survey_item).where(academic_year:, teacher_response_count = SurveyItemResponse.joins(school: :district).joins(:survey_item).where(academic_year:,
survey_item: SurveyItem.where("survey_item_id like ? ", "t-%"), "schools.district": district).count survey_item: SurveyItem.where("survey_item_id like ? ", "t-%"), "schools.district": district).count
puts "#{district.name} has #{num_of_respondents} respondents" puts "#{district.name} has #{num_of_respondents} respondents"
puts "#{district.name} has #{teacher_respondents} teacher respondents" puts "#{district.name} has #{teacher_respondents} teacher respondents"
puts "#{district.name} has #{student_respondents} student respondents" puts "#{district.name} has #{student_respondents} student respondents"

@ -21,7 +21,7 @@
"debounce": "^1.2.1", "debounce": "^1.2.1",
"esbuild": "^0.18.12", "esbuild": "^0.18.12",
"sass": "^1.43.4", "sass": "^1.43.4",
"semver": "6.3.1" "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' }

@ -0,0 +1,501 @@
District,Academic Year,LASID,HispanicLatino,Race,Gender,SpecialEdStatus,In 504 Plan,LowIncome,EL Student First Year
Maynard Public Schools,2022-23,1,TRUE,Caucasian,M,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,2,TRUE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,3,FALSE,Black,M,,,Reduced Lunch,
Maynard Public Schools,2022-23,4,FALSE,Caucasian,F,,Not 504,,LEP student not 1st year
Maynard Public Schools,2022-23,5,FALSE,Caucasian,M,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,6,TRUE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,7,FALSE,Caucasian,F,,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,8,FALSE,Caucasian,F,,Not 504,,Does not apply
Maynard Public Schools,2022-23,9,FALSE,"Asian,Native American",F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,10,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,11,FALSE,Caucasian,M,,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,12,FALSE,Caucasian,F,,Not 504,,Does not apply
Maynard Public Schools,2022-23,13,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,14,TRUE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,15,FALSE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,16,FALSE,Caucasian,M,,,Free Lunch,
Maynard Public Schools,2022-23,17,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,18,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,19,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,20,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,21,FALSE,Asian,F,,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,22,FALSE,Asian,F,,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,23,TRUE,Caucasian,F,,,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,24,TRUE,Caucasian,F,,Not 504,Free Lunch,
Maynard Public Schools,2022-23,25,TRUE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,26,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,27,TRUE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,28,TRUE,Caucasian,F,,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,29,TRUE,Native American,F,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,30,FALSE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,31,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,32,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,33,FALSE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,34,FALSE,Caucasian,M,Active,Not 504,Free Lunch,
Maynard Public Schools,2022-23,35,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,36,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,37,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,38,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,39,TRUE,"Black, Caucasian",M,Exited,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,40,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,41,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,42,FALSE,,M,,,Not Eligible,
Maynard Public Schools,2022-23,43,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,44,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,45,FALSE,Caucasian,F,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,46,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,47,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,48,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,49,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,50,TRUE,Native American,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,51,TRUE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,52,TRUE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,53,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,54,FALSE,"Caucasian,Pacific Island",M,Active,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,55,FALSE,"Caucasian,Pacific Island",F,,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,56,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,57,TRUE,Caucasian,M,Active,,Free Lunch,
Maynard Public Schools,2022-23,58,TRUE,Caucasian,M,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,59,TRUE,Caucasian,M,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,60,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,61,TRUE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,62,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,63,FALSE,,M,,,Not Eligible,
Maynard Public Schools,2022-23,64,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,65,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,66,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,67,FALSE,"Asian,Caucasian",M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,68,FALSE,"Asian,Caucasian",M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,69,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,70,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,71,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,72,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,73,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,74,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,75,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,76,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,77,TRUE,Caucasian,M,,504,Reduced Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,78,TRUE,Caucasian,M,,504,Reduced Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,79,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,80,TRUE,Caucasian,F,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,81,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,82,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,83,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,84,TRUE,Caucasian,M,,Not 504,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,85,FALSE,Caucasian,F,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,86,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,87,TRUE,Black,F,Active,504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,88,TRUE,Black,F,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,89,TRUE,Black,F,,,Free Lunch,
Maynard Public Schools,2022-23,90,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,91,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,92,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,93,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,94,FALSE,Caucasian,M,,,Free Lunch,
Maynard Public Schools,2022-23,95,FALSE,Caucasian,F,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,96,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,97,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,98,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,99,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,100,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,101,FALSE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,102,FALSE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,103,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,104,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,105,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,106,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,107,FALSE,Caucasian,M,Active,Not 504,Not Eligible,
Maynard Public Schools,2022-23,108,FALSE,Caucasian,M,Eligible,Not 504,Not Eligible,
Maynard Public Schools,2022-23,109,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,110,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,111,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,112,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,113,TRUE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,114,TRUE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,115,TRUE,Caucasian,F,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,116,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,117,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,118,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,119,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,120,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,121,FALSE,"Black, Native American",F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,122,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,123,FALSE,Caucasian,F,,,Reduced Lunch,
Maynard Public Schools,2022-23,124,FALSE,Caucasian,M,,,Reduced Lunch,
Maynard Public Schools,2022-23,125,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,126,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,127,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,128,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,129,FALSE,"Caucasian, Native American",F,,,Not Eligible,
Maynard Public Schools,2022-23,130,TRUE,Caucasian,F,,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,131,TRUE,Caucasian,M,,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,132,TRUE,Caucasian,F,Active,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,133,TRUE,Caucasian,F,,Not 504,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,134,TRUE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,135,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,136,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,137,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,138,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,139,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,140,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,141,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,142,TRUE,Caucasian,M,,,Not Eligible,LEP student 1st year
Maynard Public Schools,2022-23,143,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,144,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,145,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,146,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,147,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,148,FALSE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,149,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,150,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,151,FALSE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,152,TRUE,Caucasian,M,Active,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,153,TRUE,Caucasian,F,,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,154,FALSE,Caucasian,F,,,Not Eligible,Does not apply
Maynard Public Schools,2022-23,155,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,156,FALSE,Black,F,,504,Not Eligible,
Maynard Public Schools,2022-23,157,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,158,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,159,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,160,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,161,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,162,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,163,TRUE,Caucasian,M,,,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,164,TRUE,Caucasian,M,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,165,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,166,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,167,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,168,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,169,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,170,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,171,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,172,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,173,FALSE,Caucasian,F,,504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,174,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,175,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,176,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,177,FALSE,Caucasian,M,Exited,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,178,TRUE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,179,FALSE,Caucasian,M,,,Free Lunch,
Maynard Public Schools,2022-23,180,TRUE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,181,TRUE,Caucasian,F,,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,182,TRUE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,183,TRUE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,184,TRUE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,185,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,186,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,187,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,188,FALSE,"Asian,Caucasian",M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,189,FALSE,"Asian,Caucasian",M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,190,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,191,FALSE,"Asian,Caucasian",F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,192,FALSE,Black,F,Active,Not 504,Free Lunch,
Maynard Public Schools,2022-23,193,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,194,FALSE,Asian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,195,FALSE,Asian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,196,TRUE,Caucasian,F,,,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,197,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,198,FALSE,Caucasian,F,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,199,FALSE,"Asian,Caucasian",M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,200,FALSE,"Asian,Caucasian",F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,201,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,202,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,203,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,204,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,205,TRUE,Caucasian,M,,,Free Lunch,
Maynard Public Schools,2022-23,206,FALSE,Asian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,207,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,208,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,209,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,210,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,211,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,212,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,213,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,214,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,215,FALSE,Caucasian,F,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,216,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,217,FALSE,Caucasian,F,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,218,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,219,FALSE,Caucasian,M,Active,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,220,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,221,TRUE,"Black, Caucasian",M,Active,,Not Eligible,
Maynard Public Schools,2022-23,222,TRUE,Caucasian,F,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,223,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,224,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,225,FALSE,Caucasian,F,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,226,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,227,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,228,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,229,FALSE,Caucasian,M,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,230,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,231,TRUE,Pacific Island,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,232,TRUE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,233,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,234,FALSE,Caucasian,M,,,Free Lunch,
Maynard Public Schools,2022-23,235,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,236,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,237,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,238,TRUE,"Black, Caucasian, Native American",F,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,239,TRUE,Caucasian,M,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,240,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,241,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,242,TRUE,Caucasian,M,,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,243,TRUE,Caucasian,F,,,Reduced Lunch,LEP student 1st year
Maynard Public Schools,2022-23,244,TRUE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,245,FALSE,Caucasian,M,Active,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,246,TRUE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,247,FALSE,"Caucasian,Native American",M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,248,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,249,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,250,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,251,FALSE,"Black, Caucasian",M,Exited,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,252,FALSE,"Black, Caucasian",F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,253,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,254,FALSE,Caucasian,M,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,255,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,256,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,257,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,258,FALSE,Caucasian,M,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,259,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,260,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,261,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,262,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,263,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,264,TRUE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,265,TRUE,Caucasian,F,,,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,266,TRUE,Caucasian,M,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,267,FALSE,Caucasian,F,,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,268,TRUE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,269,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,270,TRUE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,271,TRUE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,272,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,273,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,274,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,275,FALSE,Black,M,,,Free Lunch,
Maynard Public Schools,2022-23,276,FALSE,Black,F,,,Free Lunch,
Maynard Public Schools,2022-23,277,FALSE,Black,F,,,Free Lunch,
Maynard Public Schools,2022-23,278,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,279,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,280,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,281,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,282,TRUE,Caucasian,M,Active,Not 504,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,283,TRUE,Caucasian,F,,Not 504,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,284,TRUE,Caucasian,F,,Not 504,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,285,TRUE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,286,TRUE,Caucasian,M,,504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,287,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,288,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,289,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,290,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,291,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,292,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,293,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,294,FALSE,Caucasian,M,Active,Not 504,Free Lunch,
Maynard Public Schools,2022-23,295,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,296,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,297,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,298,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,299,FALSE,Caucasian,M,Active,504,Not Eligible,
Maynard Public Schools,2022-23,300,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,301,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,302,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,303,FALSE,"Asian, Black",M,,,Not Eligible,
Maynard Public Schools,2022-23,304,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,305,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,306,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,307,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,308,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,309,TRUE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,310,TRUE,Caucasian,F,,,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,311,TRUE,Caucasian,F,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,312,TRUE,Caucasian,M,,504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,313,TRUE,Caucasian,M,Active,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,314,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,315,FALSE,Caucasian,M,Active,Not 504,Not Eligible,
Maynard Public Schools,2022-23,316,FALSE,Caucasian,M,Exited,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,317,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,318,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,319,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,320,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,321,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,322,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,323,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,324,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,325,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,326,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,327,TRUE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,328,TRUE,Caucasian,M,,,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,329,TRUE,Caucasian,F,,,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,330,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,331,FALSE,,M,,,Not Eligible,
Maynard Public Schools,2022-23,332,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,333,FALSE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,334,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,335,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,336,FALSE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,337,FALSE,Native American,M,Active,,Not Eligible,
Maynard Public Schools,2022-23,338,TRUE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,339,FALSE,"Black, Caucasian",F,,,Not Eligible,
Maynard Public Schools,2022-23,340,FALSE,"Black, Caucasian",F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,341,TRUE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,342,TRUE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,343,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,344,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,345,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,346,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,347,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,348,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,349,TRUE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,350,TRUE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,351,TRUE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,352,FALSE,Caucasian,M,,,Free Lunch,
Maynard Public Schools,2022-23,353,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,354,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,355,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,356,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,357,FALSE,Caucasian,M,,504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,358,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,359,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,360,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,361,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,362,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,363,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,364,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,365,FALSE,Black,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,366,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,367,FALSE,Caucasian,M,,504,Not Eligible,
Maynard Public Schools,2022-23,368,FALSE,Caucasian,M,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,369,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,370,FALSE,Caucasian,M,,,Free Lunch,
Maynard Public Schools,2022-23,371,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,372,FALSE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,373,FALSE,"Asian,Caucasian",F,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,374,FALSE,Caucasian,M,,,Free Lunch,
Maynard Public Schools,2022-23,375,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,376,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,377,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,378,FALSE,Caucasian,F,Active,Not 504,Not Eligible,
Maynard Public Schools,2022-23,379,FALSE,Caucasian,F,Exited,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,380,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,381,FALSE,Caucasian,F,Active,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,382,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,383,TRUE,Black,F,Active,Not 504,Free Lunch,LEP student not 1st year
Maynard Public Schools,2022-23,384,TRUE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,385,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,386,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,387,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,388,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,389,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,390,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,391,FALSE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,392,FALSE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,393,FALSE,"Caucasian,Native American",F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,394,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,395,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,396,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,397,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,398,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,399,FALSE,"Caucasian,Native American",M,,504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,400,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,401,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,402,FALSE,Caucasian,F,,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,403,FALSE,Caucasian,M,,Not 504,Reduced Lunch,Does not apply
Maynard Public Schools,2022-23,404,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,405,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,406,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,407,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,408,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,409,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,410,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,411,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,412,TRUE,Native American,F,,,Not Eligible,
Maynard Public Schools,2022-23,413,TRUE,Native American,F,,504,Not Eligible,
Maynard Public Schools,2022-23,414,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,415,FALSE,Asian,M,,Not 504,Not Eligible,
Maynard Public Schools,2022-23,416,TRUE,Native American,F,,,Not Eligible,
Maynard Public Schools,2022-23,417,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,418,TRUE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,419,FALSE,Asian,M,Exited,Not 504,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,420,FALSE,Asian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,421,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,422,FALSE,"Asian,Caucasian",F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,423,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,424,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,425,FALSE,Caucasian,M,Active,Not 504,Free Lunch,
Maynard Public Schools,2022-23,426,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,427,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,428,FALSE,Caucasian,F,,,Free Lunch,Does not apply
Maynard Public Schools,2022-23,429,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,430,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,431,TRUE,Caucasian,M,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,432,TRUE,Caucasian,M,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,433,TRUE,Native American,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,434,TRUE,Native American,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,435,TRUE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,436,TRUE,Caucasian,M,Active,Not 504,Free Lunch,
Maynard Public Schools,2022-23,437,TRUE,Caucasian,M,,,Not Eligible,Does not apply
Maynard Public Schools,2022-23,438,TRUE,Caucasian,M,,Not 504,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,439,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,440,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,441,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,442,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,443,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,444,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,445,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,446,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,447,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,448,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,449,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,450,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,451,TRUE,Caucasian,F,,,Free Lunch,LEP student 1st year
Maynard Public Schools,2022-23,452,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,453,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,454,FALSE,Caucasian,M,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,455,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,456,FALSE,"Caucasian,Pacific Island",F,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,457,FALSE,Black,M,,,Free Lunch,
Maynard Public Schools,2022-23,458,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,459,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,460,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,461,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,462,TRUE,Caucasian,F,,,Free Lunch,
Maynard Public Schools,2022-23,463,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,464,FALSE,Caucasian,F,,Not 504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,465,FALSE,Caucasian,F,,,Not Eligible,Does not apply
Maynard Public Schools,2022-23,466,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,467,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,468,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,469,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,470,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,471,FALSE,Caucasian,N,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,472,FALSE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,473,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,474,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,475,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,476,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,477,TRUE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,478,TRUE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,479,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,480,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,481,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,482,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,483,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,484,FALSE,Caucasian,M,Active,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,485,FALSE,Caucasian,M,,,Not Eligible,
Maynard Public Schools,2022-23,486,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,487,FALSE,Caucasian,F,,504,Free Lunch,Does not apply
Maynard Public Schools,2022-23,488,TRUE,Caucasian,M,,,Not Eligible,LEP student not 1st year
Maynard Public Schools,2022-23,489,FALSE,Caucasian,F,Exited,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,490,FALSE,Caucasian,M,Active,Not 504,Free Lunch,
Maynard Public Schools,2022-23,491,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,492,FALSE,Caucasian,F,,504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,493,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,494,TRUE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,495,TRUE,Caucasian,M,Active,Not 504,Free Lunch,
Maynard Public Schools,2022-23,496,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,497,TRUE,Caucasian,F,,,Not Eligible,
Maynard Public Schools,2022-23,498,FALSE,Caucasian,M,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,499,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
Maynard Public Schools,2022-23,500,FALSE,Caucasian,F,,Not 504,Not Eligible,Does not apply
1 District Academic Year LASID HispanicLatino Race Gender SpecialEdStatus In 504 Plan LowIncome EL Student First Year
2 Maynard Public Schools 2022-23 1 TRUE Caucasian M Active Not 504 Free Lunch Does not apply
3 Maynard Public Schools 2022-23 2 TRUE Caucasian M Not 504 Not Eligible Does not apply
4 Maynard Public Schools 2022-23 3 FALSE Black M Reduced Lunch
5 Maynard Public Schools 2022-23 4 FALSE Caucasian F Not 504 LEP student not 1st year
6 Maynard Public Schools 2022-23 5 FALSE Caucasian M Active Not 504 Free Lunch Does not apply
7 Maynard Public Schools 2022-23 6 TRUE Caucasian M Active Not 504 Not Eligible Does not apply
8 Maynard Public Schools 2022-23 7 FALSE Caucasian F Not 504 Reduced Lunch Does not apply
9 Maynard Public Schools 2022-23 8 FALSE Caucasian F Not 504 Does not apply
10 Maynard Public Schools 2022-23 9 FALSE Asian,Native American F Not 504 Free Lunch Does not apply
11 Maynard Public Schools 2022-23 10 FALSE Caucasian F Not Eligible
12 Maynard Public Schools 2022-23 11 FALSE Caucasian M Not 504 Reduced Lunch Does not apply
13 Maynard Public Schools 2022-23 12 FALSE Caucasian F Not 504 Does not apply
14 Maynard Public Schools 2022-23 13 FALSE Caucasian F Not 504 Not Eligible Does not apply
15 Maynard Public Schools 2022-23 14 TRUE Caucasian F Free Lunch
16 Maynard Public Schools 2022-23 15 FALSE Caucasian F Free Lunch
17 Maynard Public Schools 2022-23 16 FALSE Caucasian M Free Lunch
18 Maynard Public Schools 2022-23 17 FALSE Caucasian F Not 504 Not Eligible Does not apply
19 Maynard Public Schools 2022-23 18 FALSE Caucasian F Not 504 Not Eligible Does not apply
20 Maynard Public Schools 2022-23 19 FALSE Caucasian F Not 504 Free Lunch Does not apply
21 Maynard Public Schools 2022-23 20 FALSE Caucasian M Not 504 Not Eligible Does not apply
22 Maynard Public Schools 2022-23 21 FALSE Asian F Not 504 Reduced Lunch Does not apply
23 Maynard Public Schools 2022-23 22 FALSE Asian F Not 504 Reduced Lunch Does not apply
24 Maynard Public Schools 2022-23 23 TRUE Caucasian F Not Eligible LEP student not 1st year
25 Maynard Public Schools 2022-23 24 TRUE Caucasian F Not 504 Free Lunch
26 Maynard Public Schools 2022-23 25 TRUE Caucasian M Not 504 Free Lunch Does not apply
27 Maynard Public Schools 2022-23 26 FALSE Caucasian F Not 504 Not Eligible Does not apply
28 Maynard Public Schools 2022-23 27 TRUE Caucasian M Not 504 Free Lunch Does not apply
29 Maynard Public Schools 2022-23 28 TRUE Caucasian F Not 504 Free Lunch LEP student not 1st year
30 Maynard Public Schools 2022-23 29 TRUE Native American F Free Lunch LEP student 1st year
31 Maynard Public Schools 2022-23 30 FALSE Caucasian M Not 504 Free Lunch Does not apply
32 Maynard Public Schools 2022-23 31 FALSE Caucasian F Not 504 Not Eligible Does not apply
33 Maynard Public Schools 2022-23 32 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
34 Maynard Public Schools 2022-23 33 FALSE Caucasian F Free Lunch
35 Maynard Public Schools 2022-23 34 FALSE Caucasian M Active Not 504 Free Lunch
36 Maynard Public Schools 2022-23 35 FALSE Caucasian M Not 504 Not Eligible Does not apply
37 Maynard Public Schools 2022-23 36 FALSE Caucasian F Not 504 Not Eligible Does not apply
38 Maynard Public Schools 2022-23 37 FALSE Caucasian F Not Eligible
39 Maynard Public Schools 2022-23 38 FALSE Caucasian M Not 504 Not Eligible Does not apply
40 Maynard Public Schools 2022-23 39 TRUE Black, Caucasian M Exited Not 504 Reduced Lunch Does not apply
41 Maynard Public Schools 2022-23 40 FALSE Caucasian M Not 504 Not Eligible Does not apply
42 Maynard Public Schools 2022-23 41 FALSE Caucasian F Not 504 Not Eligible Does not apply
43 Maynard Public Schools 2022-23 42 FALSE M Not Eligible
44 Maynard Public Schools 2022-23 43 FALSE Caucasian F Not Eligible
45 Maynard Public Schools 2022-23 44 FALSE Caucasian F Not 504 Not Eligible Does not apply
46 Maynard Public Schools 2022-23 45 FALSE Caucasian F Active Not 504 Not Eligible Does not apply
47 Maynard Public Schools 2022-23 46 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
48 Maynard Public Schools 2022-23 47 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
49 Maynard Public Schools 2022-23 48 FALSE Caucasian M Not 504 Not Eligible Does not apply
50 Maynard Public Schools 2022-23 49 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
51 Maynard Public Schools 2022-23 50 TRUE Native American M Not 504 Not Eligible Does not apply
52 Maynard Public Schools 2022-23 51 TRUE Caucasian F Not 504 Not Eligible Does not apply
53 Maynard Public Schools 2022-23 52 TRUE Caucasian F Not 504 Not Eligible Does not apply
54 Maynard Public Schools 2022-23 53 FALSE Caucasian M 504 Not Eligible Does not apply
55 Maynard Public Schools 2022-23 54 FALSE Caucasian,Pacific Island M Active Not 504 Reduced Lunch Does not apply
56 Maynard Public Schools 2022-23 55 FALSE Caucasian,Pacific Island F Not 504 Reduced Lunch Does not apply
57 Maynard Public Schools 2022-23 56 FALSE Caucasian F Not 504 Free Lunch Does not apply
58 Maynard Public Schools 2022-23 57 TRUE Caucasian M Active Free Lunch
59 Maynard Public Schools 2022-23 58 TRUE Caucasian M Free Lunch LEP student 1st year
60 Maynard Public Schools 2022-23 59 TRUE Caucasian M Active Not 504 Free Lunch Does not apply
61 Maynard Public Schools 2022-23 60 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
62 Maynard Public Schools 2022-23 61 TRUE Caucasian F Not 504 Not Eligible Does not apply
63 Maynard Public Schools 2022-23 62 FALSE Caucasian F Not 504 Not Eligible Does not apply
64 Maynard Public Schools 2022-23 63 FALSE M Not Eligible
65 Maynard Public Schools 2022-23 64 FALSE Caucasian F Not 504 Not Eligible Does not apply
66 Maynard Public Schools 2022-23 65 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
67 Maynard Public Schools 2022-23 66 FALSE Caucasian F Not 504 Not Eligible Does not apply
68 Maynard Public Schools 2022-23 67 FALSE Asian,Caucasian M Not 504 Not Eligible Does not apply
69 Maynard Public Schools 2022-23 68 FALSE Asian,Caucasian M Not 504 Not Eligible Does not apply
70 Maynard Public Schools 2022-23 69 FALSE Caucasian M Not Eligible
71 Maynard Public Schools 2022-23 70 FALSE Caucasian M Not 504 Not Eligible Does not apply
72 Maynard Public Schools 2022-23 71 FALSE Caucasian M Not 504 Not Eligible Does not apply
73 Maynard Public Schools 2022-23 72 FALSE Caucasian M Not 504 Not Eligible Does not apply
74 Maynard Public Schools 2022-23 73 FALSE Caucasian F Not 504 Not Eligible Does not apply
75 Maynard Public Schools 2022-23 74 FALSE Caucasian M Not 504 Not Eligible Does not apply
76 Maynard Public Schools 2022-23 75 FALSE Caucasian M 504 Not Eligible Does not apply
77 Maynard Public Schools 2022-23 76 FALSE Caucasian M 504 Not Eligible Does not apply
78 Maynard Public Schools 2022-23 77 TRUE Caucasian M 504 Reduced Lunch LEP student not 1st year
79 Maynard Public Schools 2022-23 78 TRUE Caucasian M 504 Reduced Lunch LEP student not 1st year
80 Maynard Public Schools 2022-23 79 FALSE Caucasian F Not 504 Not Eligible Does not apply
81 Maynard Public Schools 2022-23 80 TRUE Caucasian F Free Lunch LEP student 1st year
82 Maynard Public Schools 2022-23 81 FALSE Caucasian F Not Eligible
83 Maynard Public Schools 2022-23 82 FALSE Caucasian F Not 504 Not Eligible Does not apply
84 Maynard Public Schools 2022-23 83 FALSE Caucasian M Not Eligible
85 Maynard Public Schools 2022-23 84 TRUE Caucasian M Not 504 Not Eligible LEP student not 1st year
86 Maynard Public Schools 2022-23 85 FALSE Caucasian F Active Not 504 Not Eligible Does not apply
87 Maynard Public Schools 2022-23 86 FALSE Caucasian M Not Eligible
88 Maynard Public Schools 2022-23 87 TRUE Black F Active 504 Free Lunch Does not apply
89 Maynard Public Schools 2022-23 88 TRUE Black F Active Not 504 Free Lunch Does not apply
90 Maynard Public Schools 2022-23 89 TRUE Black F Free Lunch
91 Maynard Public Schools 2022-23 90 FALSE Caucasian F Not 504 Not Eligible Does not apply
92 Maynard Public Schools 2022-23 91 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
93 Maynard Public Schools 2022-23 92 FALSE Caucasian F Not Eligible
94 Maynard Public Schools 2022-23 93 FALSE Caucasian F 504 Not Eligible Does not apply
95 Maynard Public Schools 2022-23 94 FALSE Caucasian M Free Lunch
96 Maynard Public Schools 2022-23 95 FALSE Caucasian F Active Not 504 Not Eligible Does not apply
97 Maynard Public Schools 2022-23 96 FALSE Caucasian M Not 504 Not Eligible Does not apply
98 Maynard Public Schools 2022-23 97 FALSE Caucasian M Not Eligible
99 Maynard Public Schools 2022-23 98 FALSE Caucasian F Not 504 Not Eligible Does not apply
100 Maynard Public Schools 2022-23 99 FALSE Caucasian M 504 Not Eligible Does not apply
101 Maynard Public Schools 2022-23 100 FALSE Caucasian M Not 504 Not Eligible Does not apply
102 Maynard Public Schools 2022-23 101 FALSE Caucasian F Free Lunch
103 Maynard Public Schools 2022-23 102 FALSE Caucasian F Free Lunch
104 Maynard Public Schools 2022-23 103 FALSE Caucasian M Not Eligible
105 Maynard Public Schools 2022-23 104 FALSE Caucasian F Not 504 Not Eligible Does not apply
106 Maynard Public Schools 2022-23 105 FALSE Caucasian F Not 504 Not Eligible Does not apply
107 Maynard Public Schools 2022-23 106 FALSE Caucasian F Not Eligible
108 Maynard Public Schools 2022-23 107 FALSE Caucasian M Active Not 504 Not Eligible
109 Maynard Public Schools 2022-23 108 FALSE Caucasian M Eligible Not 504 Not Eligible
110 Maynard Public Schools 2022-23 109 FALSE Caucasian F 504 Not Eligible Does not apply
111 Maynard Public Schools 2022-23 110 FALSE Caucasian M Not 504 Not Eligible Does not apply
112 Maynard Public Schools 2022-23 111 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
113 Maynard Public Schools 2022-23 112 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
114 Maynard Public Schools 2022-23 113 TRUE Caucasian F Free Lunch
115 Maynard Public Schools 2022-23 114 TRUE Caucasian M Not 504 Free Lunch Does not apply
116 Maynard Public Schools 2022-23 115 TRUE Caucasian F Active Not 504 Free Lunch Does not apply
117 Maynard Public Schools 2022-23 116 FALSE Caucasian M Not 504 Not Eligible Does not apply
118 Maynard Public Schools 2022-23 117 FALSE Caucasian F Not Eligible
119 Maynard Public Schools 2022-23 118 FALSE Caucasian F Not 504 Not Eligible Does not apply
120 Maynard Public Schools 2022-23 119 FALSE Caucasian F Not 504 Not Eligible Does not apply
121 Maynard Public Schools 2022-23 120 FALSE Caucasian F Not Eligible
122 Maynard Public Schools 2022-23 121 FALSE Black, Native American F Not 504 Free Lunch Does not apply
123 Maynard Public Schools 2022-23 122 FALSE Caucasian M Not 504 Not Eligible Does not apply
124 Maynard Public Schools 2022-23 123 FALSE Caucasian F Reduced Lunch
125 Maynard Public Schools 2022-23 124 FALSE Caucasian M Reduced Lunch
126 Maynard Public Schools 2022-23 125 FALSE Caucasian F Not 504 Not Eligible Does not apply
127 Maynard Public Schools 2022-23 126 FALSE Caucasian M Not 504 Not Eligible Does not apply
128 Maynard Public Schools 2022-23 127 FALSE Caucasian F Not 504 Not Eligible Does not apply
129 Maynard Public Schools 2022-23 128 FALSE Caucasian M Not 504 Not Eligible Does not apply
130 Maynard Public Schools 2022-23 129 FALSE Caucasian, Native American F Not Eligible
131 Maynard Public Schools 2022-23 130 TRUE Caucasian F Not 504 Free Lunch LEP student not 1st year
132 Maynard Public Schools 2022-23 131 TRUE Caucasian M Not 504 Free Lunch LEP student not 1st year
133 Maynard Public Schools 2022-23 132 TRUE Caucasian F Active Not 504 Free Lunch LEP student not 1st year
134 Maynard Public Schools 2022-23 133 TRUE Caucasian F Not 504 Not Eligible LEP student not 1st year
135 Maynard Public Schools 2022-23 134 TRUE Caucasian F Not 504 Not Eligible Does not apply
136 Maynard Public Schools 2022-23 135 FALSE Caucasian M Not 504 Not Eligible Does not apply
137 Maynard Public Schools 2022-23 136 FALSE Caucasian F Not 504 Not Eligible Does not apply
138 Maynard Public Schools 2022-23 137 FALSE Caucasian F Not 504 Not Eligible Does not apply
139 Maynard Public Schools 2022-23 138 FALSE Caucasian F Not 504 Not Eligible Does not apply
140 Maynard Public Schools 2022-23 139 FALSE Caucasian M Not 504 Not Eligible Does not apply
141 Maynard Public Schools 2022-23 140 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
142 Maynard Public Schools 2022-23 141 FALSE Caucasian M Not 504 Not Eligible Does not apply
143 Maynard Public Schools 2022-23 142 TRUE Caucasian M Not Eligible LEP student 1st year
144 Maynard Public Schools 2022-23 143 FALSE Caucasian F Not 504 Not Eligible Does not apply
145 Maynard Public Schools 2022-23 144 FALSE Caucasian M Not 504 Not Eligible Does not apply
146 Maynard Public Schools 2022-23 145 FALSE Caucasian F Not 504 Not Eligible Does not apply
147 Maynard Public Schools 2022-23 146 FALSE Caucasian F Not 504 Free Lunch Does not apply
148 Maynard Public Schools 2022-23 147 FALSE Caucasian F Not 504 Free Lunch Does not apply
149 Maynard Public Schools 2022-23 148 FALSE Caucasian M Not 504 Free Lunch Does not apply
150 Maynard Public Schools 2022-23 149 FALSE Caucasian M Not 504 Not Eligible Does not apply
151 Maynard Public Schools 2022-23 150 FALSE Caucasian F Not 504 Not Eligible Does not apply
152 Maynard Public Schools 2022-23 151 FALSE Caucasian M Not 504 Free Lunch Does not apply
153 Maynard Public Schools 2022-23 152 TRUE Caucasian M Active Not 504 Free Lunch LEP student not 1st year
154 Maynard Public Schools 2022-23 153 TRUE Caucasian F Not 504 Free Lunch LEP student not 1st year
155 Maynard Public Schools 2022-23 154 FALSE Caucasian F Not Eligible Does not apply
156 Maynard Public Schools 2022-23 155 FALSE Caucasian F Not Eligible
157 Maynard Public Schools 2022-23 156 FALSE Black F 504 Not Eligible
158 Maynard Public Schools 2022-23 157 FALSE Caucasian F Not Eligible
159 Maynard Public Schools 2022-23 158 FALSE Caucasian F Not 504 Not Eligible Does not apply
160 Maynard Public Schools 2022-23 159 FALSE Caucasian M Not 504 Not Eligible Does not apply
161 Maynard Public Schools 2022-23 160 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
162 Maynard Public Schools 2022-23 161 FALSE Caucasian M Not 504 Not Eligible Does not apply
163 Maynard Public Schools 2022-23 162 FALSE Caucasian M Not 504 Not Eligible Does not apply
164 Maynard Public Schools 2022-23 163 TRUE Caucasian M Free Lunch LEP student not 1st year
165 Maynard Public Schools 2022-23 164 TRUE Caucasian M Free Lunch LEP student 1st year
166 Maynard Public Schools 2022-23 165 FALSE Caucasian F Not 504 Not Eligible Does not apply
167 Maynard Public Schools 2022-23 166 FALSE Caucasian M Not 504 Not Eligible Does not apply
168 Maynard Public Schools 2022-23 167 FALSE Caucasian F Not 504 Not Eligible Does not apply
169 Maynard Public Schools 2022-23 168 FALSE Caucasian M Not 504 Not Eligible Does not apply
170 Maynard Public Schools 2022-23 169 FALSE Caucasian F Not 504 Free Lunch Does not apply
171 Maynard Public Schools 2022-23 170 FALSE Caucasian F Not 504 Free Lunch Does not apply
172 Maynard Public Schools 2022-23 171 FALSE Caucasian F Not 504 Free Lunch Does not apply
173 Maynard Public Schools 2022-23 172 FALSE Caucasian F Not 504 Free Lunch Does not apply
174 Maynard Public Schools 2022-23 173 FALSE Caucasian F 504 Free Lunch Does not apply
175 Maynard Public Schools 2022-23 174 FALSE Caucasian F Not 504 Free Lunch Does not apply
176 Maynard Public Schools 2022-23 175 FALSE Caucasian F Not Eligible
177 Maynard Public Schools 2022-23 176 FALSE Caucasian F Not 504 Not Eligible Does not apply
178 Maynard Public Schools 2022-23 177 FALSE Caucasian M Exited 504 Not Eligible Does not apply
179 Maynard Public Schools 2022-23 178 TRUE Caucasian F Not Eligible
180 Maynard Public Schools 2022-23 179 FALSE Caucasian M Free Lunch
181 Maynard Public Schools 2022-23 180 TRUE Caucasian F Not 504 Free Lunch Does not apply
182 Maynard Public Schools 2022-23 181 TRUE Caucasian F Not 504 Free Lunch LEP student not 1st year
183 Maynard Public Schools 2022-23 182 TRUE Caucasian F Not 504 Free Lunch Does not apply
184 Maynard Public Schools 2022-23 183 TRUE Caucasian M Not 504 Free Lunch Does not apply
185 Maynard Public Schools 2022-23 184 TRUE Caucasian M Not 504 Free Lunch Does not apply
186 Maynard Public Schools 2022-23 185 FALSE Caucasian F Not Eligible
187 Maynard Public Schools 2022-23 186 FALSE Caucasian M Not Eligible
188 Maynard Public Schools 2022-23 187 FALSE Caucasian M Not Eligible
189 Maynard Public Schools 2022-23 188 FALSE Asian,Caucasian M Active Not 504 Not Eligible Does not apply
190 Maynard Public Schools 2022-23 189 FALSE Asian,Caucasian M Not 504 Not Eligible Does not apply
191 Maynard Public Schools 2022-23 190 FALSE Caucasian F Not 504 Not Eligible Does not apply
192 Maynard Public Schools 2022-23 191 FALSE Asian,Caucasian F Not 504 Not Eligible Does not apply
193 Maynard Public Schools 2022-23 192 FALSE Black F Active Not 504 Free Lunch
194 Maynard Public Schools 2022-23 193 FALSE Caucasian F Not 504 Not Eligible Does not apply
195 Maynard Public Schools 2022-23 194 FALSE Asian F Not 504 Not Eligible Does not apply
196 Maynard Public Schools 2022-23 195 FALSE Asian M Not 504 Not Eligible Does not apply
197 Maynard Public Schools 2022-23 196 TRUE Caucasian F Not Eligible LEP student not 1st year
198 Maynard Public Schools 2022-23 197 FALSE Caucasian M Not Eligible
199 Maynard Public Schools 2022-23 198 FALSE Caucasian F Active Not 504 Not Eligible Does not apply
200 Maynard Public Schools 2022-23 199 FALSE Asian,Caucasian M Active Not 504 Not Eligible Does not apply
201 Maynard Public Schools 2022-23 200 FALSE Asian,Caucasian F Not 504 Not Eligible Does not apply
202 Maynard Public Schools 2022-23 201 FALSE Caucasian F Not 504 Not Eligible Does not apply
203 Maynard Public Schools 2022-23 202 FALSE Caucasian M Not Eligible
204 Maynard Public Schools 2022-23 203 FALSE Caucasian F Not 504 Not Eligible Does not apply
205 Maynard Public Schools 2022-23 204 FALSE Caucasian F Not 504 Not Eligible Does not apply
206 Maynard Public Schools 2022-23 205 TRUE Caucasian M Free Lunch
207 Maynard Public Schools 2022-23 206 FALSE Asian M Active Not 504 Not Eligible Does not apply
208 Maynard Public Schools 2022-23 207 FALSE Caucasian M Not 504 Not Eligible Does not apply
209 Maynard Public Schools 2022-23 208 FALSE Caucasian M Not 504 Not Eligible Does not apply
210 Maynard Public Schools 2022-23 209 FALSE Caucasian F Not 504 Not Eligible Does not apply
211 Maynard Public Schools 2022-23 210 FALSE Caucasian F Not Eligible
212 Maynard Public Schools 2022-23 211 FALSE Caucasian M Not 504 Not Eligible Does not apply
213 Maynard Public Schools 2022-23 212 FALSE Caucasian M Not 504 Not Eligible Does not apply
214 Maynard Public Schools 2022-23 213 FALSE Caucasian M Not Eligible
215 Maynard Public Schools 2022-23 214 FALSE Caucasian F Not 504 Not Eligible Does not apply
216 Maynard Public Schools 2022-23 215 FALSE Caucasian F Active Not 504 Free Lunch Does not apply
217 Maynard Public Schools 2022-23 216 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
218 Maynard Public Schools 2022-23 217 FALSE Caucasian F Active Not 504 Not Eligible Does not apply
219 Maynard Public Schools 2022-23 218 FALSE Caucasian M Not 504 Not Eligible Does not apply
220 Maynard Public Schools 2022-23 219 FALSE Caucasian M Active Not 504 Reduced Lunch Does not apply
221 Maynard Public Schools 2022-23 220 FALSE Caucasian M Not 504 Not Eligible Does not apply
222 Maynard Public Schools 2022-23 221 TRUE Black, Caucasian M Active Not Eligible
223 Maynard Public Schools 2022-23 222 TRUE Caucasian F Free Lunch LEP student 1st year
224 Maynard Public Schools 2022-23 223 FALSE Caucasian F Not 504 Not Eligible Does not apply
225 Maynard Public Schools 2022-23 224 FALSE Caucasian M Not 504 Not Eligible Does not apply
226 Maynard Public Schools 2022-23 225 FALSE Caucasian F Active Not 504 Not Eligible Does not apply
227 Maynard Public Schools 2022-23 226 FALSE Caucasian M Not 504 Not Eligible Does not apply
228 Maynard Public Schools 2022-23 227 FALSE Caucasian M Not 504 Not Eligible Does not apply
229 Maynard Public Schools 2022-23 228 FALSE Caucasian M 504 Not Eligible Does not apply
230 Maynard Public Schools 2022-23 229 FALSE Caucasian M Active Not 504 Free Lunch Does not apply
231 Maynard Public Schools 2022-23 230 FALSE Caucasian M Not Eligible
232 Maynard Public Schools 2022-23 231 TRUE Pacific Island F Not 504 Free Lunch Does not apply
233 Maynard Public Schools 2022-23 232 TRUE Caucasian F Not 504 Free Lunch Does not apply
234 Maynard Public Schools 2022-23 233 FALSE Caucasian F Not 504 Not Eligible Does not apply
235 Maynard Public Schools 2022-23 234 FALSE Caucasian M Free Lunch
236 Maynard Public Schools 2022-23 235 FALSE Caucasian F Not Eligible
237 Maynard Public Schools 2022-23 236 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
238 Maynard Public Schools 2022-23 237 FALSE Caucasian M Not 504 Not Eligible Does not apply
239 Maynard Public Schools 2022-23 238 TRUE Black, Caucasian, Native American F Active Not 504 Free Lunch Does not apply
240 Maynard Public Schools 2022-23 239 TRUE Caucasian M Active Not 504 Free Lunch Does not apply
241 Maynard Public Schools 2022-23 240 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
242 Maynard Public Schools 2022-23 241 FALSE Caucasian F Not 504 Free Lunch Does not apply
243 Maynard Public Schools 2022-23 242 TRUE Caucasian M Not 504 Reduced Lunch Does not apply
244 Maynard Public Schools 2022-23 243 TRUE Caucasian F Reduced Lunch LEP student 1st year
245 Maynard Public Schools 2022-23 244 TRUE Caucasian F Not Eligible
246 Maynard Public Schools 2022-23 245 FALSE Caucasian M Active Not 504 Free Lunch LEP student not 1st year
247 Maynard Public Schools 2022-23 246 TRUE Caucasian F Not Eligible
248 Maynard Public Schools 2022-23 247 FALSE Caucasian,Native American M Active Not 504 Not Eligible Does not apply
249 Maynard Public Schools 2022-23 248 FALSE Caucasian F Not 504 Not Eligible Does not apply
250 Maynard Public Schools 2022-23 249 FALSE Caucasian M 504 Not Eligible Does not apply
251 Maynard Public Schools 2022-23 250 FALSE Caucasian M Not 504 Not Eligible Does not apply
252 Maynard Public Schools 2022-23 251 FALSE Black, Caucasian M Exited Not 504 Free Lunch Does not apply
253 Maynard Public Schools 2022-23 252 FALSE Black, Caucasian F Not 504 Free Lunch Does not apply
254 Maynard Public Schools 2022-23 253 FALSE Caucasian F Not 504 Not Eligible Does not apply
255 Maynard Public Schools 2022-23 254 FALSE Caucasian M Active Not 504 Free Lunch Does not apply
256 Maynard Public Schools 2022-23 255 FALSE Caucasian F 504 Not Eligible Does not apply
257 Maynard Public Schools 2022-23 256 FALSE Caucasian F Not 504 Not Eligible Does not apply
258 Maynard Public Schools 2022-23 257 FALSE Caucasian F Not 504 Not Eligible Does not apply
259 Maynard Public Schools 2022-23 258 FALSE Caucasian M Active Not 504 Free Lunch Does not apply
260 Maynard Public Schools 2022-23 259 FALSE Caucasian M Not 504 Not Eligible Does not apply
261 Maynard Public Schools 2022-23 260 FALSE Caucasian F Not 504 Not Eligible Does not apply
262 Maynard Public Schools 2022-23 261 FALSE Caucasian M 504 Not Eligible Does not apply
263 Maynard Public Schools 2022-23 262 FALSE Caucasian M Not 504 Not Eligible Does not apply
264 Maynard Public Schools 2022-23 263 FALSE Caucasian M Not 504 Not Eligible Does not apply
265 Maynard Public Schools 2022-23 264 TRUE Caucasian F Not 504 Free Lunch Does not apply
266 Maynard Public Schools 2022-23 265 TRUE Caucasian F Free Lunch LEP student not 1st year
267 Maynard Public Schools 2022-23 266 TRUE Caucasian M Free Lunch LEP student 1st year
268 Maynard Public Schools 2022-23 267 FALSE Caucasian F Not 504 Free Lunch LEP student not 1st year
269 Maynard Public Schools 2022-23 268 TRUE Caucasian F Not Eligible
270 Maynard Public Schools 2022-23 269 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
271 Maynard Public Schools 2022-23 270 TRUE Caucasian M 504 Not Eligible Does not apply
272 Maynard Public Schools 2022-23 271 TRUE Caucasian M Not 504 Not Eligible Does not apply
273 Maynard Public Schools 2022-23 272 FALSE Caucasian F Not Eligible
274 Maynard Public Schools 2022-23 273 FALSE Caucasian F Not 504 Not Eligible Does not apply
275 Maynard Public Schools 2022-23 274 FALSE Caucasian M 504 Not Eligible Does not apply
276 Maynard Public Schools 2022-23 275 FALSE Black M Free Lunch
277 Maynard Public Schools 2022-23 276 FALSE Black F Free Lunch
278 Maynard Public Schools 2022-23 277 FALSE Black F Free Lunch
279 Maynard Public Schools 2022-23 278 FALSE Caucasian M Not Eligible
280 Maynard Public Schools 2022-23 279 FALSE Caucasian M Not 504 Not Eligible Does not apply
281 Maynard Public Schools 2022-23 280 FALSE Caucasian M 504 Not Eligible Does not apply
282 Maynard Public Schools 2022-23 281 FALSE Caucasian M Not 504 Not Eligible Does not apply
283 Maynard Public Schools 2022-23 282 TRUE Caucasian M Active Not 504 Not Eligible LEP student not 1st year
284 Maynard Public Schools 2022-23 283 TRUE Caucasian F Not 504 Not Eligible LEP student not 1st year
285 Maynard Public Schools 2022-23 284 TRUE Caucasian F Not 504 Not Eligible LEP student not 1st year
286 Maynard Public Schools 2022-23 285 TRUE Caucasian F Free Lunch
287 Maynard Public Schools 2022-23 286 TRUE Caucasian M 504 Free Lunch LEP student not 1st year
288 Maynard Public Schools 2022-23 287 FALSE Caucasian F Not 504 Not Eligible Does not apply
289 Maynard Public Schools 2022-23 288 FALSE Caucasian M Not 504 Not Eligible Does not apply
290 Maynard Public Schools 2022-23 289 FALSE Caucasian F Not 504 Not Eligible Does not apply
291 Maynard Public Schools 2022-23 290 FALSE Caucasian F Not 504 Not Eligible Does not apply
292 Maynard Public Schools 2022-23 291 FALSE Caucasian F Not 504 Not Eligible Does not apply
293 Maynard Public Schools 2022-23 292 FALSE Caucasian M Not 504 Not Eligible Does not apply
294 Maynard Public Schools 2022-23 293 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
295 Maynard Public Schools 2022-23 294 FALSE Caucasian M Active Not 504 Free Lunch
296 Maynard Public Schools 2022-23 295 FALSE Caucasian F Not Eligible
297 Maynard Public Schools 2022-23 296 FALSE Caucasian F Not 504 Not Eligible Does not apply
298 Maynard Public Schools 2022-23 297 FALSE Caucasian F Not Eligible
299 Maynard Public Schools 2022-23 298 FALSE Caucasian F Not Eligible
300 Maynard Public Schools 2022-23 299 FALSE Caucasian M Active 504 Not Eligible
301 Maynard Public Schools 2022-23 300 FALSE Caucasian M Not 504 Not Eligible Does not apply
302 Maynard Public Schools 2022-23 301 FALSE Caucasian F Not 504 Not Eligible Does not apply
303 Maynard Public Schools 2022-23 302 FALSE Caucasian M Not 504 Not Eligible Does not apply
304 Maynard Public Schools 2022-23 303 FALSE Asian, Black M Not Eligible
305 Maynard Public Schools 2022-23 304 FALSE Caucasian F Not Eligible
306 Maynard Public Schools 2022-23 305 FALSE Caucasian M Not 504 Not Eligible Does not apply
307 Maynard Public Schools 2022-23 306 FALSE Caucasian F 504 Not Eligible Does not apply
308 Maynard Public Schools 2022-23 307 FALSE Caucasian F Not 504 Not Eligible Does not apply
309 Maynard Public Schools 2022-23 308 FALSE Caucasian M Not Eligible
310 Maynard Public Schools 2022-23 309 TRUE Caucasian M Not 504 Free Lunch Does not apply
311 Maynard Public Schools 2022-23 310 TRUE Caucasian F Free Lunch LEP student not 1st year
312 Maynard Public Schools 2022-23 311 TRUE Caucasian F Free Lunch LEP student 1st year
313 Maynard Public Schools 2022-23 312 TRUE Caucasian M 504 Free Lunch Does not apply
314 Maynard Public Schools 2022-23 313 TRUE Caucasian M Active Not 504 Free Lunch LEP student not 1st year
315 Maynard Public Schools 2022-23 314 FALSE Caucasian F Not Eligible
316 Maynard Public Schools 2022-23 315 FALSE Caucasian M Active Not 504 Not Eligible
317 Maynard Public Schools 2022-23 316 FALSE Caucasian M Exited Not 504 Not Eligible Does not apply
318 Maynard Public Schools 2022-23 317 FALSE Caucasian M Not 504 Not Eligible Does not apply
319 Maynard Public Schools 2022-23 318 FALSE Caucasian F Not Eligible
320 Maynard Public Schools 2022-23 319 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
321 Maynard Public Schools 2022-23 320 FALSE Caucasian F Not 504 Not Eligible Does not apply
322 Maynard Public Schools 2022-23 321 FALSE Caucasian M Not 504 Not Eligible Does not apply
323 Maynard Public Schools 2022-23 322 FALSE Caucasian M Not 504 Not Eligible Does not apply
324 Maynard Public Schools 2022-23 323 FALSE Caucasian F Not 504 Not Eligible Does not apply
325 Maynard Public Schools 2022-23 324 FALSE Caucasian M 504 Not Eligible Does not apply
326 Maynard Public Schools 2022-23 325 FALSE Caucasian F 504 Not Eligible Does not apply
327 Maynard Public Schools 2022-23 326 FALSE Caucasian F 504 Not Eligible Does not apply
328 Maynard Public Schools 2022-23 327 TRUE Caucasian M Not 504 Not Eligible Does not apply
329 Maynard Public Schools 2022-23 328 TRUE Caucasian M Free Lunch LEP student not 1st year
330 Maynard Public Schools 2022-23 329 TRUE Caucasian F Free Lunch LEP student not 1st year
331 Maynard Public Schools 2022-23 330 FALSE Caucasian F Not 504 Free Lunch Does not apply
332 Maynard Public Schools 2022-23 331 FALSE M Not Eligible
333 Maynard Public Schools 2022-23 332 FALSE Caucasian M Not Eligible
334 Maynard Public Schools 2022-23 333 FALSE Caucasian M Not 504 Free Lunch Does not apply
335 Maynard Public Schools 2022-23 334 FALSE Caucasian F Not 504 Free Lunch Does not apply
336 Maynard Public Schools 2022-23 335 FALSE Caucasian F Not 504 Not Eligible Does not apply
337 Maynard Public Schools 2022-23 336 FALSE Caucasian M Not 504 Free Lunch Does not apply
338 Maynard Public Schools 2022-23 337 FALSE Native American M Active Not Eligible
339 Maynard Public Schools 2022-23 338 TRUE Caucasian M Active Not 504 Not Eligible Does not apply
340 Maynard Public Schools 2022-23 339 FALSE Black, Caucasian F Not Eligible
341 Maynard Public Schools 2022-23 340 FALSE Black, Caucasian F Not 504 Not Eligible Does not apply
342 Maynard Public Schools 2022-23 341 TRUE Caucasian F Not 504 Not Eligible Does not apply
343 Maynard Public Schools 2022-23 342 TRUE Caucasian F Not 504 Not Eligible Does not apply
344 Maynard Public Schools 2022-23 343 FALSE Caucasian F Not 504 Free Lunch Does not apply
345 Maynard Public Schools 2022-23 344 FALSE Caucasian M Not 504 Not Eligible Does not apply
346 Maynard Public Schools 2022-23 345 FALSE Caucasian F Not 504 Free Lunch Does not apply
347 Maynard Public Schools 2022-23 346 FALSE Caucasian F 504 Not Eligible Does not apply
348 Maynard Public Schools 2022-23 347 FALSE Caucasian F Not 504 Not Eligible Does not apply
349 Maynard Public Schools 2022-23 348 FALSE Caucasian F 504 Not Eligible Does not apply
350 Maynard Public Schools 2022-23 349 TRUE Caucasian F Not 504 Free Lunch Does not apply
351 Maynard Public Schools 2022-23 350 TRUE Caucasian F Not 504 Not Eligible Does not apply
352 Maynard Public Schools 2022-23 351 TRUE Caucasian M Not 504 Not Eligible Does not apply
353 Maynard Public Schools 2022-23 352 FALSE Caucasian M Free Lunch
354 Maynard Public Schools 2022-23 353 FALSE Caucasian F Not 504 Free Lunch Does not apply
355 Maynard Public Schools 2022-23 354 FALSE Caucasian F Not 504 Not Eligible Does not apply
356 Maynard Public Schools 2022-23 355 FALSE Caucasian M Not 504 Not Eligible Does not apply
357 Maynard Public Schools 2022-23 356 FALSE Caucasian F 504 Not Eligible Does not apply
358 Maynard Public Schools 2022-23 357 FALSE Caucasian M 504 Free Lunch Does not apply
359 Maynard Public Schools 2022-23 358 FALSE Caucasian F Not 504 Not Eligible Does not apply
360 Maynard Public Schools 2022-23 359 FALSE Caucasian F Not Eligible
361 Maynard Public Schools 2022-23 360 FALSE Caucasian M Not Eligible
362 Maynard Public Schools 2022-23 361 FALSE Caucasian F Not 504 Not Eligible Does not apply
363 Maynard Public Schools 2022-23 362 FALSE Caucasian F Not 504 Not Eligible Does not apply
364 Maynard Public Schools 2022-23 363 FALSE Caucasian M Not Eligible
365 Maynard Public Schools 2022-23 364 FALSE Caucasian F Not Eligible
366 Maynard Public Schools 2022-23 365 FALSE Black M Not 504 Not Eligible Does not apply
367 Maynard Public Schools 2022-23 366 FALSE Caucasian M Not 504 Not Eligible Does not apply
368 Maynard Public Schools 2022-23 367 FALSE Caucasian M 504 Not Eligible
369 Maynard Public Schools 2022-23 368 FALSE Caucasian M Active Not 504 Free Lunch Does not apply
370 Maynard Public Schools 2022-23 369 FALSE Caucasian F Not 504 Free Lunch Does not apply
371 Maynard Public Schools 2022-23 370 FALSE Caucasian M Free Lunch
372 Maynard Public Schools 2022-23 371 FALSE Caucasian M Not Eligible
373 Maynard Public Schools 2022-23 372 FALSE Caucasian M Not 504 Free Lunch Does not apply
374 Maynard Public Schools 2022-23 373 FALSE Asian,Caucasian F Active Not 504 Not Eligible Does not apply
375 Maynard Public Schools 2022-23 374 FALSE Caucasian M Free Lunch
376 Maynard Public Schools 2022-23 375 FALSE Caucasian F Not 504 Free Lunch Does not apply
377 Maynard Public Schools 2022-23 376 FALSE Caucasian M Not Eligible
378 Maynard Public Schools 2022-23 377 FALSE Caucasian F Not 504 Not Eligible Does not apply
379 Maynard Public Schools 2022-23 378 FALSE Caucasian F Active Not 504 Not Eligible
380 Maynard Public Schools 2022-23 379 FALSE Caucasian F Exited Not 504 Not Eligible Does not apply
381 Maynard Public Schools 2022-23 380 FALSE Caucasian F Not 504 Free Lunch Does not apply
382 Maynard Public Schools 2022-23 381 FALSE Caucasian F Active Not 504 Free Lunch Does not apply
383 Maynard Public Schools 2022-23 382 FALSE Caucasian F Not 504 Free Lunch Does not apply
384 Maynard Public Schools 2022-23 383 TRUE Black F Active Not 504 Free Lunch LEP student not 1st year
385 Maynard Public Schools 2022-23 384 TRUE Caucasian M Active Not 504 Not Eligible Does not apply
386 Maynard Public Schools 2022-23 385 FALSE Caucasian M Not 504 Not Eligible Does not apply
387 Maynard Public Schools 2022-23 386 FALSE Caucasian F Not 504 Not Eligible Does not apply
388 Maynard Public Schools 2022-23 387 FALSE Caucasian M Not 504 Not Eligible Does not apply
389 Maynard Public Schools 2022-23 388 FALSE Caucasian M Not Eligible
390 Maynard Public Schools 2022-23 389 FALSE Caucasian F Not 504 Not Eligible Does not apply
391 Maynard Public Schools 2022-23 390 FALSE Caucasian F Not 504 Not Eligible Does not apply
392 Maynard Public Schools 2022-23 391 FALSE Caucasian M Not 504 Free Lunch Does not apply
393 Maynard Public Schools 2022-23 392 FALSE Caucasian M Not 504 Free Lunch Does not apply
394 Maynard Public Schools 2022-23 393 FALSE Caucasian,Native American F Not 504 Not Eligible Does not apply
395 Maynard Public Schools 2022-23 394 FALSE Caucasian F Not Eligible
396 Maynard Public Schools 2022-23 395 FALSE Caucasian F Not Eligible
397 Maynard Public Schools 2022-23 396 FALSE Caucasian M Not 504 Not Eligible Does not apply
398 Maynard Public Schools 2022-23 397 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
399 Maynard Public Schools 2022-23 398 FALSE Caucasian M Not Eligible
400 Maynard Public Schools 2022-23 399 FALSE Caucasian,Native American M 504 Free Lunch Does not apply
401 Maynard Public Schools 2022-23 400 FALSE Caucasian M Not 504 Not Eligible Does not apply
402 Maynard Public Schools 2022-23 401 FALSE Caucasian F Not 504 Not Eligible Does not apply
403 Maynard Public Schools 2022-23 402 FALSE Caucasian F Not 504 Reduced Lunch Does not apply
404 Maynard Public Schools 2022-23 403 FALSE Caucasian M Not 504 Reduced Lunch Does not apply
405 Maynard Public Schools 2022-23 404 FALSE Caucasian F Not 504 Not Eligible Does not apply
406 Maynard Public Schools 2022-23 405 FALSE Caucasian M Not 504 Not Eligible Does not apply
407 Maynard Public Schools 2022-23 406 FALSE Caucasian M Not Eligible
408 Maynard Public Schools 2022-23 407 FALSE Caucasian F Not 504 Not Eligible Does not apply
409 Maynard Public Schools 2022-23 408 FALSE Caucasian M Not 504 Not Eligible Does not apply
410 Maynard Public Schools 2022-23 409 FALSE Caucasian M Not 504 Not Eligible Does not apply
411 Maynard Public Schools 2022-23 410 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
412 Maynard Public Schools 2022-23 411 FALSE Caucasian M Not 504 Not Eligible Does not apply
413 Maynard Public Schools 2022-23 412 TRUE Native American F Not Eligible
414 Maynard Public Schools 2022-23 413 TRUE Native American F 504 Not Eligible
415 Maynard Public Schools 2022-23 414 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
416 Maynard Public Schools 2022-23 415 FALSE Asian M Not 504 Not Eligible
417 Maynard Public Schools 2022-23 416 TRUE Native American F Not Eligible
418 Maynard Public Schools 2022-23 417 FALSE Caucasian F Not Eligible
419 Maynard Public Schools 2022-23 418 TRUE Caucasian M Not Eligible
420 Maynard Public Schools 2022-23 419 FALSE Asian M Exited Not 504 Not Eligible LEP student not 1st year
421 Maynard Public Schools 2022-23 420 FALSE Asian F Not 504 Not Eligible Does not apply
422 Maynard Public Schools 2022-23 421 FALSE Caucasian F Not Eligible
423 Maynard Public Schools 2022-23 422 FALSE Asian,Caucasian F Not 504 Not Eligible Does not apply
424 Maynard Public Schools 2022-23 423 FALSE Caucasian F Not 504 Not Eligible Does not apply
425 Maynard Public Schools 2022-23 424 FALSE Caucasian F Not 504 Not Eligible Does not apply
426 Maynard Public Schools 2022-23 425 FALSE Caucasian M Active Not 504 Free Lunch
427 Maynard Public Schools 2022-23 426 FALSE Caucasian F Not 504 Not Eligible Does not apply
428 Maynard Public Schools 2022-23 427 FALSE Caucasian M Not 504 Not Eligible Does not apply
429 Maynard Public Schools 2022-23 428 FALSE Caucasian F Free Lunch Does not apply
430 Maynard Public Schools 2022-23 429 FALSE Caucasian M Not 504 Not Eligible Does not apply
431 Maynard Public Schools 2022-23 430 FALSE Caucasian F Not Eligible
432 Maynard Public Schools 2022-23 431 TRUE Caucasian M Not 504 Free Lunch Does not apply
433 Maynard Public Schools 2022-23 432 TRUE Caucasian M Free Lunch LEP student 1st year
434 Maynard Public Schools 2022-23 433 TRUE Native American M Not 504 Not Eligible Does not apply
435 Maynard Public Schools 2022-23 434 TRUE Native American M Not 504 Not Eligible Does not apply
436 Maynard Public Schools 2022-23 435 TRUE Caucasian F Free Lunch
437 Maynard Public Schools 2022-23 436 TRUE Caucasian M Active Not 504 Free Lunch
438 Maynard Public Schools 2022-23 437 TRUE Caucasian M Not Eligible Does not apply
439 Maynard Public Schools 2022-23 438 TRUE Caucasian M Not 504 Not Eligible LEP student not 1st year
440 Maynard Public Schools 2022-23 439 FALSE Caucasian F Not 504 Not Eligible Does not apply
441 Maynard Public Schools 2022-23 440 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
442 Maynard Public Schools 2022-23 441 FALSE Caucasian F Not 504 Not Eligible Does not apply
443 Maynard Public Schools 2022-23 442 FALSE Caucasian F Not 504 Not Eligible Does not apply
444 Maynard Public Schools 2022-23 443 FALSE Caucasian F Not Eligible
445 Maynard Public Schools 2022-23 444 FALSE Caucasian F Not 504 Not Eligible Does not apply
446 Maynard Public Schools 2022-23 445 FALSE Caucasian F Not 504 Not Eligible Does not apply
447 Maynard Public Schools 2022-23 446 FALSE Caucasian M Not 504 Not Eligible Does not apply
448 Maynard Public Schools 2022-23 447 FALSE Caucasian M Not 504 Not Eligible Does not apply
449 Maynard Public Schools 2022-23 448 FALSE Caucasian F Not 504 Not Eligible Does not apply
450 Maynard Public Schools 2022-23 449 FALSE Caucasian M 504 Not Eligible Does not apply
451 Maynard Public Schools 2022-23 450 FALSE Caucasian M Not 504 Not Eligible Does not apply
452 Maynard Public Schools 2022-23 451 TRUE Caucasian F Free Lunch LEP student 1st year
453 Maynard Public Schools 2022-23 452 FALSE Caucasian F Not 504 Not Eligible Does not apply
454 Maynard Public Schools 2022-23 453 FALSE Caucasian M Not 504 Not Eligible Does not apply
455 Maynard Public Schools 2022-23 454 FALSE Caucasian M 504 Not Eligible Does not apply
456 Maynard Public Schools 2022-23 455 FALSE Caucasian F Not 504 Not Eligible Does not apply
457 Maynard Public Schools 2022-23 456 FALSE Caucasian,Pacific Island F Active Not 504 Not Eligible Does not apply
458 Maynard Public Schools 2022-23 457 FALSE Black M Free Lunch
459 Maynard Public Schools 2022-23 458 FALSE Caucasian M Not 504 Not Eligible Does not apply
460 Maynard Public Schools 2022-23 459 FALSE Caucasian M Not Eligible
461 Maynard Public Schools 2022-23 460 FALSE Caucasian F Not Eligible
462 Maynard Public Schools 2022-23 461 FALSE Caucasian F Not Eligible
463 Maynard Public Schools 2022-23 462 TRUE Caucasian F Free Lunch
464 Maynard Public Schools 2022-23 463 FALSE Caucasian F Not 504 Free Lunch Does not apply
465 Maynard Public Schools 2022-23 464 FALSE Caucasian F Not 504 Free Lunch Does not apply
466 Maynard Public Schools 2022-23 465 FALSE Caucasian F Not Eligible Does not apply
467 Maynard Public Schools 2022-23 466 FALSE Caucasian M Not Eligible
468 Maynard Public Schools 2022-23 467 FALSE Caucasian F Not Eligible
469 Maynard Public Schools 2022-23 468 FALSE Caucasian F Not Eligible
470 Maynard Public Schools 2022-23 469 FALSE Caucasian M Not Eligible
471 Maynard Public Schools 2022-23 470 FALSE Caucasian F Not Eligible
472 Maynard Public Schools 2022-23 471 FALSE Caucasian N Not 504 Not Eligible Does not apply
473 Maynard Public Schools 2022-23 472 FALSE Caucasian F Not Eligible
474 Maynard Public Schools 2022-23 473 FALSE Caucasian M Not 504 Not Eligible Does not apply
475 Maynard Public Schools 2022-23 474 FALSE Caucasian M Not 504 Not Eligible Does not apply
476 Maynard Public Schools 2022-23 475 FALSE Caucasian M Not 504 Not Eligible Does not apply
477 Maynard Public Schools 2022-23 476 FALSE Caucasian M Not 504 Not Eligible Does not apply
478 Maynard Public Schools 2022-23 477 TRUE Caucasian M Not Eligible
479 Maynard Public Schools 2022-23 478 TRUE Caucasian M Not 504 Not Eligible Does not apply
480 Maynard Public Schools 2022-23 479 FALSE Caucasian M Not 504 Not Eligible Does not apply
481 Maynard Public Schools 2022-23 480 FALSE Caucasian F Not 504 Not Eligible Does not apply
482 Maynard Public Schools 2022-23 481 FALSE Caucasian M Not 504 Not Eligible Does not apply
483 Maynard Public Schools 2022-23 482 FALSE Caucasian M Not 504 Not Eligible Does not apply
484 Maynard Public Schools 2022-23 483 FALSE Caucasian M Not 504 Not Eligible Does not apply
485 Maynard Public Schools 2022-23 484 FALSE Caucasian M Active Not 504 Not Eligible Does not apply
486 Maynard Public Schools 2022-23 485 FALSE Caucasian M Not Eligible
487 Maynard Public Schools 2022-23 486 FALSE Caucasian F Not 504 Not Eligible Does not apply
488 Maynard Public Schools 2022-23 487 FALSE Caucasian F 504 Free Lunch Does not apply
489 Maynard Public Schools 2022-23 488 TRUE Caucasian M Not Eligible LEP student not 1st year
490 Maynard Public Schools 2022-23 489 FALSE Caucasian F Exited Not 504 Not Eligible Does not apply
491 Maynard Public Schools 2022-23 490 FALSE Caucasian M Active Not 504 Free Lunch
492 Maynard Public Schools 2022-23 491 FALSE Caucasian F Not 504 Not Eligible Does not apply
493 Maynard Public Schools 2022-23 492 FALSE Caucasian F 504 Not Eligible Does not apply
494 Maynard Public Schools 2022-23 493 FALSE Caucasian M Not 504 Not Eligible Does not apply
495 Maynard Public Schools 2022-23 494 TRUE Caucasian M Not 504 Not Eligible Does not apply
496 Maynard Public Schools 2022-23 495 TRUE Caucasian M Active Not 504 Free Lunch
497 Maynard Public Schools 2022-23 496 FALSE Caucasian F Not 504 Not Eligible Does not apply
498 Maynard Public Schools 2022-23 497 TRUE Caucasian F Not Eligible
499 Maynard Public Schools 2022-23 498 FALSE Caucasian M Not 504 Not Eligible Does not apply
500 Maynard Public Schools 2022-23 499 FALSE Caucasian F Not 504 Not Eligible Does not apply
501 Maynard Public Schools 2022-23 500 FALSE Caucasian F Not 504 Not Eligible Does not apply

@ -0,0 +1,36 @@
StartDate,EndDate,Status,IPAddress,Progress,Duration (in seconds),Finished,RecordedDate,ResponseId,District,School,LASID,Gender,Race,What grade are you in?,s-emsa-q1,s-emsa-q2,s-emsa-q3,s-tint-q1,s-tint-q2,s-tint-q3,s-tint-q4,s-tint-q5,s-acpr-q1,s-acpr-q2,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
2023-03-17 7:57:47,2023-03-17 8:09:15,0,71.174.81.214,100,1000,1,2023-03-17T8:9:15,1000,2,1740505,1,2,4,9,3,5,5,,,,,,,,,,,,,,,,,,,,,,,,,4,4,3,5,,,,,,,,,,4,4,2,3,2,5,5,5,5,4,2,2,4,3,2,3,3,5,4,4,3,5,2,3,3,4,4,4,1,2,5,5,,,,,4,4,4,3,4,5
2023-03-17 8:02:15,2023-03-17 8:08:02,0,71.174.81.214,25,1000,1,2023-03-17T8:8:3,1001,2,1740505,2,1,5,10,,,,,,,,,,,,,,,,,,,,5,4,4,4,,,,,,,,,2,3,2,,,,,,,4,3,2,4,3,5,5,4,4,4,4,3,5,3,4,3,2,4,3,4,3,3,1,2,2,2,3,,,,,,5,4,4,5,4,4,5,3,3,4
2023-03-17 8:00:05,2023-03-17 8:07:39,0,71.174.81.214,24,1000,1,2023-03-17T8:7:39,1002,2,1740505,3,,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,4,4,5,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2023-03-17 8:03:35,2023-03-17 8:15:38,0,71.174.81.214,0,1000,1,2023-03-17T8:15:38,1003,2,1740505,4,1,"1,4",10,4,5,5,,,,,,,,,,3,4,5,5,4,4,3,5,4,3,4,4,3,3,3,2,1,3,2,3,1,1,,,,,,,,,,,,,,,,,,,,,,,5,4,3,5,3,4,3,3,3,5,4,5,4,4,5,5,5,5,5,5,,,,,,
2023-03-17 7:57:09,2023-03-17 8:12:26,0,71.174.81.214,,1000,1,2023-03-17T8:12:27,1004,2,1740505,5,1,"5,4",9,4,4,5,,,,,,,,,,,,,,,,,,,,,,,,,4,4,2,4,,,,,,,,,,3,3,3,3,3,5,5,4,5,5,3,3,4,2,2,1,3,2,4,5,4,5,4,3,1,3,4,3,1,3,3,1,,,,,4,4,3,4,2,4
2023-03-17 8:01:50,2023-03-17 8:17:51,0,71.174.81.214,100,240,1,2023-03-17T8:17:52,1005,2,1740505,6,1,"5,4",9,4,3,4,,,,,,,,,,4,4,3,3,3,4,4,4,5,2,5,4,4,4,4,4,4,5,3,4,3,5,,,,,,,4,4,4,5,2,,,,,,,,,,,,,,,,,,,,,,,4,1,4,4,4,5,4,4,5,4,5,5,5,5,4
2023-03-17 8:01:45,2023-03-17 8:07:59,0,71.174.81.214,100,239,1,2023-03-17T8:8:0,1006,2,1740505,7,1,5,10,,,,,,,,,,,,,5,3,3,5,2,3,3,,,,,4,4,4,4,,,,,,,,,,,,,,4,5,3,4,3,5,5,4,5,5,4,4,4,2,1,1,4,5,4,4,3,4,2,2,3,2,3,,,,,,,,,,4,4,5,4,4,5
2023-03-17 9:07:09,2023-03-17 9:20:10,0,71.174.81.214,100,0,1,2023-03-17T9:20:11,1007,2,1740305,8,2,5,7,,,,,,,,,,,,,4,5,5,4,,4,3,,,,,5,4,4,5,,,,,,,,,,,,,,5,5,4,5,5,5,5,5,5,4,5,5,5,3,2,3,3,3,5,4,4,4,3,3,4,,4,,,,,,,,,,4,5,5,5,5,5
2023-03-17 8:02:11,2023-03-17 8:29:53,0,71.174.81.214,100,,1,2023-03-17T8:29:53,1008,2,1740505,9,1,"5,4",10,,,,,,,,,,,,,,,,,,,,3,3,2,3,,,,,,,,,2,3,3,,,,,,,3,4,3,3,3,5,5,5,5,4,4,2,3,2,3,2,2,5,2,4,3,3,1,3,3,3,4,,,,,,4,4,4,4,4,4,4,4,2,4
2023-03-17 8:00:42,2023-03-17 8:12:00,0,71.174.81.214,100,1000,1,2023-03-17T8:12:0,1009,2,1740505,10,2,5,1,,,,,,,,,,,,,1,4,3,2,1,3,2,,,,,3,3,4,4,,,,,,,,,,,,,,5,5,2,4,5,4,4,5,4,2,2,1,2,2,4,3,5,5,4,4,1,4,1,2,1,3,3,,,,,,,,,,3,4,3,1,1,1
2023-03-17 8:03:09,2023-03-17 8:13:27,0,71.174.81.214,100,1000,1,2023-03-17T8:13:28,1010,2,1740505,11,1,5,2,,,,,,,,,,,,,5,3,2,4,2,2,3,,,,,4,5,5,5,,,,,,,,,,,,,,4,4,3,4,4,4,4,4,5,4,3,5,4,2,1,2,4,4,5,4,3,5,4,1,3,3,3,,,,,,,,,,4,3,4,4,4,4
2023-03-17 8:23:20,2023-03-17 8:34:00,0,71.174.81.214,100,1000,1,2023-03-17T8:34:0,1011,2,1740505,12,2,3,3,1,2,2,2,3,2,4,2,5,5,3,5,3,3,3,2,3,4,4,2,4,3,5,4,4,4,3,4,3,3,4,3,1,3,,,,,,,,,,,,,,,,,,,,,,,1,2,4,4,3,4,3,2,2,4,4,,,,,,,,,,,,,,,
2023-03-17 8:36:36,2023-03-17 8:47:33,0,71.174.81.214,100,1000,1,2023-03-17T8:47:34,1012,2,1740505,13,1,3,4,4,5,4,,,,,,,,,,4,2,3,2,2,3,4,4,5,3,4,2,4,2,3,3,4,3,3,2,1,1,,,,,,,,,,,,5,5,2,4,2,3,3,4,5,4,5,,,,,,,,,,,,4,2,3,3,2,4,4,3,3,,,,,,
2023-03-17 8:01:10,2023-03-17 8:09:17,0,71.174.81.214,100,1000,1,2023-03-17T8:9:18,1013,2,1740505,14,1,4,5,4,3,5,,,,,,,,,,3,4,3,4,3,4,4,2,4,4,2,3,1,2,2,4,2,3,5,2,2,1,,,,,,,4,4,3,3,4,,,,,,,,,,,,,,,,,,,,,,,2,1,3,2,5,4,5,2,2,2,3,4,3,3,4
2023-03-17 10:06:07,2023-03-17 10:12:54,0,71.174.81.214,100,1000,1,2023-03-17T10:12:56,1014,2,1740505,15,1,5,6,,,,,,,,,,,,,3,3,4,2,1,2,4,,,,,2,2,2,2,,,,,,,,,,,,,,1,2,1,2,3,4,5,3,5,3,1,2,3,2,2,1,2,4,3,2,3,2,4,2,3,2,2,,,,,,,,,,4,4,4,4,4,5
2023-03-17 7:57:13,2023-03-17 8:05:02,0,71.174.81.214,100,1000,1,2023-03-17T8:5:2,1015,2,1740505,16,4,5,7,,,,,,,,,,,,,,,,,,,,3,3,3,3,,,,,,,,,3,5,2,,,,,,,2,1,3,2,4,4,4,3,3,2,2,3,4,3,5,5,5,4,3,4,2,4,5,3,1,3,2,,,,,,4,4,3,4,4,5,4,5,5,5
2023-03-17 7:57:50,2023-03-17 8:02:53,0,71.174.81.214,100,1000,1,2023-03-17T8:2:54,1016,2,1740505,17,2,5,8,1,1,1,,,,,,,,,,,,,,,,,,,,,,,,,5,2,1,1,,,,,,,,,,5,4,3,3,3,2,4,3,3,5,1,1,1,1,3,5,1,1,4,5,4,1,3,1,3,2,1,1,1,1,1,1,,,,,1,1,1,1,1,2
2023-03-17 8:40:22,2023-03-17 8:53:19,0,71.174.81.214,100,1000,0,2023-03-18T8:53:20,1017,2,1740505,18,2,5,9,,,,,,,,,,,,,,,,,3,3,4,,,,,1,1,1,2,4,3,2,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,2,2,1,3,2,3,2,4,,,,,,
2023-03-17 8:37:13,2023-03-17 8:43:34,0,71.174.81.214,100,1000,1,2023-03-17T8:43:35,1018,2,1740505,19,2,2,10,,,,,,,,,,,,,,,,,,,,3,3,2,3,,,,,,,,,2,2,1,,,,,,,4,4,4,4,4,4,5,4,5,3,4,3,3,4,5,4,,,3,2,3,4,1,4,2,3,4,,,,,,4,5,5,4,4,4,5,4,3,4
2023-03-17 8:36:27,2023-03-17 8:44:07,0,71.174.81.214,100,1000,1,2023-03-17T8:44:8,1019,2,1740505,20,1,2,11,3,4,3,,,,,,,,,,2,3,3,2,4,5,4,3,4,3,5,3,4,4,5,4,5,3,4,5,4,4,,,,,,,3,4,3,4,3,,,,,,,,,,,,,,,,,,,,,,,4,2,,2,4,4,5,2,4,5,5,4,3,3,4
2023-03-17 8:33:55,2023-03-17 8:43:13,0,71.174.81.214,100,1000,1,2023-03-17T8:43:14,1020,2,1740505,21,1,5,12,3,1,3,,,,,,,,,,,,,,2,3,3,,4,,4,,4,,4,4,3,4,3,1,5,2,,,,,,,3,4,2,3,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,4,5,5,5,4,,4,4,4
2023-03-17 8:50:49,2023-03-17 9:13:18,0,71.174.81.214,100,1000,1,2023-03-17T9:13:19,1021,2,1740305,22,2,"5,2",1,,,,,,,,,,,,,2,3,3,3,2,3,3,,,,,3,4,4,4,,,,,,,,,,,,,,2,2,1,3,2,5,5,4,5,4,2,2,2,1,1,1,1,3,3,4,3,4,4,1,1,1,3,,,,,,,,,,4,4,4,4,3,4
2023-03-17 7:57:37,2023-03-17 8:04:25,0,71.174.81.214,100,1000,1,2023-03-17T8:4:25,1022,2,1740305,23,1,5,2,,,,,,,,,,,,,,,,,,,,4,4,4,5,,,,,,,,,4,4,5,,,,,,,3,3,4,4,4,4,5,4,5,4,3,3,3,2,2,2,3,3,3,2,3,4,4,3,3,2,4,,,,,,3,2,4,3,3,3,3,4,2,2
2023-03-17 8:01:47,2023-03-17 8:08:39,0,71.174.81.214,100,1000,1,2023-03-17T8:8:39,1023,2,1740305,24,1,"2,4",3,4,3,5,,,,,,,,,,2,2,2,2,1,2,2,4,3,2,3,2,3,3,4,3,4,2,3,4,3,2,,,,,,,,,,,,5,4,3,5,3,1,2,2,2,2,4,,,,,,,,,,,,1,2,4,2,2,5,4,1,4,,,,,,
2023-03-17 8:37:21,2023-03-17 8:58:16,0,71.174.81.214,100,1000,1,2023-03-17T8:58:16,1024,2,1740305,25,1,5,4,3,3,3,,,,,,,,,,,,,,,,,,,,,,,,,4,4,2,3,,,,,,,,,,3,4,4,3,2,5,5,4,5,3,3,2,3,4,3,3,2,4,4,3,3,3,2,2,3,3,4,3,3,3,3,3,,,,,4,3,2,3,3,3
2023-03-17 8:02:25,2023-03-17 8:11:16,0,71.174.81.214,100,1000,0,2023-03-18T8:11:21,1025,2,1740305,26,2,5,5,,,,,,,,,,,,,,,,,3,3,3,4,4,4,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,1,3,,,,,,4,3,3,3,3,,,,,,,,,,
2023-03-17 9:33:54,2023-03-17 9:57:21,0,71.174.81.214,100,1000,1,2023-03-17T9:57:22,1026,2,1740305,27,2,5,6,,,,,,,,,5,5,4,5,,,,,,,,1,2,2,2,,,,,,,,,1,1,2,5,4,4,4,3,5,5,4,,4,2,5,4,4,3,5,3,2,2,1,1,5,3,2,2,3,1,1,1,2,1,3,3,,,,,,,,,,,,,,,
2023-03-17 9:48:38,2023-03-17 9:58:45,0,71.174.81.214,100,1000,1,2023-03-17T9:58:45,1027,2,1740305,28,1,5,7,,,,,,,,,2,4,4,2,,,,,,,,3,3,3,5,,,,,,,,,2,3,3,4,3,2,2,3,2,1,3,2,3,2,3,3,3,2,4,3,4,2,2,1,1,5,5,2,3,2,3,5,4,3,2,2,,,,,,,,,,,,,,,
2023-03-17 8:36:40,2023-03-17 8:43:21,0,71.174.81.214,100,1000,1,2023-03-17T8:43:22,1028,2,1740305,29,2,5,8,,,,,,,,,,,,,,,,,,,,3,3,2,3,,,,,,,,,4,3,4,,,,,,,3,3,2,4,3,5,4,4,5,4,4,2,3,4,2,5,4,4,3,3,3,3,2,2,3,1,4,,,,,,3,4,4,4,2,4,5,4,3,4
2023-03-17 9:40:56,2023-03-17 9:52:52,0,71.174.81.214,100,1000,1,2023-03-17T9:52:52,1029,2,1740305,30,2,5,9,3,4,3,5,3,5,5,5,5,4,4,5,4,4,4,4,2,3,2,4,4,3,4,3,4,3,5,5,5,4,3,5,4,4,,,,,,,,,,,,1,5,4,4,5,4,3,4,3,1,4,,,,,,,,,,,,,,,,,,,,,,,,,,
2023-03-17 9:33:58,2023-03-17 9:48:33,0,71.174.81.214,100,1000,1,2023-03-17T9:48:33,1030,2,1740305,31,2,5,10,,,,,,,,,4,3,5,2,,,,,,,,5,4,4,5,,,,,,,,,5,2,4,4,4,2,2,3,4,2,2,1,1,2,1,2,2,5,5,5,4,4,3,1,1,1,1,3,2,3,3,3,4,4,4,2,,,,,,,,,,,,,,,
2023-03-17 8:03:04,2023-03-17 8:23:33,0,71.174.81.214,100,1000,1,2023-03-17T8:23:33,1031,2,1740305,32,1,5,11,1,1,1,3,4,2,4,3,,,,,,,,,,,,,,,,,,,,5,5,5,5,,,,4,4,3,2,3,3,2,3,3,1,3,3,5,3,4,5,3,3,5,1,2,2,1,1,4,5,3,5,4,2,4,2,3,,,,,,,,,,,,,,,
2023-03-17 8:33:14,2023-03-17 8:41:01,0,71.174.81.214,100,1000,1,2023-03-17T8:41:2,1032,2,1740305,33,1,5,12,,,,,,,,,,,,,2,1,1,1,2,2,3,,,,,2,1,3,2,,,,,,,,,,,,,,1,1,1,1,1,4,3,4,5,4,1,1,2,3,2,3,4,2,2,3,3,2,2,2,1,2,3,,,,,,,,,,3,2,2,1,2,2
2023-03-17 7:57:06,2023-03-17 8:08:35,0,71.174.81.214,100,1000,1,2023-03-17T8:8:35,1033,2,1740505,34,2,5,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,2,2,2,2,2,2,2,2,2
2023-03-17 7:58:38,2023-03-17 8:12:04,0,71.174.81.214,100,1000,1,2023-03-17T8:12:5,1034,2,1740505,35,2,"5,4",12,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1 StartDate EndDate Status IPAddress Progress Duration (in seconds) Finished RecordedDate ResponseId District School LASID Gender Race What grade are you in? s-emsa-q1 s-emsa-q2 s-emsa-q3 s-tint-q1 s-tint-q2 s-tint-q3 s-tint-q4 s-tint-q5 s-acpr-q1 s-acpr-q2 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
2 2023-03-17 7:57:47 2023-03-17 8:09:15 0 71.174.81.214 100 1000 1 2023-03-17T8:9:15 1000 2 1740505 1 2 4 9 3 5 5 4 4 3 5 4 4 2 3 2 5 5 5 5 4 2 2 4 3 2 3 3 5 4 4 3 5 2 3 3 4 4 4 1 2 5 5 4 4 4 3 4 5
3 2023-03-17 8:02:15 2023-03-17 8:08:02 0 71.174.81.214 25 1000 1 2023-03-17T8:8:3 1001 2 1740505 2 1 5 10 5 4 4 4 2 3 2 4 3 2 4 3 5 5 4 4 4 4 3 5 3 4 3 2 4 3 4 3 3 1 2 2 2 3 5 4 4 5 4 4 5 3 3 4
4 2023-03-17 8:00:05 2023-03-17 8:07:39 0 71.174.81.214 24 1000 1 2023-03-17T8:7:39 1002 2 1740505 3 9 5 4 4 5 3
5 2023-03-17 8:03:35 2023-03-17 8:15:38 0 71.174.81.214 0 1000 1 2023-03-17T8:15:38 1003 2 1740505 4 1 1,4 10 4 5 5 3 4 5 5 4 4 3 5 4 3 4 4 3 3 3 2 1 3 2 3 1 1 5 4 3 5 3 4 3 3 3 5 4 5 4 4 5 5 5 5 5 5
6 2023-03-17 7:57:09 2023-03-17 8:12:26 0 71.174.81.214 1000 1 2023-03-17T8:12:27 1004 2 1740505 5 1 5,4 9 4 4 5 4 4 2 4 3 3 3 3 3 5 5 4 5 5 3 3 4 2 2 1 3 2 4 5 4 5 4 3 1 3 4 3 1 3 3 1 4 4 3 4 2 4
7 2023-03-17 8:01:50 2023-03-17 8:17:51 0 71.174.81.214 100 240 1 2023-03-17T8:17:52 1005 2 1740505 6 1 5,4 9 4 3 4 4 4 3 3 3 4 4 4 5 2 5 4 4 4 4 4 4 5 3 4 3 5 4 4 4 5 2 4 1 4 4 4 5 4 4 5 4 5 5 5 5 4
8 2023-03-17 8:01:45 2023-03-17 8:07:59 0 71.174.81.214 100 239 1 2023-03-17T8:8:0 1006 2 1740505 7 1 5 10 5 3 3 5 2 3 3 4 4 4 4 4 5 3 4 3 5 5 4 5 5 4 4 4 2 1 1 4 5 4 4 3 4 2 2 3 2 3 4 4 5 4 4 5
9 2023-03-17 9:07:09 2023-03-17 9:20:10 0 71.174.81.214 100 0 1 2023-03-17T9:20:11 1007 2 1740305 8 2 5 7 4 5 5 4 4 3 5 4 4 5 5 5 4 5 5 5 5 5 5 4 5 5 5 3 2 3 3 3 5 4 4 4 3 3 4 4 4 5 5 5 5 5
10 2023-03-17 8:02:11 2023-03-17 8:29:53 0 71.174.81.214 100 1 2023-03-17T8:29:53 1008 2 1740505 9 1 5,4 10 3 3 2 3 2 3 3 3 4 3 3 3 5 5 5 5 4 4 2 3 2 3 2 2 5 2 4 3 3 1 3 3 3 4 4 4 4 4 4 4 4 4 2 4
11 2023-03-17 8:00:42 2023-03-17 8:12:00 0 71.174.81.214 100 1000 1 2023-03-17T8:12:0 1009 2 1740505 10 2 5 1 1 4 3 2 1 3 2 3 3 4 4 5 5 2 4 5 4 4 5 4 2 2 1 2 2 4 3 5 5 4 4 1 4 1 2 1 3 3 3 4 3 1 1 1
12 2023-03-17 8:03:09 2023-03-17 8:13:27 0 71.174.81.214 100 1000 1 2023-03-17T8:13:28 1010 2 1740505 11 1 5 2 5 3 2 4 2 2 3 4 5 5 5 4 4 3 4 4 4 4 4 5 4 3 5 4 2 1 2 4 4 5 4 3 5 4 1 3 3 3 4 3 4 4 4 4
13 2023-03-17 8:23:20 2023-03-17 8:34:00 0 71.174.81.214 100 1000 1 2023-03-17T8:34:0 1011 2 1740505 12 2 3 3 1 2 2 2 3 2 4 2 5 5 3 5 3 3 3 2 3 4 4 2 4 3 5 4 4 4 3 4 3 3 4 3 1 3 1 2 4 4 3 4 3 2 2 4 4
14 2023-03-17 8:36:36 2023-03-17 8:47:33 0 71.174.81.214 100 1000 1 2023-03-17T8:47:34 1012 2 1740505 13 1 3 4 4 5 4 4 2 3 2 2 3 4 4 5 3 4 2 4 2 3 3 4 3 3 2 1 1 5 5 2 4 2 3 3 4 5 4 5 4 2 3 3 2 4 4 3 3
15 2023-03-17 8:01:10 2023-03-17 8:09:17 0 71.174.81.214 100 1000 1 2023-03-17T8:9:18 1013 2 1740505 14 1 4 5 4 3 5 3 4 3 4 3 4 4 2 4 4 2 3 1 2 2 4 2 3 5 2 2 1 4 4 3 3 4 2 1 3 2 5 4 5 2 2 2 3 4 3 3 4
16 2023-03-17 10:06:07 2023-03-17 10:12:54 0 71.174.81.214 100 1000 1 2023-03-17T10:12:56 1014 2 1740505 15 1 5 6 3 3 4 2 1 2 4 2 2 2 2 1 2 1 2 3 4 5 3 5 3 1 2 3 2 2 1 2 4 3 2 3 2 4 2 3 2 2 4 4 4 4 4 5
17 2023-03-17 7:57:13 2023-03-17 8:05:02 0 71.174.81.214 100 1000 1 2023-03-17T8:5:2 1015 2 1740505 16 4 5 7 3 3 3 3 3 5 2 2 1 3 2 4 4 4 3 3 2 2 3 4 3 5 5 5 4 3 4 2 4 5 3 1 3 2 4 4 3 4 4 5 4 5 5 5
18 2023-03-17 7:57:50 2023-03-17 8:02:53 0 71.174.81.214 100 1000 1 2023-03-17T8:2:54 1016 2 1740505 17 2 5 8 1 1 1 5 2 1 1 5 4 3 3 3 2 4 3 3 5 1 1 1 1 3 5 1 1 4 5 4 1 3 1 3 2 1 1 1 1 1 1 1 1 1 1 1 2
19 2023-03-17 8:40:22 2023-03-17 8:53:19 0 71.174.81.214 100 1000 0 2023-03-18T8:53:20 1017 2 1740505 18 2 5 9 3 3 4 1 1 1 2 4 3 2 4 2 2 2 1 3 2 3 2 4
20 2023-03-17 8:37:13 2023-03-17 8:43:34 0 71.174.81.214 100 1000 1 2023-03-17T8:43:35 1018 2 1740505 19 2 2 10 3 3 2 3 2 2 1 4 4 4 4 4 4 5 4 5 3 4 3 3 4 5 4 3 2 3 4 1 4 2 3 4 4 5 5 4 4 4 5 4 3 4
21 2023-03-17 8:36:27 2023-03-17 8:44:07 0 71.174.81.214 100 1000 1 2023-03-17T8:44:8 1019 2 1740505 20 1 2 11 3 4 3 2 3 3 2 4 5 4 3 4 3 5 3 4 4 5 4 5 3 4 5 4 4 3 4 3 4 3 4 2 2 4 4 5 2 4 5 5 4 3 3 4
22 2023-03-17 8:33:55 2023-03-17 8:43:13 0 71.174.81.214 100 1000 1 2023-03-17T8:43:14 1020 2 1740505 21 1 5 12 3 1 3 2 3 3 4 4 4 4 4 3 4 3 1 5 2 3 4 2 3 4 5 4 5 5 5 4 4 4 4
23 2023-03-17 8:50:49 2023-03-17 9:13:18 0 71.174.81.214 100 1000 1 2023-03-17T9:13:19 1021 2 1740305 22 2 5,2 1 2 3 3 3 2 3 3 3 4 4 4 2 2 1 3 2 5 5 4 5 4 2 2 2 1 1 1 1 3 3 4 3 4 4 1 1 1 3 4 4 4 4 3 4
24 2023-03-17 7:57:37 2023-03-17 8:04:25 0 71.174.81.214 100 1000 1 2023-03-17T8:4:25 1022 2 1740305 23 1 5 2 4 4 4 5 4 4 5 3 3 4 4 4 4 5 4 5 4 3 3 3 2 2 2 3 3 3 2 3 4 4 3 3 2 4 3 2 4 3 3 3 3 4 2 2
25 2023-03-17 8:01:47 2023-03-17 8:08:39 0 71.174.81.214 100 1000 1 2023-03-17T8:8:39 1023 2 1740305 24 1 2,4 3 4 3 5 2 2 2 2 1 2 2 4 3 2 3 2 3 3 4 3 4 2 3 4 3 2 5 4 3 5 3 1 2 2 2 2 4 1 2 4 2 2 5 4 1 4
26 2023-03-17 8:37:21 2023-03-17 8:58:16 0 71.174.81.214 100 1000 1 2023-03-17T8:58:16 1024 2 1740305 25 1 5 4 3 3 3 4 4 2 3 3 4 4 3 2 5 5 4 5 3 3 2 3 4 3 3 2 4 4 3 3 3 2 2 3 3 4 3 3 3 3 3 4 3 2 3 3 3
27 2023-03-17 8:02:25 2023-03-17 8:11:16 0 71.174.81.214 100 1000 0 2023-03-18T8:11:21 1025 2 1740305 26 2 5 5 3 3 3 4 4 4 4 3 1 3 4 3 3 3 3
28 2023-03-17 9:33:54 2023-03-17 9:57:21 0 71.174.81.214 100 1000 1 2023-03-17T9:57:22 1026 2 1740305 27 2 5 6 5 5 4 5 1 2 2 2 1 1 2 5 4 4 4 3 5 5 4 4 2 5 4 4 3 5 3 2 2 1 1 5 3 2 2 3 1 1 1 2 1 3 3
29 2023-03-17 9:48:38 2023-03-17 9:58:45 0 71.174.81.214 100 1000 1 2023-03-17T9:58:45 1027 2 1740305 28 1 5 7 2 4 4 2 3 3 3 5 2 3 3 4 3 2 2 3 2 1 3 2 3 2 3 3 3 2 4 3 4 2 2 1 1 5 5 2 3 2 3 5 4 3 2 2
30 2023-03-17 8:36:40 2023-03-17 8:43:21 0 71.174.81.214 100 1000 1 2023-03-17T8:43:22 1028 2 1740305 29 2 5 8 3 3 2 3 4 3 4 3 3 2 4 3 5 4 4 5 4 4 2 3 4 2 5 4 4 3 3 3 3 2 2 3 1 4 3 4 4 4 2 4 5 4 3 4
31 2023-03-17 9:40:56 2023-03-17 9:52:52 0 71.174.81.214 100 1000 1 2023-03-17T9:52:52 1029 2 1740305 30 2 5 9 3 4 3 5 3 5 5 5 5 4 4 5 4 4 4 4 2 3 2 4 4 3 4 3 4 3 5 5 5 4 3 5 4 4 1 5 4 4 5 4 3 4 3 1 4
32 2023-03-17 9:33:58 2023-03-17 9:48:33 0 71.174.81.214 100 1000 1 2023-03-17T9:48:33 1030 2 1740305 31 2 5 10 4 3 5 2 5 4 4 5 5 2 4 4 4 2 2 3 4 2 2 1 1 2 1 2 2 5 5 5 4 4 3 1 1 1 1 3 2 3 3 3 4 4 4 2
33 2023-03-17 8:03:04 2023-03-17 8:23:33 0 71.174.81.214 100 1000 1 2023-03-17T8:23:33 1031 2 1740305 32 1 5 11 1 1 1 3 4 2 4 3 5 5 5 5 4 4 3 2 3 3 2 3 3 1 3 3 5 3 4 5 3 3 5 1 2 2 1 1 4 5 3 5 4 2 4 2 3
34 2023-03-17 8:33:14 2023-03-17 8:41:01 0 71.174.81.214 100 1000 1 2023-03-17T8:41:2 1032 2 1740305 33 1 5 12 2 1 1 1 2 2 3 2 1 3 2 1 1 1 1 1 4 3 4 5 4 1 1 2 3 2 3 4 2 2 3 3 2 2 2 1 2 3 3 2 2 1 2 2
35 2023-03-17 7:57:06 2023-03-17 8:08:35 0 71.174.81.214 100 1000 1 2023-03-17T8:8:35 1033 2 1740505 34 2 5 9 2 2 2 2 2 2 2 2 2 2
36 2023-03-17 7:58:38 2023-03-17 8:12:04 0 71.174.81.214 100 1000 1 2023-03-17T8:12:5 1034 2 1740505 35 2 5,4 12

@ -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:37,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:38,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:37 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:38 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

@ -1,29 +1,46 @@
require 'rails_helper' require "rails_helper"
require 'fileutils' require "fileutils"
RSpec.describe Cleaner do RSpec.describe Cleaner do
let(:district) { create(:district, name: 'District1') } let(:district) { create(:district, name: "Maynard Public Schools") }
let(:second_district) { create(:district, name: 'District2') } let(:second_district) { create(:district, name: "District2") }
let(:school) { create(:school, dese_id: 1_740_505, district:) } let(:school) { create(:school, dese_id: 1_740_505, district:) }
let(:second_school) { create(:school, dese_id: 222_222, district: second_district) } let(:second_school) { create(:school, dese_id: 1_740_305, district:) }
let(:third_school) { create(:school, dese_id: 222_222, district: second_district) }
let(:academic_year) { create(:academic_year, range: '2022-23') } let(:academic_year) { create(:academic_year, range: "2022-23") }
let(:respondents) { create(:respondent, school:, academic_year:, nine: 40, ten: 40, eleven: 40, twelve: 40) } let(:respondents) do
let(:recorded_date) { '2023-04-01' } create(:respondent, school:, academic_year:, one: 0, nine: 40, ten: 40, eleven: 40, twelve: 40)
create(:respondent, school: second_school, academic_year:, one: 0, four: 40, five: 40, six: 40, seven: 40,
eight: 40)
end
let(:recorded_date) { "2023-04-01" }
let(:input_filepath) do let(:input_filepath) do
Rails.root.join('spec', 'fixtures', 'raw') Rails.root.join("spec", "fixtures", "raw")
end end
let(:output_filepath) do let(:output_filepath) do
Rails.root.join('tmp', 'spec', 'clean') Rails.root.join("tmp", "spec", "clean")
end end
let(:log_filepath) do let(:log_filepath) do
Rails.root.join('tmp', 'spec', 'removed') Rails.root.join("tmp", "spec", "removed")
end
let(:disaggregation_filepath) do
Rails.root.join("spec", "fixtures", "disaggregation")
end
let(:path_to_sample_disaggregation_file) do
File.open(Rails.root.join("spec", "fixtures", "disaggregation", "sample_maynard_disaggregation_data.csv"))
end
let(:path_to_sample_raw_file) do
File.open(Rails.root.join("spec", "fixtures", "raw", "sample_maynard_raw_student_survey.csv"))
end end
let(:common_headers) do let(:common_headers) do
['Recorded Date', 'Dese ID', 'ResponseID'] ["Recorded Date", "DeseID", "ResponseID"]
end end
let(:standard_survey_items) do let(:standard_survey_items) do
@ -41,16 +58,16 @@ RSpec.describe Cleaner do
end end
let(:short_form_survey_items) do let(:short_form_survey_items) do
([create(:survey_item, survey_item_id: 's-phys-q1', on_short_form: true), ([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) << common_headers).flatten on_short_form: true)].map(&:survey_item_id) << common_headers).flatten
end end
let(:early_education_survey_items) do let(:early_education_survey_items) do
([create(:survey_item, survey_item_id: 's-emsa-es1'), ([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) << common_headers).flatten create(:survey_item, survey_item_id: "s-emsa-es3")].map(&:survey_item_id) << common_headers).flatten
end end
let(:teacher_survey_items) do let(:teacher_survey_items) do
@ -71,6 +88,7 @@ RSpec.describe Cleaner do
before :each do before :each do
school school
second_school second_school
third_school
standard_survey_items standard_survey_items
short_form_survey_items short_form_survey_items
early_education_survey_items early_education_survey_items
@ -79,84 +97,231 @@ RSpec.describe Cleaner do
respondents respondents
end end
context 'Creating a new Cleaner' do context "Creating a new Cleaner" do
it 'creates a directory for the clean data' do it "creates a directory for the clean data" do
Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).clean
expect(output_filepath).to exist expect(output_filepath).to exist
end end
it 'creates a directory for the removed data' do it "creates a directory for the removed data" do
Cleaner.new(input_filepath:, output_filepath:, log_filepath:).clean Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).clean
expect(log_filepath).to exist expect(log_filepath).to exist
end end
end end
context '.filename' do context ".process_raw_file" do
context 'defines a filename in the format: [district].[early_ed/short_form/standard/teacher].[year as 2022-23]' do it "sorts data into valid and invalid csvs" do
context 'when the file is based on standard survey items' do cleaner = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:)
it 'adds the survey type as standard to the filename' do processed_data = cleaner.process_raw_file(
file: path_to_sample_raw_file, disaggregation_data: cleaner.disaggregation_data
)
processed_data in [headers, clean_csv, log_csv, data]
reads_headers_from_raw_csv(processed_data)
valid_rows = %w[1000 1001 1004 1005 1008 1017 1018 1019 1020 1024 1025 1026
1027 1028]
valid_rows.each do |response_id|
valid_row = data.find { |row| row.response_id == response_id }
expect(valid_row.valid?).to eq true
end
invalid_rows = %w[1002 1003 1006 1007 1009 1010 1011 1012 1013 1014 1015 1016 1021 1022 1023 1029 1030 1031 1032
1033 1034]
invalid_rows.each do |response_id|
invalid_row = data.find { |row| row.response_id == response_id }
expect(invalid_row.valid?).to eq false
end
expect(clean_csv.length).to eq valid_rows.length + 1 # headers + rows
expect(log_csv.length).to eq invalid_rows.length + 1 # headers + rows
csv_contains_the_correct_rows(clean_csv, valid_rows)
csv_contains_the_correct_rows(log_csv, invalid_rows)
invalid_rows_are_rejected_for_the_correct_reasons(data)
end
it "adds dissaggregation data to the cleaned file " do
cleaner = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:)
processed_data = cleaner.process_raw_file(
file: path_to_sample_raw_file, disaggregation_data: cleaner.disaggregation_data
)
processed_data in [headers, clean_csv, log_csv, data]
expect(clean_csv.second.last).to eq "Economically Disadvantaged - Y"
one_thousand = data.find { |row| row.response_id == "1000" }
expect(one_thousand.income).to eq "Economically Disadvantaged - Y"
one_thousand_one = data.find { |row| row.response_id == "1001" }
expect(one_thousand_one.income).to eq "Economically Disadvantaged - N"
end
end
context ".filename" do
context "defines a filename in the format: [district].[early_ed/short_form/standard/teacher].[year as 2022-23]" do
context "when the file is based on standard survey items" do
it "adds the survey type as standard to the filename" do
survey_items = SurveyItem.where(survey_item_id: standard_survey_items) survey_items = SurveyItem.where(survey_item_id: standard_survey_items)
data = [SurveyItemValues.new(row: { 'Recorded Date' => recorded_date, 'Dese ID' => '1_740_505' }, headers: standard_survey_items, genders: nil, survey_items:, data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: standard_survey_items, genders: nil, survey_items:,
schools: School.school_hash)] schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename(
headers: standard_survey_items, data: headers: standard_survey_items, data:
) )
expect(filename).to eq 'district1.standard.2022-23.csv' expect(filename).to eq "maynard.standard.2022-23.csv"
end end
context 'when the file is based on short form survey items' do context "when the file is based on short form survey items" do
it 'adds the survey type as short form to the filename' do it "adds the survey type as short form to the filename" do
survey_items = SurveyItem.where(survey_item_id: short_form_survey_items) survey_items = SurveyItem.where(survey_item_id: short_form_survey_items)
data = [SurveyItemValues.new(row: { 'Recorded Date' => recorded_date, 'Dese ID' => '1_740_505' }, headers: short_form_survey_items, genders: nil, survey_items:, data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: short_form_survey_items, genders: nil, survey_items:,
schools: School.school_hash)] schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename(
headers: short_form_survey_items, data: headers: short_form_survey_items, data:
) )
expect(filename).to eq 'district1.short_form.2022-23.csv' expect(filename).to eq "maynard.short_form.2022-23.csv"
end end
end end
context 'when the file is based on early education survey items' do context "when the file is based on early education survey items" do
it 'adds the survey type as early education to the filename' do it "adds the survey type as early education to the filename" do
survey_items = SurveyItem.where(survey_item_id: early_education_survey_items) survey_items = SurveyItem.where(survey_item_id: early_education_survey_items)
data = [SurveyItemValues.new(row: { 'Recorded Date' => recorded_date, 'Dese ID' => '1_740_505' }, headers: early_education_survey_items, genders: nil, survey_items:, data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: early_education_survey_items, genders: nil, survey_items:,
schools: School.school_hash)] schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename(
headers: early_education_survey_items, data: headers: early_education_survey_items, data:
) )
expect(filename).to eq 'district1.early_education.2022-23.csv' expect(filename).to eq "maynard.early_education.2022-23.csv"
end end
end end
context 'when the file is based on teacher survey items' do context "when the file is based on teacher survey items" do
it 'adds the survey type as teacher to the filename' do it "adds the survey type as teacher to the filename" do
survey_items = SurveyItem.where(survey_item_id: teacher_survey_items) survey_items = SurveyItem.where(survey_item_id: teacher_survey_items)
data = [SurveyItemValues.new(row: { 'Recorded Date' => recorded_date, 'Dese ID' => '1_740_505' }, headers: teacher_survey_items, genders: nil, survey_items:, data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: teacher_survey_items, genders: nil, survey_items:,
schools: School.school_hash)] schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename(
headers: teacher_survey_items, data: headers: teacher_survey_items, data:
) )
expect(filename).to eq 'district1.teacher.2022-23.csv' expect(filename).to eq "maynard.teacher.2022-23.csv"
end end
end end
context 'when there is more than one district' do context "when there is more than one district" do
it 'adds all districts to the filename' do it "adds all districts to the filename" do
survey_items = SurveyItem.where(survey_item_id: teacher_survey_items) survey_items = SurveyItem.where(survey_item_id: teacher_survey_items)
data = [SurveyItemValues.new(row: { 'Recorded Date' => recorded_date, 'Dese ID' => '1_740_505' }, headers: teacher_survey_items, genders: nil, survey_items:, schools: School.school_hash), data = [SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "1_740_505" }, headers: teacher_survey_items, genders: nil, survey_items:, schools: School.school_hash),
SurveyItemValues.new(row: { 'Recorded Date' => recorded_date, 'Dese ID' => '222_222' }, SurveyItemValues.new(row: { "Recorded Date" => recorded_date, "Dese ID" => "222_222" },
headers: teacher_survey_items, genders: nil, survey_items:, schools: School.school_hash)] headers: teacher_survey_items, genders: nil, survey_items:, schools: School.school_hash)]
filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:).filename( filename = Cleaner.new(input_filepath:, output_filepath:, log_filepath:, disaggregation_filepath:).filename(
headers: teacher_survey_items, data: headers: teacher_survey_items, data:
) )
expect(filename).to eq 'district1.district2.teacher.2022-23.csv' expect(filename).to eq "maynard.district2.teacher.2022-23.csv"
end end
end end
end end
end end
end end
end end
def reads_headers_from_raw_csv(processed_data)
processed_data in [headers, clean_csv, log_csv, data]
expect(headers).to eq ["StartDate", "EndDate", "Status", "IPAddress", "Progress", "Duration (in seconds)",
"Finished", "RecordedDate", "ResponseId", "District", "School",
"LASID", "Gender", "Race", "What grade are you in?", "s-emsa-q1", "s-emsa-q2", "s-emsa-q3", "s-tint-q1",
"s-tint-q2", "s-tint-q3", "s-tint-q4", "s-tint-q5", "s-acpr-q1", "s-acpr-q2",
"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", "Raw Income", "Income"]
end
def invalid_rows_are_rejected_for_the_correct_reasons(data)
one_thousand_two = data.find { |row| row.response_id == "1002" }
expect(one_thousand_two.valid_progress?).to eq false
expect(one_thousand_two.valid_duration?).to eq true
expect(one_thousand_two.valid_grade?).to eq true
expect(one_thousand_two.valid_sd?).to eq true
one_thousand_three = data.find { |row| row.response_id == "1003" }
expect(one_thousand_three.valid_progress?).to eq false
expect(one_thousand_three.valid_duration?).to eq true
expect(one_thousand_three.valid_grade?).to eq true
expect(one_thousand_three.valid_sd?).to eq true
one_thousand_six = data.find { |row| row.response_id == "1006" }
expect(one_thousand_six.valid_progress?).to eq true
expect(one_thousand_six.valid_duration?).to eq false
expect(one_thousand_six.valid_grade?).to eq true
expect(one_thousand_six.valid_sd?).to eq true
one_thousand_seven = data.find { |row| row.response_id == "1007" }
expect(one_thousand_seven.valid_progress?).to eq true
expect(one_thousand_seven.valid_duration?).to eq false
expect(one_thousand_seven.valid_grade?).to eq true
expect(one_thousand_seven.valid_sd?).to eq true
one_thousand_seven = data.find { |row| row.response_id == "1007" }
expect(one_thousand_seven.valid_progress?).to eq true
expect(one_thousand_seven.valid_duration?).to eq false
expect(one_thousand_seven.valid_grade?).to eq true
expect(one_thousand_seven.valid_sd?).to eq true
one_thousand_nine = data.find { |row| row.response_id == "1009" }
expect(one_thousand_nine.valid_progress?).to eq true
expect(one_thousand_nine.valid_duration?).to eq true
expect(one_thousand_nine.valid_grade?).to eq false
expect(one_thousand_nine.valid_sd?).to eq true
one_thousand_ten = data.find { |row| row.response_id == "1010" }
expect(one_thousand_ten.valid_progress?).to eq true
expect(one_thousand_ten.valid_duration?).to eq true
expect(one_thousand_ten.valid_grade?).to eq false
expect(one_thousand_ten.valid_sd?).to eq true
one_thousand_eleven = data.find { |row| row.response_id == "1011" }
expect(one_thousand_eleven.valid_progress?).to eq true
expect(one_thousand_eleven.valid_duration?).to eq true
expect(one_thousand_eleven.valid_grade?).to eq false
expect(one_thousand_eleven.valid_sd?).to eq true
one_thousand_twenty_two = data.find { |row| row.response_id == "1022" }
expect(one_thousand_twenty_two.valid_progress?).to eq true
expect(one_thousand_twenty_two.valid_duration?).to eq true
expect(one_thousand_twenty_two.valid_grade?).to eq false
expect(one_thousand_twenty_two.valid_sd?).to eq true
one_thousand_twenty_three = data.find { |row| row.response_id == "1023" }
expect(one_thousand_twenty_three.valid_progress?).to eq true
expect(one_thousand_twenty_three.valid_duration?).to eq true
expect(one_thousand_twenty_three.valid_grade?).to eq false
expect(one_thousand_twenty_three.valid_sd?).to eq true
one_thousand_thirty_three = data.find { |row| row.response_id == "1033" }
expect(one_thousand_thirty_three.valid_progress?).to eq true
expect(one_thousand_thirty_three.valid_duration?).to eq true
expect(one_thousand_thirty_three.valid_grade?).to eq true
expect(one_thousand_thirty_three.valid_sd?).to eq false
one_thousand_thirty_four = data.find { |row| row.response_id == "1034" }
expect(one_thousand_thirty_four.valid_progress?).to eq true
expect(one_thousand_thirty_four.valid_duration?).to eq true
expect(one_thousand_thirty_four.valid_grade?).to eq true
expect(one_thousand_thirty_four.valid_sd?).to eq false
end
def csv_contains_the_correct_rows(csv, rows)
rows.each_with_index do |row, index|
response_id = 8 # eigth column
expect(csv[index + 1][response_id]).to eq row
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

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

@ -1,77 +0,0 @@
require 'rails_helper'
describe RaceScoreLoader do
let(:measure) { create(:measure, :with_student_survey_items) }
let(:school) { create(:school) }
let(:academic_year) { create(:academic_year) }
let(:race) { create(:race) }
let(:students) do
[].tap do |arr|
10.times do
s = create(:student)
s.races << race
s.save
arr << s
end
end
end
let(:survey_item_1) { measure.survey_items[0] }
let(:survey_item_2) { measure.survey_items[1] }
let(:survey_item_3) { measure.survey_items[2] }
let(:response_rate) do
create(:response_rate, school:, academic_year:, subcategory: measure.subcategory, meets_student_threshold: true)
end
# I'm not sure how to securely make available the key_derivation_salt for github actions. Disabling the tests
context 'when survey item responses exist' do
before :each do
response_rate
students.each do |student|
create(:survey_item_response, school:, academic_year:, likert_score: 2, survey_item: survey_item_1, student:)
end
students.each do |student|
create(:survey_item_response, school:, academic_year:, likert_score: 3, survey_item: survey_item_2, student:)
end
RaceScoreLoader.reset
end
it 'returns a list of averages' do
expect(measure.student_survey_items.count).to eq 2
american_indian_score = RaceScore.find_by(measure:, school:, academic_year:, race:)
expect(american_indian_score.average).to eq 2.5
expect(american_indian_score.meets_student_threshold).to eq true
end
it 'is idempotent' do
original_count = RaceScore.count
RaceScoreLoader.reset
new_count = RaceScore.count
expect(original_count).to eq new_count
end
end
context 'when there NOT sufficient survey item responses' do
before :each do
response_rate
9.times do |index|
create(:survey_item_response, school:, academic_year:, likert_score: 2, survey_item: survey_item_1,
student: students[index])
end
9.times do |index|
create(:survey_item_response, school:, academic_year:, likert_score: 3, survey_item: survey_item_2,
student: students[index])
end
RaceScoreLoader.reset
end
it 'returns a list of averages' do
expect(measure.student_survey_items.count).to eq 2
expect(SurveyItemResponse.count).to eq 18
rs = RaceScore.find_by(measure:, school:, academic_year:, race:)
expect(rs.average).to eq 0
expect(rs.meets_student_threshold).to eq false
end
end
end

@ -1,34 +1,30 @@
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_hash = {} Gender.gender_hash
Gender.all.each do |gender|
gender_hash[gender.qualtrics_code] = gender
end
gender_hash
end end
let(:survey_items) { [] } let(:survey_items) { [] }
let(:district) { create(:district, name: "Attleboro") }
let(:attleboro) do let(:attleboro) do
create(:school, name: 'Attleboro', dese_id: 1234) 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
@ -46,9 +42,9 @@ 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:)
@ -57,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
@ -81,279 +77,321 @@ 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
headers = ['Dese ID'] row = { "Dese ID" => "1234" }
row = { 'Dese ID' => '1234' }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:)
expect(values.school).to eq attleboro
headers = ['School']
row = { 'School' => '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
headers = ['School- Attleboro'] row = { "DeseID" => "1234" }
row = { 'School- Attleboro' => '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 '.dese_id' do context ".respondent_type" do
it 'returns the dese id for the id provided' do it "reads header to find the survey type" do
headers = ['Dese ID'] headers = %w[s-sbel-q5 s-phys-q2 RecordedDate]
row = { 'Dese ID' => '11' } 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.dese_id).to eq 11
headers = ['School'] headers = %w[t-sbel-q5 t-phys-q2]
row = { 'School' => '22' } values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:)
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:) expect(values.respondent_type).to eq :teacher
expect(values.dese_id).to eq 22
end end
end end
# context '.survey_type' do context ".survey_type" do
# it 'reads header to find the survey type' do context "when survey type is standard form" do
# headers = %w[s-sbel-q5 s-phys-q2 RecordedDate] it "returns the survey type" do
# values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:)
# expect(values.respondent_type).to eq :student
# headers = %w[t-sbel-q5 t-phys-q2]
# values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:)
# expect(values.respondent_type).to eq :teacher
# end
# end
context '.survey_type' do
context 'when survey type is standard form' 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
end end
end end
end end
context '.valid_duration' do
context 'when duration is valid' do context ".income" do
it 'returns true' do context "when no disaggregation data is provided" do
it "returns an empty string " do
disaggregation_data = {}
values = SurveyItemValues.new(row: {}, headers:, genders:, survey_items:, schools:, disaggregation_data:)
expect(values.income).to eq "Unknown"
end
end
context "when disaggregation data is provided" do
before :each do
attleboro
ay_2022_23
end
it "translates Free Lunch to Economically Disadvantaged - Y" do
headers = ["District", "Academic Year", "LASID", "LowIncome"]
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Free Lunch" }
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"]
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:,
disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - Y"
end
it "translates Reduced Lunch to Economically Disadvantaged - Y" do
headers = ["District", "Academic Year", "LASID", "LowIncome"]
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Reduced Lunch" }
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"]
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:,
disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - Y"
end
it "translates LowIncome to Economically Disadvantaged - Y" do
headers = ["District", "Academic Year", "LASID", "LowIncome"]
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "LowIncome" }
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"]
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:,
disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - Y"
end
it "translates Not Eligible to Economically Disadvantaged - N" do
headers = ["District", "Academic Year", "LASID", "LowIncome"]
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "Not Eligible" }
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"]
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:,
disaggregation_data:)
expect(values.income).to eq "Economically Disadvantaged - N"
end
it "translates blanks to Unknown" do
headers = ["District", "Academic Year", "LASID", "LowIncome"]
row = { "District" => "Attleboro", "AcademicYear" => "2022-23", "LASID" => "1", "LowIncome" => "" }
disaggregation_data = { %w[1 Attleboro 2022-23] => DisaggregationRow.new(row:, headers:) }
headers = ["LASID", "Dese Id", "RecordedDate"]
row = { "LASID" => "1", "DESE ID" => "1234", "RecordedDate" => "2023-1-1" }
values = SurveyItemValues.new(row:, headers:, genders:, survey_items:, schools:,
disaggregation_data:)
expect(values.income).to eq "Unknown"
end
end
end
context ".valid_duration" do
context "when duration is valid" 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:)
end
end
end
context '.valid_duration' do
context 'when duration is valid' do
it 'returns true' do
headers = ['s-sbel-q5', 's-phys-q2', 'RecordedDate', 'Duration (in seconds)']
values = SurveyItemValues.new(row: { 'Duration (in seconds)' => '240' }, headers:, genders:, survey_items:,
schools:)
expect(values.valid_duration?).to eq true
headers = ['t-sbel-q5', 't-phys-q2', 'Duration (in seconds)']
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
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
# headers = ['s-sbel-q5', 's-phys-q2', 'RecordedDate', 'Duration (in seconds)'] 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

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

@ -3931,12 +3931,12 @@ saxes@^6.0.0:
dependencies: dependencies:
xmlchars "^2.2.0" xmlchars "^2.2.0"
semver@6.3.1, semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
version "6.3.1" version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.5.3: semver@^7.5.2, semver@^7.5.3:
version "7.5.4" version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
@ -4381,11 +4381,6 @@ xml-name-validator@^4.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
y18n@^5.0.5: y18n@^5.0.5:
version "5.0.8" version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"

Loading…
Cancel
Save