From a70ce7aafc741e761f9da32354dc43ef911d3bff Mon Sep 17 00:00:00 2001 From: Gabe Farrell Date: Fri, 19 Apr 2024 15:04:51 -0400 Subject: [PATCH] Feat: Add item-level reporting by grade --- app/models/report/survey_item.rb | 81 ++++++++++++++++++++++++++++- lib/tasks/report.rake | 88 ++++++++++++++++++++++++++++++-- 2 files changed, 165 insertions(+), 4 deletions(-) diff --git a/app/models/report/survey_item.rb b/app/models/report/survey_item.rb index db34ed7c..12da9f8e 100644 --- a/app/models/report/survey_item.rb +++ b/app/models/report/survey_item.rb @@ -1,6 +1,85 @@ module Report class SurveyItem - def self.create_report(school:, academic_year:, filename:) + def self.create_grade_report(school:, academic_year:, filename:) + # 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 + 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| + # key[1] is survey item id + survey_items.add(key[1]) + sufficient_survey_items[key[0]].add(key[1]) if count >= 10 + end + # write headers + headers = [ + "School Name", + "Grade", + ] + survey_items.each do |survey_item_id| + headers << ::SurveyItem.find_by_id(survey_item_id).prompt + end + data = [] + data << headers + # for each grade, iterate through all survey items in our list + # if it has sufficient responses, write out the avg score + # 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 + row.append("Kindergarten") + else + row.append("Grade #{grade}") + 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)}") + else + row.append("N/A") + end + end + data << row + end + + # now iterate through all survey items again + # if the whole school has sufficient responses, write the school avg + # else, N/A + row = [] + row.append(school.name) + 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 + 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)}") + else + row.append("N/A") + end + end + data << row + # write out file + FileUtils.mkdir_p Rails.root.join("tmp", "reports") + filepath = Rails.root.join("tmp", "reports", filename) + write_csv(data:, filepath:) + data + end + + def self.create_item_report(school:, academic_year:, filename:) # 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 diff --git a/lib/tasks/report.rake b/lib/tasks/report.rake index 6dec4ed2..99fe2e24 100644 --- a/lib/tasks/report.rake +++ b/lib/tasks/report.rake @@ -5,6 +5,65 @@ namespace :report do end namespace :measure do + task :district, [:district, :ay] => :environment do |_, args| + + # measure_ids = %w[ + # 1A-i + # 1A-ii + # 1A-iii + # 1B-i + # 1B-ii + # 2A-i + # 2A-ii + # 2B-i + # 2B-ii + # 2C-i + # 2C-ii + # 3A-i + # 3A-ii + # 3B-i + # 3B-ii + # 3B-iii + # 3C-i + # 3C-ii + # 4A-i + # 4A-ii + # 4B-i + # 4B-ii + # 4C-i + # 4D-i + # 4D-ii + # 5A-i + # 5A-ii + # 5B-i + # 5B-ii + # 5C-i + # 5C-ii + # 5D-i + # 5D-ii + # ] + # measures = measure_ids.map { |measure_id| Measure.find_by_measure_id(measure_id) } + + district = District.find_by_name args[:district] + academic_years = AcademicYear.where(range: args[:ay]) + if district == nil + puts "Invalid district name" + bad = true + end + if academic_years == nil + puts "Invalid academic year" + bad = true + end + next if bad + + Report::Measure.create_report( + schools: School.where(district:), + academic_years:, + # measures:, + filename: "measure_report_"+district.slug+".csv" + ) + end + task sqm: :environment do measure_ids = %w[ 1A-i @@ -26,6 +85,7 @@ namespace :report do 3C-i 3C-ii 4A-i + 4A-ii 4B-i 4B-ii 4C-i @@ -71,8 +131,8 @@ namespace :report do end end - namespace :survey_item do - task :by_item, [:school, :academic_year] => :environment do |_, args| + namespace :survey_item do + task :create, [:school, :academic_year] => :environment do |_, args| school = School.find_by_name(args[:school]) academic_year = AcademicYear.find_by_range(args[:academic_year]) if school == nil @@ -84,7 +144,29 @@ namespace :report do bad = 1 end next if bad == 1 - Report::SurveyItem.create_report(school: , academic_year:, filename: "survey_item_by_item_report.csv") + Report::SurveyItem.create_item_report(school:, academic_year:, filename: "survey_item_report_"+school.slug+"_"+academic_year.range+"_by_item.csv") + Report::SurveyItem.create_grade_report(school:, academic_year:, filename: "survey_item_report_"+school.slug+"_"+academic_year.range+"_by_grade.csv") end end + + namespace :survey_item do + task :district, [:district, :academic_year] => :environment do |_, args| + district = District.find_by_name(args[:district]) + if district == nil + puts "Invalid district name" + bad = 1 + end + academic_year = AcademicYear.find_by_range(args[:academic_year]) + if academic_year == nil + puts "Invalid academic year" + bad = 1 + end + next if bad == 1 + schools = district.schools + schools.each do |school| + Report::SurveyItem.create_item_report(school:, academic_year:, filename: "survey_item_report_"+school.slug+"_"+academic_year.range+"_by_item.csv") + Report::SurveyItem.create_grade_report(school:, academic_year:, filename: "survey_item_report_"+school.slug+"_"+academic_year.range+"_by_grade.csv") + end + end + end end