From 9a6db9235a669db476214386ca8c29adcd8030ef Mon Sep 17 00:00:00 2001 From: Nelson Jovel Date: Wed, 19 Jun 2024 10:15:02 -0700 Subject: [PATCH] chore: add report for listing district level averages for measure scores --- app/models/report/measure_summary.rb | 149 +++++++++++++++++++++++++++ lib/tasks/report.rake | 58 ++++------- 2 files changed, 170 insertions(+), 37 deletions(-) create mode 100644 app/models/report/measure_summary.rb diff --git a/app/models/report/measure_summary.rb b/app/models/report/measure_summary.rb new file mode 100644 index 00000000..a852f257 --- /dev/null +++ b/app/models/report/measure_summary.rb @@ -0,0 +1,149 @@ +module Report + class MeasureSummary + def self.create_report(district:, academic_years: AcademicYear.all, measures: ::Measure.all.order(measure_id: :ASC), filename: "measure_summary.csv") + data = [] + mutex = Thread::Mutex.new + data << ["Measure Name", "Measure ID", "District", "Academic Year", "Recorded Date Range", "Grades", "Student Score", "Student Zone", "Teacher Score", + "Teacher Zone", "Admin Score", "Admin Zone", "All Score (Average)", "All Score Zone"] + pool_size = 2 + jobs = Queue.new + measures.each { |measure| jobs << measure } + + workers = pool_size.times.map do + Thread.new do + while measure = jobs.pop(true) + academic_years.each do |academic_year| + all_grades = Set.new + + respondents = Respondent.where(school: district.schools, academic_year:) + respondents.each do |respondent| + respondent.enrollment_by_grade.keys.each do |grade| + all_grades.add(grade) + end + end + all_grades = all_grades.to_a + grades = "#{all_grades.first}-#{all_grades.last}" + + begin_date = SurveyItemResponse.where(school: district.schools, + academic_year:).where.not(recorded_date: nil).order(:recorded_date).first&.recorded_date&.to_date + end_date = SurveyItemResponse.where(school: district.schools, + academic_year:).where.not(recorded_date: nil).order(:recorded_date).last&.recorded_date&.to_date + date_range = "#{begin_date} - #{end_date}" + + row = [measure, district, academic_year] + + mutex.synchronize do + data << [measure.name, + measure.measure_id, + district.name, + academic_year.range, + date_range, + grades, + student_score(row:), + student_zone(row:), + teacher_score(row:), + teacher_zone(row:), + admin_score(row:), + admin_zone(row:), + all_data_score(row:), + all_data_zone(row:)] + end + end + end + rescue ThreadError + end + end + + workers.each(&:join) + FileUtils.mkdir_p Rails.root.join("tmp", "reports") + filepath = Rails.root.join("tmp", "reports", filename) + write_csv(data:, filepath:) + data + end + + def self.write_csv(data:, filepath:) + csv = CSV.generate do |csv| + data.each do |row| + csv << row + end + end + File.write(filepath, csv) + end + + def self.all_data_score(row:) + row in [ measure, district, academic_year] + score = district.schools.map do |school| + score = measure.score(school:, academic_year:).average + end.remove_blanks.average + score || "N/A" + end + + def self.all_data_zone(row:) + row in [ measure, district, academic_year] + + average = all_data_score(row:) + score = Score.new(average:, meets_teacher_threshold: true, meets_student_threshold: true, + meets_admin_data_threshold: true) + student_zone = measure.zone_for_score(score:).type.to_s + + student_zone || "N/A" + end + + def self.student_score(row:) + row in [ measure, district, academic_year] + student_score = district.schools.map do |school| + student_score = measure.student_score(school:, academic_year:).average + end.remove_blanks.average + student_score || "N/A" + end + + def self.student_zone(row:) + row in [ measure, district, academic_year] + average = student_score(row:) + score = Score.new(average:, meets_teacher_threshold: true, meets_student_threshold: true, + meets_admin_data_threshold: true) + student_zone = measure.zone_for_score(score:).type.to_s + + student_zone || "N/A" + end + + def self.teacher_score(row:) + row in [ measure, district, academic_year] + teacher_score = district.schools.map do |school| + measure.teacher_score(school:, academic_year:).average + end.remove_blanks.average + + teacher_score || "N/A" + end + + def self.teacher_zone(row:) + row in [ measure, district, academic_year] + average = teacher_score(row:) + score = Score.new(average:, meets_teacher_threshold: true, meets_student_threshold: true, + meets_admin_data_threshold: true) + teacher_zone = measure.zone_for_score(score:).type.to_s + + teacher_zone || "N/A" + end + + def self.admin_score(row:) + row in [ measure, district, academic_year] + admin_score = district.schools.map do |school| + measure.admin_score(school:, academic_year:).average + end.remove_blanks.average + + admin_score = "N/A" unless admin_score.present? && admin_score >= 0 + admin_score + end + + def self.admin_zone(row:) + row in [ measure, district, academic_year] + average = admin_score(row:) + score = Score.new(average:, meets_teacher_threshold: true, meets_student_threshold: true, + meets_admin_data_threshold: true) + + tmp_zone = measure.zone_for_score(score:).type.to_s + tmp_zone == :insufficient_data ? "N/A" : tmp_zone.to_s.capitalize + end + end +end diff --git a/lib/tasks/report.rake b/lib/tasks/report.rake index 6e080b1d..0f877819 100644 --- a/lib/tasks/report.rake +++ b/lib/tasks/report.rake @@ -5,44 +5,28 @@ namespace :report do end namespace :measure do - task :district, %i[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) } + task :district_summary, %i[district ay] => :environment do |_, args| + 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::MeasureSummary.create_report( + district:, + academic_years:, + filename: "measure_summary_" + district.slug + ".csv" + ) + end + + task :district, %i[district ay] => :environment do |_, args| district = District.find_by_name args[:district] academic_years = AcademicYear.where(range: args[:ay]) if district.nil?