Skip to content

Commit

Permalink
Merge pull request #59 from Vizzuality/feature/import-sector-benchmarks
Browse files Browse the repository at this point in the history
Feature/import sector benchmarks
  • Loading branch information
tsubik committed Sep 18, 2019
2 parents dd8cb29 + dc8e8ad commit 51d20cb
Show file tree
Hide file tree
Showing 22 changed files with 403 additions and 70 deletions.
58 changes: 58 additions & 0 deletions app/admin/cp_benchmarks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
ActiveAdmin.register CP::Benchmark do
config.sort_order = 'release_date_desc'

menu parent: 'TPI', priority: 3, label: 'Carbon Performance Benchmarks'

permit_params :scenario, :sector_id, :release_date, :emissions

filter :release_date
filter :sector

data_export_sidebar 'CPBenchmarks'

show do
attributes_table do
row :id
row :release_date
row :sector
row :scenario
row :created_at
row :updated_at
end

panel 'Benchmark emission values' do
render 'admin/cp/emissions_table', emissions: resource.emissions
end
end

csv do
year_columns = collection.flat_map(&:emissions_all_years).uniq.sort

column :id
column(:sector) { |b| b.sector.name }
column(:release_date) { |b| b.release_date.strftime('%Y-%m') }
column :scenario

year_columns.map do |year|
column year do |benchmark|
benchmark.emissions[year]
end
end
end

index do
id_column
column :scenario
column :sector
column :release_date
actions
end

form partial: 'form'

controller do
def scoped_collection
super.includes(:sector)
end
end
end
16 changes: 9 additions & 7 deletions app/admin/sectors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@
'No Carbon Performance Benchmarks for this sector yet'
end
else
resource.cp_benchmarks.latest_first.map do |benchmark|
panel "Released in #{benchmark.date.strftime('%B %Y')}", class: 'benchmark' do
table_for benchmark.benchmarks, class: 'cell-padding-sm cell-centered' do
column :name do |b|
b['name']
resource.cp_benchmarks.latest_first.group_by(&:release_date).map do |release_date, benchmarks|
panel "Released in #{release_date.to_s(:month_and_year)}", class: 'benchmark' do
all_years = benchmarks.map(&:emissions_all_years).flatten.uniq

table_for benchmarks.sort_by(&:average_emission).reverse, class: 'cell-padding-sm cell-centered' do
column :scenario do |benchmark|
link_to benchmark.scenario, edit_admin_cp_benchmark_path(benchmark)
end
benchmark.benchmarks_all_years.map do |year|
all_years.map do |year|
column year do |b|
b['values'][year]
b.emissions[year]
end
end
end
Expand Down
9 changes: 9 additions & 0 deletions app/assets/stylesheets/admin/models/cp_benchmarks.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
form.cp_benchmark {
table {
tr.emission {
input {
padding: 8px;
}
}
}
}
11 changes: 11 additions & 0 deletions app/assets/stylesheets/admin/utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,14 @@ table.cell-centered {
border-radius: 5px;
}
}

.sortable-handle {
cursor: grab;

&::before {
content: '\28f6';
color: black;
position: relative;
top: -3px;
}
}
43 changes: 43 additions & 0 deletions app/javascript/admin/controllers/cp_benchmark_form_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Controller } from 'stimulus';

