From 1782ef348259c71657e4aa96fa9c21c0a233f298 Mon Sep 17 00:00:00 2001 From: Alex Basson Date: Thu, 16 Sep 2021 13:26:12 -0400 Subject: [PATCH] Compute the score for a given construct, school, and academic year --- app/controllers/dashboard_controller.rb | 43 +++++-------- app/models/item.rb | 3 - app/models/survey_item.rb | 3 + app/models/survey_response.rb | 4 ++ app/models/survey_response_aggregator.rb | 16 +++++ db/migrate/20210915183344_create_items.rb | 10 ---- .../20210915183344_create_survey_items.rb | 10 ++++ .../20210916143538_create_survey_responses.rb | 13 ++++ db/schema.rb | 23 ++++--- db/seeds.rb | 4 +- .../features/school_dashboard_feature_spec.rb | 20 +++++-- spec/models/item_spec.rb | 7 --- spec/models/survey_item_spec.rb | 7 +++ .../models/survey_response_aggregator_spec.rb | 60 +++++++++++++++++++ 14 files changed, 160 insertions(+), 63 deletions(-) delete mode 100644 app/models/item.rb create mode 100644 app/models/survey_item.rb create mode 100644 app/models/survey_response.rb create mode 100644 app/models/survey_response_aggregator.rb delete mode 100644 db/migrate/20210915183344_create_items.rb create mode 100644 db/migrate/20210915183344_create_survey_items.rb create mode 100644 db/migrate/20210916143538_create_survey_responses.rb delete mode 100644 spec/models/item_spec.rb create mode 100644 spec/models/survey_item_spec.rb create mode 100644 spec/models/survey_response_aggregator_spec.rb diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index f065e018..30fb8771 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,45 +1,30 @@ class DashboardController < ApplicationController - before_action :set_school - def index authenticate(district.name.downcase, "#{district.name.downcase}!") - @construct_graph_row_presenters = [ - ConstructGraphRowPresenter.new(construct: Construct.find_by_construct_id('1A-i'), score: score('1A-i')) - ] + @construct_graph_row_presenters = Construct.where(construct_id: '1A-i').map do | construct | + ConstructGraphRowPresenter.new( + construct: construct, + score: SurveyResponseAggregator.score(school: school, academic_year: academic_year, construct: construct) + ) + end end private - def set_school - @school = School.find_by_slug school_slug + def school + @school ||= School.find_by_slug school_slug end - def school_slug - params[:school_id] + def district + @district ||= school.district end - def district - @district ||= @school.district + def school_slug + params[:school_id] end - def score(construct_id) - # for this school, get the score for this construct - # what we mean by this is: - # for the given school, find the responses to the survey of the given academic year (AY) - # that respond to questions that belong the given construct, - # then average the scores for those responses - # E.g. Find the responses from AY2020-21 from Milford High School, then get just those - # responses that correspond to questions from Professional Qualifications - # and then average the scores from those responses - # all_responses_from_milford_in_2020-21 = SurveyResponse.where('academic_year = 2020-21').where('school = Milford High School') - # milford-2020-21-responses-to-1A-i = all_responses_from_milford_2020-21.filter { |response| response.question.construct.construct_id = '1A-i' } - # score = average(milford-2020-21-responses-to-1A-i) - 4.8 + def academic_year + params[:year] end end - -# -# response belongs_to question -# question belongs_to construct -# response.question.construct \ No newline at end of file diff --git a/app/models/item.rb b/app/models/item.rb deleted file mode 100644 index 654b836e..00000000 --- a/app/models/item.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Item < ActiveRecord::Base - belongs_to :construct -end diff --git a/app/models/survey_item.rb b/app/models/survey_item.rb new file mode 100644 index 00000000..66cad8a9 --- /dev/null +++ b/app/models/survey_item.rb @@ -0,0 +1,3 @@ +class SurveyItem < ActiveRecord::Base + belongs_to :construct +end diff --git a/app/models/survey_response.rb b/app/models/survey_response.rb new file mode 100644 index 00000000..890b4656 --- /dev/null +++ b/app/models/survey_response.rb @@ -0,0 +1,4 @@ +class SurveyResponse < ActiveRecord::Base + belongs_to :school + belongs_to :survey_item +end diff --git a/app/models/survey_response_aggregator.rb b/app/models/survey_response_aggregator.rb new file mode 100644 index 00000000..56eda0ff --- /dev/null +++ b/app/models/survey_response_aggregator.rb @@ -0,0 +1,16 @@ +class Array + def average + self.sum.to_f / self.size + end +end + +class SurveyResponseAggregator + def self.score(academic_year:, school:, construct:) + SurveyResponse + .where(academic_year: academic_year) + .where(school: school) + .filter { |survey_response| survey_response.survey_item.construct == construct } + .map { |survey_response| survey_response.likert_score } + .average + end +end diff --git a/db/migrate/20210915183344_create_items.rb b/db/migrate/20210915183344_create_items.rb deleted file mode 100644 index 9db4fc40..00000000 --- a/db/migrate/20210915183344_create_items.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreateItems < ActiveRecord::Migration[5.0] - def change - create_table :items do |t| - t.integer :construct_id - t.string :prompt - end - - add_foreign_key :items, :constructs - end -end diff --git a/db/migrate/20210915183344_create_survey_items.rb b/db/migrate/20210915183344_create_survey_items.rb new file mode 100644 index 00000000..da3ba9e8 --- /dev/null +++ b/db/migrate/20210915183344_create_survey_items.rb @@ -0,0 +1,10 @@ +class CreateSurveyItems < ActiveRecord::Migration[5.0] + def change + create_table :survey_items do |t| + t.integer :construct_id + t.string :prompt + end + + add_foreign_key :survey_items, :constructs + end +end diff --git a/db/migrate/20210916143538_create_survey_responses.rb b/db/migrate/20210916143538_create_survey_responses.rb new file mode 100644 index 00000000..d955cbb5 --- /dev/null +++ b/db/migrate/20210916143538_create_survey_responses.rb @@ -0,0 +1,13 @@ +class CreateSurveyResponses < ActiveRecord::Migration[5.0] + def change + create_table :survey_responses do |t| + t.string :academic_year + t.integer :likert_score + t.integer :school_id + t.integer :survey_item_id + end + + add_foreign_key :survey_responses, :schools + add_foreign_key :survey_responses, :survey_items + end +end diff --git a/db/schema.rb b/db/schema.rb index c3cc458d..290e8a18 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20210915183344) do +ActiveRecord::Schema.define(version: 20210916143538) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -64,11 +64,6 @@ ActiveRecord::Schema.define(version: 20210915183344) do t.datetime "updated_at", null: false end - create_table "items", force: :cascade do |t| - t.integer "construct_id" - t.string "prompt" - end - create_table "question_lists", force: :cascade do |t| t.string "name" t.text "description" @@ -208,6 +203,18 @@ ActiveRecord::Schema.define(version: 20210915183344) do t.datetime "updated_at", null: false end + create_table "survey_items", force: :cascade do |t| + t.integer "construct_id" + t.string "prompt" + end + + create_table "survey_responses", force: :cascade do |t| + t.string "academic_year" + t.integer "likert_score" + t.integer "school_id" + t.integer "survey_item_id" + end + create_table "user_schools", force: :cascade do |t| t.integer "user_id" t.integer "school_id" @@ -233,9 +240,11 @@ ActiveRecord::Schema.define(version: 20210915183344) do t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree end - add_foreign_key "items", "constructs" add_foreign_key "recipient_lists", "schools" add_foreign_key "schedules", "schools" add_foreign_key "school_categories", "categories" add_foreign_key "school_categories", "schools" + add_foreign_key "survey_items", "constructs" + add_foreign_key "survey_responses", "schools" + add_foreign_key "survey_responses", "survey_items" end diff --git a/db/seeds.rb b/db/seeds.rb index 0f699d14..374cae41 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,6 +1,6 @@ require 'csv' -Item.destroy_all +SurveyItem.destroy_all Construct.destroy_all csv_file = File.read(Rails.root.join('data', 'AY2020-21_construct_items.csv')) @@ -18,7 +18,7 @@ CSV.parse(csv_file, headers: true).each do |row| end item_prompt = row['Survey Item'] - Item.create construct: Construct.find_by_construct_id(construct_id), prompt: item_prompt + SurveyItem.create construct: Construct.find_by_construct_id(construct_id), prompt: item_prompt end end diff --git a/spec/features/school_dashboard_feature_spec.rb b/spec/features/school_dashboard_feature_spec.rb index 0b380fbd..3f9a9683 100644 --- a/spec/features/school_dashboard_feature_spec.rb +++ b/spec/features/school_dashboard_feature_spec.rb @@ -2,12 +2,22 @@ require "rails_helper" feature "School dashboard", type: feature do let(:district) { District.create name: 'Winchester' } - let(:school) { - School.create name: 'Winchester High School', slug: 'winchester-high-school', district: district - } + let(:school) { School.create name: 'Winchester High School', slug: 'winchester-high-school', district: district } + + let(:construct) { Construct.find_by_construct_id('1A-i') } + + let(:survey_item_1_for_construct) { SurveyItem.create construct: construct } + let(:survey_item_2_for_construct) { SurveyItem.create construct: construct } let(:construct_row_bars) { page.all('rect.construct-row-bar') } + let(:ay_2020_21) { '2020-21' } + + before :each do + SurveyResponse.create academic_year: ay_2020_21, school: school, survey_item: survey_item_1_for_construct, likert_score: 4 + SurveyResponse.create academic_year: ay_2020_21, school: school, survey_item: survey_item_2_for_construct, likert_score: 5 + end + scenario "User authentication fails" do page.driver.browser.basic_authorize('wrong username', 'wrong password') @@ -19,12 +29,12 @@ feature "School dashboard", type: feature do scenario "User views a school dashboard" do page.driver.browser.basic_authorize(username, password) - visit "/districts/winchester/schools/#{school.slug}/dashboard?year=2020-21" + visit "/districts/winchester/schools/#{school.slug}/dashboard?year=#{ay_2020_21}" expect(page).to have_text(school.name) expect(page).to have_text('Professional Qualifications') first_row_bar = construct_row_bars.first - expect(first_row_bar['width']).to eq '283' + expect(first_row_bar['width']).to eq '179' end let(:username) { 'winchester' } diff --git a/spec/models/item_spec.rb b/spec/models/item_spec.rb deleted file mode 100644 index bbad9670..00000000 --- a/spec/models/item_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'rails_helper' - -describe Item, type: :model do - it('has all the questions') do - expect(Item.count).to eq 153 - end -end diff --git a/spec/models/survey_item_spec.rb b/spec/models/survey_item_spec.rb new file mode 100644 index 00000000..617ce641 --- /dev/null +++ b/spec/models/survey_item_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +describe SurveyItem, type: :model do + it('has all the questions') do + expect(SurveyItem.count).to eq 153 + end +end diff --git a/spec/models/survey_response_aggregator_spec.rb b/spec/models/survey_response_aggregator_spec.rb new file mode 100644 index 00000000..dff8f180 --- /dev/null +++ b/spec/models/survey_response_aggregator_spec.rb @@ -0,0 +1,60 @@ +require 'rails_helper' + +describe SurveyResponseAggregator, type: :model do + let(:ay_2020_21) { '2020-21' } + let(:ay_2021_22) { '2021-22' } + + let(:school_a) { School.create name: 'School A' } + let(:school_b) { School.create name: 'School A' } + + let(:construct_a) { Construct.create name: 'Construct A', construct_id: 'construct-a-id' } + let(:construct_b) { Construct.create name: 'Construct B', construct_id: 'construct-b-id' } + + let(:survey_item_1_for_construct_a) { SurveyItem.create construct: construct_a } + let(:survey_item_2_for_construct_a) { SurveyItem.create construct: construct_a } + + let(:survey_item_1_for_construct_b) { SurveyItem.create construct: construct_b } + let(:survey_item_2_for_construct_b) { SurveyItem.create construct: construct_b } + + before :each do + SurveyResponse.create academic_year: ay_2020_21, school: school_a, survey_item: survey_item_1_for_construct_a, likert_score: 1 + SurveyResponse.create academic_year: ay_2020_21, school: school_a, survey_item: survey_item_2_for_construct_a, likert_score: 2 + + SurveyResponse.create academic_year: ay_2020_21, school: school_a, survey_item: survey_item_1_for_construct_b, likert_score: 1 + SurveyResponse.create academic_year: ay_2020_21, school: school_a, survey_item: survey_item_2_for_construct_b, likert_score: 3 + + SurveyResponse.create academic_year: ay_2020_21, school: school_b, survey_item: survey_item_1_for_construct_a, likert_score: 1 + SurveyResponse.create academic_year: ay_2020_21, school: school_b, survey_item: survey_item_2_for_construct_a, likert_score: 4 + + SurveyResponse.create academic_year: ay_2020_21, school: school_b, survey_item: survey_item_1_for_construct_b, likert_score: 1 + SurveyResponse.create academic_year: ay_2020_21, school: school_b, survey_item: survey_item_2_for_construct_b, likert_score: 5 + + SurveyResponse.create academic_year: ay_2021_22, school: school_a, survey_item: survey_item_1_for_construct_a, likert_score: 2 + SurveyResponse.create academic_year: ay_2021_22, school: school_a, survey_item: survey_item_2_for_construct_a, likert_score: 3 + + SurveyResponse.create academic_year: ay_2021_22, school: school_a, survey_item: survey_item_1_for_construct_b, likert_score: 2 + SurveyResponse.create academic_year: ay_2021_22, school: school_a, survey_item: survey_item_2_for_construct_b, likert_score: 4 + + SurveyResponse.create academic_year: ay_2021_22, school: school_b, survey_item: survey_item_1_for_construct_a, likert_score: 2 + SurveyResponse.create academic_year: ay_2021_22, school: school_b, survey_item: survey_item_2_for_construct_a, likert_score: 5 + + SurveyResponse.create academic_year: ay_2021_22, school: school_b, survey_item: survey_item_1_for_construct_b, likert_score: 3 + SurveyResponse.create academic_year: ay_2021_22, school: school_b, survey_item: survey_item_2_for_construct_b, likert_score: 5 + end + + describe '.score' do + it 'returns the average score of the survey responses for the given school, academic year, and construct' do + expect(SurveyResponseAggregator.score(academic_year: ay_2020_21, school: school_a, construct: construct_a)).to eq 1.5 + expect(SurveyResponseAggregator.score(academic_year: ay_2020_21, school: school_a, construct: construct_b)).to eq 2.0 + + expect(SurveyResponseAggregator.score(academic_year: ay_2020_21, school: school_b, construct: construct_a)).to eq 2.5 + expect(SurveyResponseAggregator.score(academic_year: ay_2020_21, school: school_b, construct: construct_b)).to eq 3.0 + + expect(SurveyResponseAggregator.score(academic_year: ay_2021_22, school: school_a, construct: construct_a)).to eq 2.5 + expect(SurveyResponseAggregator.score(academic_year: ay_2021_22, school: school_a, construct: construct_b)).to eq 3.0 + + expect(SurveyResponseAggregator.score(academic_year: ay_2021_22, school: school_b, construct: construct_a)).to eq 3.5 + expect(SurveyResponseAggregator.score(academic_year: ay_2021_22, school: school_b, construct: construct_b)).to eq 4.0 + end + end +end