diff --git a/app/models/report/survey_item.rb b/app/models/report/survey_item.rb index e78076a4..22b0f7a2 100644 --- a/app/models/report/survey_item.rb +++ b/app/models/report/survey_item.rb @@ -1,26 +1,29 @@ module Report class SurveyItem - def self.create_grade_report(school:, academic_year:, filename:) + def self.create_grade_report(school:, academic_year:, filename:, use_student_survey_items: ::SurveyItem.student_survey_items.pluck(:id)) # get list of survey items with sufficient responses survey_items = Set.new # also get a map of grade->survey_id - sufficient_survey_items = Hash.new + sufficient_survey_items = {} school.grades(academic_year:).each do |grade| sufficient_survey_items[grade] ||= Set.new end SurveyItemResponse.student_survey_items_with_responses_by_grade( school:, - academic_year:, - ).each do |key,count| + academic_year: + ).select do |key, _value| + use_student_survey_items.include?(key[1]) + end.each do |key, count| # key[1] is survey item id - next if key[0] == nil + next if key[0].nil? + survey_items.add(key[1]) sufficient_survey_items[key[0]].add(key[1]) if count >= 10 end # write headers headers = [ "School Name", - "Grade", + "Grade" ] survey_items.each do |survey_item_id| headers << ::SurveyItem.find_by_id(survey_item_id).prompt @@ -32,6 +35,7 @@ module Report # else, write 'N/A' school.grades(academic_year:).sort.each do |grade| next if grade == -1 # skip pre-k + row = [] row << school.name if grade == 0 @@ -41,8 +45,9 @@ module Report end survey_items.each do |survey_item_id| survey_item = ::SurveyItem.find_by_id survey_item_id - if sufficient_survey_items[grade].include? survey_item_id - row.append("#{survey_item.survey_item_responses.where(school:, academic_year:, grade:).average(:likert_score).to_f.round(2)}") + if sufficient_survey_items[grade].include? survey_item_id + row.append("#{survey_item.survey_item_responses.where(school:, academic_year:, + grade:).average(:likert_score).to_f.round(2)}") else row.append("N/A") end @@ -58,16 +63,16 @@ module Report row.append("All School") survey_items.each do |survey_item_id| survey_item = ::SurveyItem.find_by_id survey_item_id -# filter out response rate at subcategory level <24.5% for school average + # filter out response rate at subcategory level <24.5% for school average scale = Scale.find_by_id(survey_item.scale_id) measure = ::Measure.find_by_id(scale.measure_id) subcategory = ::Subcategory.find_by_id(measure.subcategory_id) if ::StudentResponseRateCalculator.new(subcategory:, school:, academic_year:).meets_student_threshold? row.append("#{survey_item.survey_item_responses.where( - # We allow the nil (unknown) grades in the school survey item average - # also filter less than 10 responses in the whole school - "school_id = ? and academic_year_id = ? and (grade IS NULL or grade IN (?))", school.id, academic_year.id, school.grades(academic_year:) - ).group("survey_item_id").having("count(*) >= 10").average(:likert_score).values[0].to_f.round(2)}") + # We allow the nil (unknown) grades in the school survey item average + # also filter less than 10 responses in the whole school + 'school_id = ? and academic_year_id = ? and (grade IS NULL or grade IN (?))', school.id, academic_year.id, school.grades(academic_year:) + ).group('survey_item_id').having('count(*) >= 10').average(:likert_score).values[0].to_f.round(2)}") else row.append("N/A") end @@ -80,18 +85,20 @@ module Report data end - def self.create_item_report(school:, academic_year:, filename:) + def self.create_item_report(school:, academic_year:, filename:, use_student_survey_items: ::SurveyItem.student_survey_items.pluck(:id)) # first, determine headers by finding the school and grades # Q: Should a grade not be included if it has no sufficient responses? # A: Include all grades, except preschool # Convert they keys in this hash to a hash where the key is the grade # and the value is a set of sufficient survey IDs - survey_ids_to_grades = Hash.new + survey_ids_to_grades = {} SurveyItemResponse.student_survey_items_with_responses_by_grade( school:, academic_year: - ).each do |key, count| + ).select do |key, _value| + use_student_survey_items.include?(key[1]) + end.each do |key, count| # key[1] is survey item ID # key[0] is grade survey_ids_to_grades[key[1]] ||= Set.new @@ -108,6 +115,7 @@ module Report ] school.grades(academic_year:).sort.each do |value| next if value == -1 + if value == 0 headers.append("Kindergarten") else @@ -126,9 +134,11 @@ module Report row.append("Students") school.grades(academic_year:).sort.each do |grade| next if grade == -1 + if grades.include?(grade) # we already know grade has sufficient responses - row.append("#{survey_item.survey_item_responses.where(school:, academic_year:, grade:).average(:likert_score).to_f.round(2)}") + row.append("#{survey_item.survey_item_responses.where(school:, academic_year:, + grade:).average(:likert_score).to_f.round(2)}") else row.append("N/A") end @@ -139,10 +149,10 @@ module Report subcategory = ::Subcategory.find_by_id(measure.subcategory_id) if ::StudentResponseRateCalculator.new(subcategory:, school:, academic_year:).meets_student_threshold? row.append("#{survey_item.survey_item_responses.where( - # We allow the nil (unknown) grades in the school survey item average - # also filter less than 10 responses in the whole school - "school_id = ? and academic_year_id = ? and (grade IS NULL or grade IN (?))", school.id, academic_year.id, school.grades(academic_year:) - ).group("survey_item_id").having("count(*) >= 10").average(:likert_score).values[0].to_f.round(2)}") + # We allow the nil (unknown) grades in the school survey item average + # also filter less than 10 responses in the whole school + 'school_id = ? and academic_year_id = ? and (grade IS NULL or grade IN (?))', school.id, academic_year.id, school.grades(academic_year:) + ).group('survey_item_id').having('count(*) >= 10').average(:likert_score).values[0].to_f.round(2)}") else row.append("N/A") end @@ -157,10 +167,11 @@ module Report # we need to add padding to skip the grades columns # we also need to remove another column if the school teaches preschool, which we skip entirely preschool_adjustment = school.grades(academic_year:).include?(-1) ? 1 : 0 - padding = Array.new(school.grades(academic_year:).length - preschool_adjustment) { '' } + padding = Array.new(school.grades(academic_year:).length - preschool_adjustment) { "" } row.concat(padding) # we already know that the survey item we are looking at has sufficient responses - row.append("#{survey_item.survey_item_responses.where(school:, academic_year:).average(:likert_score).to_f.round(2)}") + row.append("#{survey_item.survey_item_responses.where(school:, + academic_year:).average(:likert_score).to_f.round(2)}") data << row end FileUtils.mkdir_p Rails.root.join("tmp", "reports") @@ -184,7 +195,7 @@ module Report measure = ::Measure.find_by_id(scale.measure_id) subcategory = ::Subcategory.find_by_id(measure.subcategory_id) category = Category.find_by_id(subcategory.category_id) - return [prompt, category.name, subcategory.name, measure.name, scale.scale_id] + [prompt, category.name, subcategory.name, measure.name, scale.scale_id] end end end diff --git a/lib/tasks/report.rake b/lib/tasks/report.rake index e717147a..1fb048d7 100644 --- a/lib/tasks/report.rake +++ b/lib/tasks/report.rake @@ -135,8 +135,10 @@ namespace :report do end end + # Usage example + # bundle exec rake 'report:survey_item:district[Lee Public Schools, 2023-24 Fall, early_education]' namespace :survey_item do - task :district, %i[district academic_year] => :environment do |_, args| + task :district, %i[district academic_year survey_item_type] => :environment do |_, args| district = District.find_by_name(args[:district]) if district.nil? puts "Invalid district name" @@ -147,14 +149,33 @@ namespace :report do puts "Invalid academic year" bad = 1 end + + survey_item_type = args[:survey_item_type] || "" + use_student_survey_items = case survey_item_type + when "standard" + ::SurveyItem.standard_survey_items.pluck(:id) + when "short_form" + ::SurveyItem.short_form_survey_items.pluck(:id) + when "early_education" + ::SurveyItem.early_education_survey_items.pluck(:id) + else + ::SurveyItem.student_survey_items.pluck(:id) + end + next if bad == 1 schools = district.schools + schools.each do |school| + filename_prefix = [] + filename_prefix << "survey_item_report" + filename_prefix << survey_item_type unless survey_item_type.blank? + filename_prefix << school.slug + filename_prefix << academic_year.range Report::SurveyItem.create_item_report(school:, academic_year:, - filename: "survey_item_report_" + school.slug + "_" + academic_year.range + "_by_item.csv") + filename: filename_prefix.join("_") + "_by_item.csv", use_student_survey_items:) Report::SurveyItem.create_grade_report(school:, academic_year:, - filename: "survey_item_report_" + school.slug + "_" + academic_year.range + "_by_grade.csv") + filename: filename_prefix.join("_") + "_by_grade.csv", use_student_survey_items:) end end end