export default class extends Controller {
static targets = ['emissionTable', 'emissionRowTemplate', 'emissionRowAdd']

connect() {
$(this.element).on('submit', () => {
// serialize emissions
this.element.querySelector('#input_emissions').value = JSON.stringify(this.serializeTableData());
});

$(this.emissionTableTarget).find('tbody').sortable({
handle: '.sortable-handle',
items: 'tr.emission'
});
}

serializeTableData() {
const result = {};

$(this.emissionTableTarget).find('tr.emission').each((idx, el) => {
const year = $(el).find('input[name=emission_year]').val();
const value = parseFloat($(el).find('input[name=emission_value]').val());

if (!year || !value) return;

result[year] = value;
});

return result;
}

//actions
addEmission() {
const content = this.emissionRowTemplateTarget.innerHTML;
this.emissionRowAddTarget.insertAdjacentHTML('beforebegin', content);
}

removeEmission() {
const element = event.target.closest('tr');
element.remove();
}
}
38 changes: 27 additions & 11 deletions app/models/cp/benchmark.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,41 @@
#
# Table name: cp_benchmarks
#
# id :bigint not null, primary key
# sector_id :bigint
# date :date not null
# benchmarks :jsonb
# created_at :datetime not null
# updated_at :datetime not null
# id :bigint not null, primary key
# sector_id :bigint
# release_date :date not null
# created_at :datetime not null
# updated_at :datetime not null
# emissions :jsonb
# scenario :string
#

module CP
class Benchmark < ApplicationRecord
belongs_to :sector

scope :latest_first, -> { order(date: :desc) }
scope :by_date, -> { order(:date) }
scope :latest_first, -> { order(release_date: :desc) }
scope :by_release_date, -> { order(:release_date) }

validates_presence_of :date
validates_presence_of :release_date, :scenario

def benchmarks_all_years
benchmarks.map { |b| b['values'].keys }.flatten.uniq
def emissions_all_years
emissions.keys
end

def average_emission
none_empty_values = emissions.values.compact
return 0 if not_empty_values.empty?

none_empty_values.map(&:to_f).reduce(:+) / none_empty_values.length
end

def emissions=(value)
if value.is_a?(String)
write_attribute(:emissions, JSON.parse(value))
else
super
end
end
end
end
1 change: 1 addition & 0 deletions app/models/data_upload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class DataUpload < ApplicationRecord
has_one_attached :file

