From 8dfaa86982c5db214391ce43e28059f8c9303512 Mon Sep 17 00:00:00 2001 From: Nelson Jovel Date: Wed, 6 Mar 2024 11:50:14 -0800 Subject: [PATCH] feat: create a score csv report for measures --- app/models/measure.rb | 21 ++++++ app/models/report/gps.rb | 45 +++++++++++++ app/models/report/measure.rb | 121 +++++++++++++++++++++++++++++++++++ lib/tasks/report.rake | 48 +++++++++++++- 4 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 app/models/report/gps.rb create mode 100644 app/models/report/measure.rb diff --git a/app/models/measure.rb b/app/models/measure.rb index a2e5ecc6..433a6a9e 100644 --- a/app/models/measure.rb +++ b/app/models/measure.rb @@ -132,6 +132,27 @@ class Measure < ActiveRecord::Base averages.average end + def zone(school:, academic_year:) + zone_for_score(score: score(school:, academic_year:)) + end + + def student_zone(school:, academic_year:) + zone_for_score(score: student_score(school:, academic_year:)) + end + + def teacher_zone(school:, academic_year:) + zone_for_score(score: teacher_score(school:, academic_year:)) + end + + def admin_zone(school:, academic_year:) + zone_for_score(score: admin_score(school:, academic_year:)) + end + + def zone_for_score(score:) + Zones.new(watch_low_benchmark:, growth_low_benchmark:, + approval_low_benchmark:, ideal_low_benchmark:).zone_for_score(score.average) + end + private def any_admin_data_collected?(school:, academic_year:) diff --git a/app/models/report/gps.rb b/app/models/report/gps.rb new file mode 100644 index 00000000..cd46e9f1 --- /dev/null +++ b/app/models/report/gps.rb @@ -0,0 +1,45 @@ +module Report + class Gps + def self.to_csv + headers = ['School', 'Pillar', 'Indicator', 'Period', 'HALS Category', 'Ref.', 'Score', 'Zone'] + attributes = %w[school_name pillar indicator period category measure_ids score zone] + pillars = generate_pillars + CSV.generate(headers: true) do |csv| + csv << headers + pillars.each do |gps| + csv << attributes.map { |attr| gps.send(attr) } + end + end + end + + def self.generate_pillars + schools = School.all + academic_years = AcademicYear.order(range: :desc).first(2) + periods = %w[Current Previous] + + [].tap do |pillars| + academic_years.zip(periods).each do |academic_year, period| + schools.each do |school| + INDICATORS.each do |indicator, measures| + pillars << Pillar.new(school:, measures:, indicator:, period:, academic_year:) + end + end + end + end + end + + INDICATORS = + { "Teaching Environment": [Measure.includes(%i[subcategory admin_data_items]).find_by_measure_id('1A-iii'), Measure.includes(%i[subcategory admin_data_items]).find_by_measure_id('1B-ii')], + "Safety": Subcategory.find_by_subcategory_id('2A').measures.includes(:admin_data_items), + "Relationships": Subcategory.find_by_subcategory_id('2B').measures.includes(:admin_data_items), + "Academic Orientation": Subcategory.find_by_subcategory_id('2C').measures.includes(:admin_data_items), + "Facilities & Personnel": Subcategory.find_by_subcategory_id('3A').measures.includes(:admin_data_items), + "Family-School Relationships": [Measure.includes(%i[subcategory admin_data_items]).find_by_measure_id('3C-i')], + "Community Involvement & External Partners": [Measure.includes(%i[subcategory + admin_data_items]).find_by_measure_id('3C-ii')], + "Perception of Performance": Subcategory.find_by_subcategory_id('4A').measures.includes(:admin_data_items), + "Student Commitment To Learning": Subcategory.find_by_subcategory_id('4B').measures.includes(:admin_data_items), + "Critical Thinking": Subcategory.find_by_subcategory_id('4C').measures.includes(:admin_data_items), + "College & Career Readiness": Subcategory.find_by_subcategory_id('4D').measures.includes(:admin_data_items) } + end +end diff --git a/app/models/report/measure.rb b/app/models/report/measure.rb new file mode 100644 index 00000000..05d359e6 --- /dev/null +++ b/app/models/report/measure.rb @@ -0,0 +1,121 @@ +module Report + class Measure + def self.create_report(schools: School.all.includes(:district), academic_years: AcademicYear.all, measures: ::Measure.all, filename: "measure_report.csv") + data = [] + mutex = Thread::Mutex.new + data << ["District", "School", "School Code", "Academic Year", "Recorded Date Range", "Grades", "Measure", "Student Score", "Student Zone", "Teacher Score", + "Teacher Zone", "Admin Score", "Admin Zone", "All Score (Average)", "All Score Zone"] + pool_size = 2 + jobs = Queue.new + schools.each { |school| jobs << school } + + workers = pool_size.times.map do + Thread.new do + while school = jobs.pop(true) + academic_years.each do |academic_year| + measures.each do |measure| + respondents = Respondent.by_school_and_year(school:, academic_year:) + next if respondents.nil? + + response_rate = measure.subcategory.response_rate(school:, academic_year:) + next unless response_rate.meets_student_threshold? || response_rate.meets_teacher_threshold? + + score = measure.score(school:, academic_year:) + zone = measure.zone(school:, academic_year:).type.to_s.capitalize + + begin_date = SurveyItemResponse.where(school:, + academic_year:).where.not(recorded_date: nil).order(:recorded_date).first&.recorded_date&.to_date + end_date = SurveyItemResponse.where(school:, + academic_year:).where.not(recorded_date: nil).order(:recorded_date).last&.recorded_date&.to_date + date_range = "#{begin_date} - #{end_date}" + + row = [response_rate, measure, school, academic_year] + + all_grades = respondents.enrollment_by_grade.keys + grades = "#{all_grades.first}-#{all_grades.last}" + mutex.synchronize do + data << [school.district.name, + school.name, + school.dese_id, + academic_year.range, + date_range, + grades, + measure.measure_id, + student_score(row:), + student_zone(row:), + teacher_score(row:), + teacher_zone(row:), + admin_score(row:), + admin_zone(row:), + score.average, + zone] + end + 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.student_score(row:) + row in [response_rate, measure, school, academic_year] + student_score = measure.student_score(school:, academic_year:).average if response_rate.meets_student_threshold? + student_score || "N/A" + end + + def self.student_zone(row:) + row in [response_rate, measure, school, academic_year] + if response_rate.meets_student_threshold? + student_zone = measure.student_zone(school:, + academic_year:).type.to_s.capitalize + end + + student_zone || "N/A" + end + + def self.teacher_score(row:) + row in [response_rate, measure, school, academic_year] + teacher_score = measure.teacher_score(school:, academic_year:).average if response_rate.meets_teacher_threshold? + + teacher_score || "N/A" + end + + def self.teacher_zone(row:) + row in [response_rate, measure, school, academic_year] + if response_rate.meets_teacher_threshold? + teacher_zone = measure.teacher_zone(school:, academic_year:).type.to_s.capitalize + end + + teacher_zone || "N/A" + end + + def self.admin_score(row:) + row in [response_rate, measure, school, academic_year] + admin_score = measure.admin_score(school:, academic_year:).average + admin_score = "N/A" unless admin_score.present? && admin_score >= 0 + admin_score + end + + def self.admin_zone(row:) + row in [response_rate, measure, school, academic_year] + tmp_zone = measure.admin_zone(school:, academic_year:).type + 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 3c87cb9c..f6ca44ff 100644 --- a/lib/tasks/report.rake +++ b/lib/tasks/report.rake @@ -1,6 +1,50 @@ namespace :report do - desc 'create a report of the scores for all subcategories' + desc "create a report of the scores for all subcategories" task subcategory: :environment do - Report::Subcategory.create_report(filename: 'mciea_subcategory_report.csv') + Report::Subcategory.create_report(filename: "subcategory_report.csv") + end + + namespace :measure do + task bll: :environment do + schools = District.find_by_name("Attleboro").schools + 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 + 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) } + + Report::Measure.create_report(filename: "measure_report_attleboro.csv", measures:, schools:) + end end end