diff --git a/app/controllers/analyze_controller.rb b/app/controllers/analyze_controller.rb index 2a6fb055..de36b148 100644 --- a/app/controllers/analyze_controller.rb +++ b/app/controllers/analyze_controller.rb @@ -6,7 +6,7 @@ class AnalyzeController < SqmApplicationController @subcategory ||= Subcategory.find_by_subcategory_id(params[:subcategory_id]) @subcategory ||= Subcategory.find_by_subcategory_id('1A') - @measure = @subcategory.measures.includes([:admin_data_items]).first + @measure = @subcategory.measures.includes(%i[admin_data_items category])[0] @academic_year ||= AcademicYear.order('range DESC').first end end diff --git a/app/helpers/analyze_helper.rb b/app/helpers/analyze_helper.rb index 25b5cfd7..ababfcb5 100644 --- a/app/helpers/analyze_helper.rb +++ b/app/helpers/analyze_helper.rb @@ -7,7 +7,7 @@ module AnalyzeHelper 2 end - def graph_height + def analyze_graph_height 85 end @@ -20,7 +20,7 @@ module AnalyzeHelper end def benchmark_y - (zone_height * 2) - (benchmark_height / 2.0) + (analyze_zone_height * 2) - (benchmark_height / 2.0) end def benchmark_height @@ -36,15 +36,19 @@ module AnalyzeHelper end def bar_label_height - (100 - ((100 - graph_height) / 2)) + (100 - ((100 - analyze_graph_height) / 2)) end def bar_label_x(position) zone_label_width + (grouped_chart_width * position) - (grouped_chart_width / 2) end - def zone_height - graph_height / 5 + def analyze_zone_height + analyze_graph_height / 5 + end + + def zone_height_percentage + analyze_zone_height / 100.0 end def zone_label_y(position) diff --git a/app/models/measure.rb b/app/models/measure.rb index e660d579..5eb54800 100644 --- a/app/models/measure.rb +++ b/app/models/measure.rb @@ -57,8 +57,8 @@ class Measure < ActiveRecord::Base next Score.new(nil, false, false, false) if incalculable_score scores = [] - scores << collect_survey_scale_average(teacher_scales, school, academic_year) if meets_teacher_threshold - scores << collect_survey_scale_average(student_scales, school, academic_year) if meets_student_threshold + scores << teacher_score(school:, academic_year:).average if meets_teacher_threshold + scores << student_score(school:, academic_year:).average if meets_student_threshold scores << collect_admin_scale_average(admin_data_items, school, academic_year) if includes_admin_data_items? average = scores.flatten.compact.remove_zeros.average @@ -72,6 +72,26 @@ class Measure < ActiveRecord::Base @score[[school, academic_year]] end + def student_score(school:, academic_year:) + @student_score ||= begin + meets_student_threshold = sufficient_student_data?(school:, academic_year:) + meets_teacher_threshold = sufficient_teacher_data?(school:, academic_year:) + meets_admin_data_threshold = all_admin_data_collected?(school:, academic_year:) + average = collect_survey_scale_average(student_scales, school, academic_year) if meets_student_threshold + Score.new(average, meets_teacher_threshold, meets_student_threshold, meets_admin_data_threshold) + end + end + + def teacher_score(school:, academic_year:) + @teacher_score ||= begin + meets_student_threshold = sufficient_student_data?(school:, academic_year:) + meets_teacher_threshold = sufficient_teacher_data?(school:, academic_year:) + meets_admin_data_threshold = all_admin_data_collected?(school:, academic_year:) + average = collect_survey_scale_average(teacher_scales, school, academic_year) if meets_teacher_threshold + Score.new(average, meets_teacher_threshold, meets_student_threshold, meets_admin_data_threshold) + end + end + def warning_low_benchmark 1 end diff --git a/app/presenters/grouped_bar_chart_presenter.rb b/app/presenters/grouped_bar_column_presenter.rb similarity index 52% rename from app/presenters/grouped_bar_chart_presenter.rb rename to app/presenters/grouped_bar_column_presenter.rb index 2a7fcb4d..d797ecb3 100644 --- a/app/presenters/grouped_bar_chart_presenter.rb +++ b/app/presenters/grouped_bar_column_presenter.rb @@ -1,7 +1,8 @@ -class GroupedBarChartPresenter - attr_reader :score +class GroupedBarColumnPresenter + include AnalyzeHelper + attr_reader :score, :measure_name, :measure_id, :category, :position, :type - def initialize(measure:, score:) + def initialize(measure:, score:, position:, type:) @measure = measure @score = score.average @meets_teacher_threshold = score.meets_teacher_threshold? @@ -9,35 +10,35 @@ class GroupedBarChartPresenter @measure_name = @measure.name @measure_id = @measure.measure_id @category = @measure.subcategory.category + @position = position + @type = type end - IDEAL_ZONE_WIDTH_PERCENTAGE = 0.17 - APPROVAL_ZONE_WIDTH_PERCENTAGE = 0.17 - GROWTH_ZONE_WIDTH_PERCENTAGE = 0.17 - WATCH_ZONE_WIDTH_PERCENTAGE = 0.17 - WARNING_ZONE_WIDTH_PERCENTAGE = 0.17 - def y_offset case zone.type when :ideal, :approval - 34 - bar_height_percentage * 100 + (analyze_zone_height * 2) - bar_height_percentage else - 34 + (analyze_zone_height * 2) end end + def bar_color + "fill-#{zone.type}" + end + def bar_height_percentage case zone.type when :ideal - percentage * IDEAL_ZONE_WIDTH_PERCENTAGE + APPROVAL_ZONE_WIDTH_PERCENTAGE + (percentage * zone_height_percentage + zone_height_percentage) * 100 when :approval - percentage * APPROVAL_ZONE_WIDTH_PERCENTAGE + (percentage * zone_height_percentage) * 100 when :growth - (1 - percentage) * GROWTH_ZONE_WIDTH_PERCENTAGE + ((1 - percentage) * zone_height_percentage) * 100 when :watch - (1 - percentage) * WATCH_ZONE_WIDTH_PERCENTAGE + GROWTH_ZONE_WIDTH_PERCENTAGE + ((1 - percentage) * zone_height_percentage + zone_height_percentage) * 100 when :warning - (1 - percentage) * WARNING_ZONE_WIDTH_PERCENTAGE + WATCH_ZONE_WIDTH_PERCENTAGE + GROWTH_ZONE_WIDTH_PERCENTAGE + ((1 - percentage) * zone_height_percentage + zone_height_percentage + zone_height_percentage) * 100 else 0.0 end @@ -56,4 +57,15 @@ class GroupedBarChartPresenter ) zones.zone_for_score(@score) end + + def label + case type + when :all + 'All Survey Data' + when :student + 'All Students' + when :teacher + 'All Teachers' + end + end end diff --git a/app/views/analyze/_grouped_bar_chart.html.erb b/app/views/analyze/_grouped_bar_chart.html.erb new file mode 100644 index 00000000..f49c1208 --- /dev/null +++ b/app/views/analyze/_grouped_bar_chart.html.erb @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + Ideal + + + Approval + + + Growth + + + Watch + + + Warning + + + + +<% presenter = GroupedBarColumnPresenter.new(measure: measure, score: measure.student_score(school: @school, academic_year: @academic_year), position: 1, type: :student) %> + <%= render partial: "grouped_bar_column", locals: {presenter: presenter} %> +<% presenter = GroupedBarColumnPresenter.new(measure: measure, score: measure.teacher_score(school: @school, academic_year: @academic_year), position: 2, type: :teacher) %> + <%= render partial: "grouped_bar_column", locals: {presenter: presenter} %> +<% presenter = GroupedBarColumnPresenter.new(measure: measure, score: measure.score(school: @school, academic_year: @academic_year), position: 3, type: :all) %> + <%= render partial: "grouped_bar_column", locals: {presenter: presenter} %> + + diff --git a/app/views/analyze/_grouped_bar_column.html.erb b/app/views/analyze/_grouped_bar_column.html.erb new file mode 100644 index 00000000..464601c2 --- /dev/null +++ b/app/views/analyze/_grouped_bar_column.html.erb @@ -0,0 +1,11 @@ + + + + + <%= presenter.score %> + + + + <%= presenter.label %> + + diff --git a/app/views/analyze/index.html.erb b/app/views/analyze/index.html.erb index a4c5073b..b3e53f23 100644 --- a/app/views/analyze/index.html.erb +++ b/app/views/analyze/index.html.erb @@ -1,74 +1,20 @@ <% content_for :title do %> -
Analysis of <%= @school.name %>
+

