Skip to content

Commit

Permalink
feat: ASCOR bubble chart data
Browse files Browse the repository at this point in the history
  • Loading branch information
martintomas committed Oct 2, 2023
1 parent 9ead60f commit aee7cee
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 0 deletions.
26 changes: 26 additions & 0 deletions app/assets/stylesheets/tpi/pages/ascor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,32 @@
}
}

.bubble-chart-header {
margin-bottom: 20px;

@include desktop {
display: flex;
justify-content: space-between;
align-items: center;
}

&__assessment-dropdown {
display: flex;
align-items: center;
gap: 10px;
}
}

.bubble-chart-description {
margin-bottom: 40px;
font-size: 14px;
line-height: 1.75;

@include desktop {
max-width: 60%;
}
}

section {
margin-top: 70px;

Expand Down
12 changes: 12 additions & 0 deletions app/controllers/tpi/ascor_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module TPI
class ASCORController < TPIController
before_action :fetch_ascor_countries, only: [:index, :show]
before_action :fetch_ascor_country, only: [:show]
before_action :fetch_assessment_date, only: [:index, :show, :index_assessment]
before_action :fetch_ascor_assessment_results, only: [:index, :index_assessment]

def index
@assessment_dates = ASCOR::Assessment.pluck(:assessment_date).uniq
Expand All @@ -18,6 +20,8 @@ def show
fixed_navbar("ASCOR Country #{@country.name}", admin_ascor_country_path(@country.id))
end

def index_assessment; end

def user_download
render zip: {
'ASCOR_countries.xlsx' => Api::CSVToExcel.new(CSVExport::ASCOR::Countries.new.call).call,
Expand All @@ -38,5 +42,13 @@ def fetch_ascor_countries
def fetch_ascor_country
@country = ASCOR::Country.friendly.find(params[:id])
end

def fetch_assessment_date
@assessment_date = params[:assessment_date] || ASCOR::Assessment.maximum(:assessment_date)
end

def fetch_ascor_assessment_results
@ascor_assessment_results = Api::ASCOR::BubbleChart.new(@assessment_date).call
end
end
end
3 changes: 3 additions & 0 deletions app/models/ascor/assessment_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ class ASCOR::AssessmentResult < ApplicationRecord
belongs_to :indicator, class_name: 'ASCOR::AssessmentIndicator', foreign_key: :indicator_id

validates_uniqueness_of :indicator_id, scope: :assessment_id

scope :of_type, ->(type) { includes(:indicator).where(ascor_assessment_indicators: {indicator_type: type}) }
scope :by_date, ->(date) { includes(:assessment).where(ascor_assessments: {assessment_date: date}) }
end
63 changes: 63 additions & 0 deletions app/services/api/ascor/bubble_chart.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module Api
module ASCOR
class BubbleChart
MARKET_CAP_QUERY = {
emissions_metric: 'Intensity per capita',
emissions_boundary: 'Production - excluding LULUCF'
}.freeze

attr_accessor :assessment_date

def initialize(assessment_date)
@assessment_date = assessment_date
end

def call
::ASCOR::AssessmentResult
.by_date(@assessment_date)
.of_type(:area)
.includes(assessment: :country)
.order(:indicator_id)
.map do |result|
{
pillar: pillars[result.indicator.code.split('.').first]&.first&.text,
area: result.indicator.text,
result: result.answer,
country_id: result.assessment.country_id,
country_name: result.assessment.country.name,
country_path: result.assessment.country.path,
market_cap_group: calculate_market_cap_group(result.assessment.country_id)
}
end
end

private

def calculate_market_cap_group(country_id)
recent_emission_level = recent_emission_levels[country_id]&.first&.recent_emission_level
return :small if recent_emission_level.blank?

market_cap_groups.find { |range, _| range.include?(recent_emission_level) }&.last || :small
end

def pillars
::ASCOR::AssessmentIndicator.where(indicator_type: :pillar).group_by(&:code)
end

def recent_emission_levels
@recent_emission_levels ||= ::ASCOR::Pathway.where(MARKET_CAP_QUERY).where(assessment_date: assessment_date)
.select(:country_id, :recent_emission_level)
.group_by(&:country_id)
end

def market_cap_groups
@market_cap_groups ||= begin
min = ::ASCOR::Pathway.where(MARKET_CAP_QUERY).where(assessment_date: assessment_date).minimum(:recent_emission_level)
max = ::ASCOR::Pathway.where(MARKET_CAP_QUERY).where(assessment_date: assessment_date).maximum(:recent_emission_level)
step = (max.to_f - min.to_f) / 3
{min..min + step => :small, min + step..min + (2 * step) => :medium, min + (2 * step)..max => :large}
end
end
end
end
end
3 changes: 3 additions & 0 deletions app/views/tpi/ascor/_bubble_chart.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!-- TODO: React components need to be replaced!!! -->
<%#= react_component('charts/bank-bubble/Chart', { results: @ascor_assessment_results }) %>
<%#= react_component('charts/bank-bubble/CompaniesAccordion', { results: @ascor_assessment_results }) %>
7 changes: 7 additions & 0 deletions app/views/tpi/ascor/_index_assessment.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(function () {
document.getElementById('bubble-chart').innerHTML = "<%= j render('bubble_chart') %>";
const newUrl = new URL(window.location.href);
newUrl.searchParams.set('assessment_date', <%= @assessment_date %>);
window.history.replaceState({}, '', newUrl.href);
ReactRailsUJS.mountComponents('#assessment-charts');
})();
31 changes: 31 additions & 0 deletions app/views/tpi/ascor/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,37 @@
</div>
</div>

<!-- TODO: Finalise bubble chart section -->
<!-- All React components code (even mobile one) goes into tpi/ascor/bubble_chart partial -->
<section class="container">
<div class="bubble-chart-header">
<h2 class="is-size-4">
Lorem ipsum dolor sit amet consectetur.
</h2>

<div class="bubble-chart-header__assessment-dropdown">
<div class="caption">
Assessment Date:
</div>
<div class="date-dropdown">
<%= react_component('RemoteDropdown', {
name: 'assessment_date',
remote: true,
url: index_assessment_tpi_ascor_index_path,
data: @assessment_dates.map {|v| {label: v&.strftime('%d %B %Y'), value: v}},
selected: @assessment_date
}) %>
</div>
</div>
</div>
<div class="bubble-chart-description">
Lorem ipsum dolor sit amet consectetur. Viverra vestibulum eget vitae justo morbi eget.
</div>
<div id="bubble-chart">
<%= render 'tpi/ascor/bubble_chart' %>
</div>
</section>

<% if @methodology_publication.present? %>
<section id="methodology" class="container">
<div class="columns">
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

resources :ascor, only: [:show, :index] do
collection do
get :index_assessment
get :user_download
end
end
Expand Down
105 changes: 105 additions & 0 deletions spec/services/api/ascor/bubble_chart_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
require 'rails_helper'

RSpec.describe Api::ASCOR::BubbleChart do
subject { described_class.new(assessment_date).call }

before_all do
usa = create(:ascor_country, id: 1, name: 'USA', iso: 'USA')
japan = create(:ascor_country, id: 2, name: 'Japan', iso: 'JPN')

_indicator_pillar_1 = create(:ascor_assessment_indicator, id: 1, code: 'EP', indicator_type: :pillar,
text: 'Emissions Performance')
_indicator_pillar_2 = create(:ascor_assessment_indicator, id: 2, code: 'CP', indicator_type: :pillar,
text: 'Climate Performance')
indicator_area_1 = create(:ascor_assessment_indicator, id: 3, code: 'EP.1', indicator_type: :area,
text: 'Emissions Performance 1')
indicator_area_2 = create(:ascor_assessment_indicator, id: 4, code: 'EP.2', indicator_type: :area,
text: 'Emissions Performance 2')
indicator_area_3 = create(:ascor_assessment_indicator, id: 5, code: 'CP.1', indicator_type: :area,
text: 'Climate Performance 1')

create :ascor_assessment, country: usa, assessment_date: Date.new(2019, 1, 1), results: [
build(:ascor_assessment_result, indicator: indicator_area_1, answer: 'Yes'),
build(:ascor_assessment_result, indicator: indicator_area_2, answer: 'No'),
build(:ascor_assessment_result, indicator: indicator_area_3, answer: 'Yes')
]
create :ascor_assessment, country: usa, assessment_date: Date.new(2019, 2, 1), results: [
build(:ascor_assessment_result, indicator: indicator_area_1, answer: 'Yes'),
build(:ascor_assessment_result, indicator: indicator_area_2, answer: 'No'),
build(:ascor_assessment_result, indicator: indicator_area_3, answer: 'No')
]
create :ascor_assessment, country: japan, assessment_date: Date.new(2019, 2, 1), results: [
build(:ascor_assessment_result, indicator: indicator_area_1, answer: 'No'),
build(:ascor_assessment_result, indicator: indicator_area_2, answer: 'Yes'),
build(:ascor_assessment_result, indicator: indicator_area_3, answer: 'No')
]

create :ascor_pathway, country: usa, assessment_date: Date.new(2019, 1, 1), emissions_metric: 'Intensity per capita',
emissions_boundary: 'Production - excluding LULUCF', recent_emission_level: 100
create :ascor_pathway, country: usa, assessment_date: Date.new(2019, 2, 1), emissions_metric: 'Intensity per capita',
emissions_boundary: 'Production - excluding LULUCF', recent_emission_level: 200
create :ascor_pathway, country: japan, assessment_date: Date.new(2019, 2, 1), emissions_metric: 'Intensity per capita',
emissions_boundary: 'Production - excluding LULUCF', recent_emission_level: 300
end

let(:assessment_date) { Date.new(2019, 2, 1) }

it 'returns the correct data' do
expect(subject).to eq(
[{
pillar: 'Emissions Performance',
area: 'Emissions Performance 1',
result: 'Yes',
country_id: 1,
country_name: 'USA',
country_path: '/ascor/usa',
market_cap_group: :small
},
{
pillar: 'Emissions Performance',
area: 'Emissions Performance 1',
result: 'No',
country_id: 2,
country_name: 'Japan',
country_path: '/ascor/japan',
market_cap_group: :large
},
{
pillar: 'Emissions Performance',
area: 'Emissions Performance 2',
result: 'No',
country_id: 1,
country_name: 'USA',
country_path: '/ascor/usa',
market_cap_group: :small
},
{
pillar: 'Emissions Performance',
area: 'Emissions Performance 2',
result: 'Yes',
country_id: 2,
country_name: 'Japan',
country_path: '/ascor/japan',
market_cap_group: :large
},
{
pillar: 'Climate Performance',
area: 'Climate Performance 1',
result: 'No',
country_id: 1,
country_name: 'USA',
country_path: '/ascor/usa',
market_cap_group: :small
},
{
pillar: 'Climate Performance',
area: 'Climate Performance 1',
result: 'No',
country_id: 2,
country_name: 'Japan',
country_path: '/ascor/japan',
market_cap_group: :large
}]
)
end
end

0 comments on commit aee7cee

Please sign in to comment.