UPLOADERS = %w[
CPBenchmarks
Legislations
Litigations
Companies
Expand Down
63 changes: 63 additions & 0 deletions app/services/csv_import/cp_benchmarks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module CSVImport
class CPBenchmarks < BaseImporter
include UploaderHelpers

EMISSION_YEAR_PATTERN = /\d{4}/.freeze

def import
import_each_csv_row(csv) do |row|
benchmark = prepare_benchmark(row)
benchmark.assign_attributes(benchmark_attributes(row))

was_new_record = benchmark.new_record?
any_changes = benchmark.changed?

benchmark.save!

update_import_results(was_new_record, any_changes)
end
end

private

def resource_klass
CP::Benchmark
end

def prepare_benchmark(row)
find_record_by(:id, row) ||
CP::Benchmark.find_or_initialize_by(
sector: find_or_create_sector(row),
release_date: parse_date(row[:release_date]),
scenario: row[:scenario]
)
end

def find_or_create_sector(row)
return unless row[:sector].present?

Sector.where('lower(name) = ?', row[:sector].downcase).first ||
Sector.new(name: row[:sector])
end

def benchmark_attributes(row)
{
scenario: row[:scenario],
release_date: parse_date(row[:release_date]),
emissions: emissions(row)
}
end

def parse_date(date)
Import::DateUtils.safe_parse(date, ['%Y-%m', '%Y-%m-%d'])
end

def emissions(row)
row.headers.grep(EMISSION_YEAR_PATTERN).reduce({}) do |acc, year|
next acc unless row[year].present?

acc.merge(year.to_s.to_i => row[year].to_f)
end
end
end
end
23 changes: 11 additions & 12 deletions app/services/import/cp_benchmarks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Import
class CPBenchmarks
include ClimateWatchEngine::CSVImporter

EMISSION_YEAR_PATTERN = /\d{4}/.freeze
FILEPATH = "#{FILES_PREFIX}cpbenchmarks.csv".freeze

def call
Expand All @@ -18,13 +19,10 @@ def import
sector = Sector.find_by!(name: row[:sector])
benchmark = CP::Benchmark.find_or_initialize_by(
sector: sector,
date: parse_date(row[:date])
)
benchmarks = benchmark.benchmarks || []
benchmarks << benchmark_attributes(row)
benchmark.update!(
benchmarks: benchmarks
release_date: parse_date(row[:date]),
scenario: row[:type]
)
benchmark.update!(benchmark_attributes(row))
end
end

Expand All @@ -38,19 +36,20 @@ def cleanup

def benchmark_attributes(row)
{
name: row[:type],
values: values(row)
emissions: emissions(row)
}
end

def parse_date(date)
Import::DateUtils.safe_parse(date, ['%m-%Y'])
end

def values(row)
row.headers.grep(/\d{4}/).map do |year|
{year.to_s.to_i => row[year]&.to_f}
end.reduce(&:merge)
def emissions(row)
row.headers.grep(EMISSION_YEAR_PATTERN).reduce({}) do |acc, year|
next acc unless row[year].present?

acc.merge(year.to_s.to_i => row[year].to_f)
end
end
end
end
2 changes: 1 addition & 1 deletion app/views/admin/cp/_emissions_table.html.arb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
if emissions.empty?
div class: 'padding-20' do
'No emission or target values for this assessment'
'No emission values'
end
else
emissions_all_years = emissions.keys
Expand Down
22 changes: 22 additions & 0 deletions app/views/admin/cp_benchmarks/_emissions_table.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template data-target="cp-benchmark-form.emissionRowTemplate">
<%= render "emissions_table_row", year: '', value: '' %>
</template>

<table data-target="cp-benchmark-form.emissionTable">
<thead>
<th></th>
<th>Year</th>
<th>Value</th>
<th></th>
</thead>
<tbody>
<% f.object&.emissions&.each do |year, value| %>
<%= render "emissions_table_row", year: year, value: value %>
<% end %>
<tr data-target="cp-benchmark-form.emissionRowAdd">
<td colspan="4">
<%= button_tag "Add new value", type: 'button', class: 'button button--raised', data: { action: "click->cp-benchmark-form#addEmission" } %>
</td>
</tr>
</tbody>
</table>
6 changes: 6 additions & 0 deletions app/views/admin/cp_benchmarks/_emissions_table_row.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<tr class="emission">
<td><span class="sortable-handle"></span></td>
<td><input name="emission_year" type="number" value="<%= year %>" /></td>
<td><input name="emission_value" type="number" value="<%= value %>" /></td>
<td><%= button_tag "Remove", type: 'button', class: 'button button--raised button--red', data: { action: "click->cp-benchmark-form#removeEmission" } %></td>
</tr>
22 changes: 22 additions & 0 deletions app/views/admin/cp_benchmarks/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<%= semantic_form_for [:admin, resource], builder: ActiveAdmin::FormBuilder, html: {'data-controller' => 'check-modified cp-benchmark-form'} do |f| %>
<%= f.semantic_errors(*f.object.errors.keys) %>
<%= f.inputs do %>
<%= f.input :sector %>
<%= f.input :release_date %>
<%= f.input :scenario %>
<%= f.input :emissions, as: :hidden, input_html: { value: f.object.emissions.to_json, id: 'input_emissions' } %>
<% end %>

<div class="panel">
<h3>Benchmark emission values</h3>
<div class="panel-contents padding-20">
<%= render "emissions_table", f: f %>
</div>
</div>

<%= f.actions do %>
<%= f.action :submit %>
<%= f.action :cancel, :wrapper_html => { :class => 'cancel' } %>
<% end %>
<% end %>
8 changes: 8 additions & 0 deletions db/migrate/20190916134155_cp_benchmarks_flatten.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class CpBenchmarksFlatten < ActiveRecord::Migration[5.2]
def change
remove_column :cp_benchmarks, :benchmarks, :jsonb
add_column :cp_benchmarks, :emissions, :jsonb
add_column :cp_benchmarks, :scenario, :string
rename_column :cp_benchmarks, :date, :release_date
end
end
Loading

0 comments on commit 51d20cb

Please sign in to comment.