Analysis of <%= @school.name %>

<% end %> -<% presenter = GroupedBarChartPresenter.new(measure: @measure, score: @measure.score(school: @school, academic_year: @academic_year)) %> +

-
+

Measure <%= @measure.measure_id %>

- - <%= @measure.name %> - -
+

<%= @measure.name %>

- - - - - - - - - - - - - - - - - - - Ideal - - - Approval - - - Growth - - - Watch - - - Warning - - - - - - All Students - - - All Teachers - - - All Survey Data - - - - - - - - <%= presenter.score %> - - - + <%= render partial: "grouped_bar_chart" , locals: { measure: @measure} %>
+ +
diff --git a/spec/presenters/grouped_bar_column_presenter_spec.rb b/spec/presenters/grouped_bar_column_presenter_spec.rb new file mode 100644 index 00000000..97d0886f --- /dev/null +++ b/spec/presenters/grouped_bar_column_presenter_spec.rb @@ -0,0 +1,187 @@ +require 'rails_helper' + +describe GroupedBarColumnPresenter do + let(:watch_low_benchmark) { 2.9 } + let(:growth_low_benchmark) { 3.1 } + let(:approval_low_benchmark) { 3.6 } + let(:ideal_low_benchmark) { 3.8 } + + let(:measure) do + measure = create( + :measure, + name: 'Some Title' + ) + scale = create(:scale, measure:) + + create(:student_survey_item, scale:, + watch_low_benchmark:, + growth_low_benchmark:, + approval_low_benchmark:, + ideal_low_benchmark:) + + measure + end + + let(:measure_without_admin_data_items) do + create( + :measure, + name: 'Some Title' + ) + end + + let(:presenter) do + GroupedBarColumnPresenter.new measure:, score:, position: 1, type: :all + end + + shared_examples_for 'measure_name' do + it 'returns the measure name' do + expect(presenter.measure_name).to eq 'Some Title' + end + end + + context 'when the score is in the Ideal zone' do + let(:score) { Score.new(4.4, true, true) } + + it_behaves_like 'measure_name' + + it 'returns the correct color' do + expect(presenter.bar_color).to eq 'fill-ideal' + end + + it 'returns a bar width equal to the approval zone width plus the proportionate ideal zone width' do + expect(presenter.bar_height_percentage).to be_within(0.01).of(25.5) + end + + it 'returns a y_offset equal to the ' do + expect(presenter.y_offset).to be_within(0.01).of(8.5) + end + end + + context 'when the score is in the Approval zone' do + let(:score) { Score.new(3.7, true, true) } + + it_behaves_like 'measure_name' + + it 'returns the correct color' do + expect(presenter.bar_color).to eq 'fill-approval' + end + + it 'returns a bar width equal to the proportionate approval zone width' do + expect(presenter.bar_height_percentage).to be_within(0.01).of(8.5) + end + + it 'returns an x-offset of 60%' do + expect(presenter.y_offset).to be_within(0.01).of(25.5) + end + end + + context 'when the score is in the Growth zone' do + let(:score) { Score.new(3.2, true, true) } + + it_behaves_like 'measure_name' + + it 'returns the correct color' do + expect(presenter.bar_color).to eq 'fill-growth' + end + + it 'returns a bar width equal to the proportionate growth zone width' do + expect(presenter.bar_height_percentage).to be_within(0.01).of(13.59) + end + + context 'in order to achieve the visual effect' do + it 'returns an x-offset equal to 60% minus the bar width' do + expect(presenter.y_offset).to eq 34 + end + end + end + + context 'when the score is in the Watch zone' do + let(:score) { Score.new(2.9, true, true) } + + it_behaves_like 'measure_name' + + it 'returns the correct color' do + expect(presenter.bar_color).to eq 'fill-watch' + end + + it 'returns a bar width equal to the proportionate watch zone width plus the growth zone width' do + expect(presenter.bar_height_percentage).to eq 34 + end + + context 'in order to achieve the visual effect' do + it 'returns an x-offset equal to 60% minus the bar width' do + expect(presenter.y_offset).to eq 34 + end + end + end + + context 'when the score is in the Warning zone' do + let(:score) { Score.new(1.0, true, true) } + + it_behaves_like 'measure_name' + + it 'returns the correct color' do + expect(presenter.bar_color).to eq 'fill-warning' + end + + it 'returns a bar width equal to the proportionate warning zone width plus the watch & growth zone widths' do + expect(presenter.bar_height_percentage).to eq 51 + end + + context 'in order to achieve the visual effect' do + it 'returns an y-offset equal to 60% minus the bar width' do + expect(presenter.y_offset).to eq 34 + end + end + end + + # context 'when a measure contains teacher survey items' do + # before :each do + # scale = create(:scale, measure:) + # create :teacher_survey_item, scale: + # end + + # context 'when there are insufficient teacher survey item responses' do + # let(:score) { Score.new(nil, false, true) } + # it 'shows a message saying there are insufficient responses' do + # expect(presenter.insufficient_teacher_responses?).to be true + # end + # end + + # context 'when there are sufficient teacher survey item responses' do + # let(:score) { Score.new(nil, true, true) } + # it 'does not show a partial data indicator' do + # expect(presenter.show_teacher_inapplicability_message?).to be true + # end + # end + # end + + # context 'when a measure does not contain teacher survey items' do + # context 'when there are insufficient teacher survey item responses' do + # let(:score) { Score.new(nil, false, true) } + # it 'shows a message saying the measure is not based on teacher survey items' do + # expect(presenter.show_teacher_inapplicability_message?).to be false + # end + # end + # end + + # context 'when a measure contains student survey items' do + # before :each do + # scale = create(:scale, measure:) + # create :student_survey_item, scale: + # end + + # context 'when there are insufficient student survey item responses' do + # let(:score) { Score.new(nil, true, false) } + # it 'shows a partial data indicator' do + # expect(presenter.show_student_inapplicability_message?).to be true + # end + # end + # context 'when there are sufficient student survey item responses' do + # let(:score) { Score.new(nil, true, true) } + # it 'shows a partial data indicator' do + # expect(presenter.show_student_inapplicability_message?).to be true + # end + # end + # end +end diff --git a/spec/system/journey_spec.rb b/spec/system/journey_spec.rb index 18914351..7da750fd 100644 --- a/spec/system/journey_spec.rb +++ b/spec/system/journey_spec.rb @@ -1,4 +1,5 @@ require 'rails_helper' +include AnalyzeHelper describe 'District Admin', js: true do let(:district) { District.find_by_slug 'winchester' } diff --git a/spec/views/analyze/index.html.erb_spec.rb b/spec/views/analyze/index.html.erb_spec.rb new file mode 100644 index 00000000..3079994f --- /dev/null +++ b/spec/views/analyze/index.html.erb_spec.rb @@ -0,0 +1,87 @@ +require 'rails_helper' +include AnalyzeHelper + +describe 'analyze/index' do + subject { Nokogiri::HTML(rendered) } + let(:category) { create(:category) } + let(:subcategory) { create(:subcategory, category:) } + + let(:support_for_teaching) do + measure = create(:measure, name: 'Support For Teaching Development & Growth', measure_id: '1', subcategory:) + scale = create(:scale, measure:) + create(:student_survey_item, + scale:, + watch_low_benchmark: 1.5, + growth_low_benchmark: 2.5, + approval_low_benchmark: 3.5, + ideal_low_benchmark: 4.5) + measure + end + + let(:effective_leadership) do + measure = create(:measure, name: 'Effective Leadership', measure_id: '2', subcategory:) + scale = create(:scale, measure:) + create(:teacher_survey_item, + scale:, + watch_low_benchmark: 1.5, + growth_low_benchmark: 2.5, + approval_low_benchmark: 3.5, + ideal_low_benchmark: 4.5) + measure + end + + let(:professional_qualifications) do + measure = create(:measure, name: 'Professional Qualifications', measure_id: '3', subcategory:) + scale = create(:scale, measure:) + create(:admin_data_item, + scale:, + watch_low_benchmark: 1.5, + growth_low_benchmark: 2.5, + approval_low_benchmark: 3.5, + ideal_low_benchmark: 4.5) + measure + end + let(:academic_year) { create(:academic_year) } + + before :each do + # assign :category_presenters, [] + # assign :grouped_bar_column_presenters, grouped_bar_column_presenters + assign :academic_year, academic_year + # assign :academic_years, [academic_year] + assign :district, create(:district) + assign :school, create(:school) + assign :category, category + assign :subcategory, subcategory + assign :measure, support_for_teaching + + render + end + + context 'when all the presenters have a non-nil score' do + # let(:grouped_bar_column_presenters) do + # measure = create(:measure, name: 'Display Me', measure_id: 'display-me') + # scale = create(:scale, measure:) + # create(:student_survey_item, + # scale:, + # watch_low_benchmark: 1.5, + # growth_low_benchmark: 2.5, + # approval_low_benchmark: 3.5, + # ideal_low_benchmark: 4.5) + # [ + # GroupedBarColumnPresenter.new(measure:, + # score: Score.new(rand)) + # ] + # end + + it 'displays a set of grouped bars for each presenter' do + displayed_variance_rows = subject.css('[data-for-measure-id]') + expect(displayed_variance_rows.count).to eq 3 + expect(displayed_variance_rows.first.attribute('data-for-measure-id').value).to eq '1' + + displayed_variance_labels = subject.css('[data-grouped-bar-label]') + expect(displayed_variance_labels.count).to eq 3 + expect(displayed_variance_labels.first.inner_text).to include 'All Students' + expect(displayed_variance_labels.last.inner_text).to include 'All Survey Data' + end + end +end