Skip to content

Commit

Permalink
Merge pull request #3144 from OpenTreeMap/rtb/ecobenefits-totals
Browse files Browse the repository at this point in the history
ecobenefits totals
  • Loading branch information
RobinIsTheBird authored Jun 8, 2017
2 parents e862115 + 9a07679 commit 92495b7
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 36 deletions.
8 changes: 8 additions & 0 deletions opentreemap/treemap/ecobenefits.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ class BenefitCategory(object):

GROUPS = (ENERGY, STORMWATER, AIRQUALITY, CO2, CO2STORAGE)

is_annual_table = {
ENERGY: True,
STORMWATER: True,
AIRQUALITY: True,
CO2: True,
CO2STORAGE: False
}


class BenefitCalculator(object):
"""
Expand Down
14 changes: 13 additions & 1 deletion opentreemap/treemap/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import re

from django.db import connection
from django.utils.translation import ugettext as _
from django.utils.formats import number_format

from treemap.units import get_units, get_display_value
Expand All @@ -14,6 +15,9 @@


def format_benefits(instance, benefits, basis, digits=None):
# keep the top level module dependencies small
from treemap.ecobenefits import BenefitCategory

currency_symbol = ''
if instance.eco_benefits_conversion:
currency_symbol = instance.eco_benefits_conversion.currency_symbol
Expand All @@ -26,7 +30,8 @@ def format_benefits(instance, benefits, basis, digits=None):
# TODO: Use i18n/l10n to format currency
benefit['currency_saved'] = currency_symbol + number_format(
benefit['currency'], decimal_pos=0)
total_currency += benefit['currency']
if BenefitCategory.is_annual_table.get(key, True):
total_currency += benefit['currency']

unit_key = benefit.get('unit-name')

Expand All @@ -38,6 +43,13 @@ def format_benefits(instance, benefits, basis, digits=None):
benefit['value'] = value
benefit['unit'] = get_units(instance, unit_key, key)

benefits['all'] = {'totals': {
'value': None,
'label': _('Total annual benefits'),
'currency': total_currency,
'currency_saved': currency_symbol + number_format(
total_currency, decimal_pos=0)}}

# Add total and percent to basis
rslt = {'benefits': benefits,
'benefits_total_currency': total_currency,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
<i class="icon-right-open"></i>
<i class="icon-cancel"></i>
</a>
{% with all_benefits=benefits.all %}
{% include "treemap/partials/benefit_value_row.html" with benefit=all_benefits.totals total=True %}
{% endwith %}
{% with plot_benefits=benefits.plot %}
{% include "treemap/partials/benefit_value_row.html" with benefit=plot_benefits.totals total=True %}
{% if not hide_summary and plot_benefits %}
<div class="benefit-value-title">{% trans "Tree Benefits"%}</div>
{% endif %}
Expand Down
44 changes: 32 additions & 12 deletions opentreemap/treemap/tests/test_ecobenefits.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.test import override_settings

from treemap.models import (Plot, Tree, Species, ITreeRegion,
ITreeCodeOverride)
ITreeCodeOverride, BenefitCurrencyConversion)
from treemap.tests import (make_instance, make_commander_user, make_request,
OTMTestCase)
from treemap.tests.test_urls import UrlTestCase
Expand All @@ -27,7 +27,7 @@
invalidate_ecoservice_cache_if_stale)


class EcoTest(UrlTestCase):
class EcoTestCase(UrlTestCase):
def setUp(self):
# Example url for
# CEAT, 1630 dbh, NoEastXXX
Expand Down Expand Up @@ -57,7 +57,22 @@ def mockbenefits(*args, **kwargs):
region = ITreeRegion.objects.get(code='NoEastXXX')
p = region.geometry.point_on_surface

converter = BenefitCurrencyConversion(
currency_symbol='$',
electricity_kwh_to_currency=1.0,
natural_gas_kbtu_to_currency=1.0,
co2_lb_to_currency=1.0,
o3_lb_to_currency=1.0,
nox_lb_to_currency=1.0,
pm10_lb_to_currency=1.0,
sox_lb_to_currency=1.0,
voc_lb_to_currency=1.0,
h20_gal_to_currency=1.0)
converter.save()

self.instance = make_instance(is_public=True, point=p)
self.instance.eco_benefits_conversion = converter
self.instance.save()
self.user = make_commander_user(self.instance)

self.species = Species(otm_code='CEAT',
Expand All @@ -69,6 +84,21 @@ def mockbenefits(*args, **kwargs):

self.species.save_with_user(self.user)

self.origBenefitFn = ecobackend.json_benefits_call
ecobackend.json_benefits_call = mockbenefits

def tearDown(self):
ecobackend.json_benefits_call = self.origBenefitFn

def assert_benefit_value(self, bens, benefit, unit, value):
self.assertEqual(bens[benefit]['unit'], unit)
self.assertEqual(int(float(bens[benefit]['value'])), value)


class EcoTest(EcoTestCase):
def setUp(self):
super(EcoTest, self).setUp()
p = self.instance.center
self.plot = Plot(geom=p, instance=self.instance)

self.plot.save_with_user(self.user)
Expand All @@ -81,16 +111,6 @@ def mockbenefits(*args, **kwargs):

self.tree.save_with_user(self.user)

self.origBenefitFn = ecobackend.json_benefits_call
ecobackend.json_benefits_call = mockbenefits

def tearDown(self):
ecobackend.json_benefits_call = self.origBenefitFn

def assert_benefit_value(self, bens, benefit, unit, value):
self.assertEqual(bens[benefit]['unit'], unit)
self.assertEqual(int(float(bens[benefit]['value'])), value)

def test_eco_benefit_sanity(self):
rslt, basis, error = TreeBenefitsCalculator()\
.benefits_for_object(self.instance, self.tree.plot)
Expand Down
107 changes: 105 additions & 2 deletions opentreemap/treemap/tests/test_map_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
from contextlib import contextmanager
from unittest.case import skip

from django.contrib.gis.geos import Point
from django.contrib.gis.geos import Point, MultiPolygon, Polygon
from django.test.utils import override_settings

from treemap.models import (Tree, Plot, MapFeature, Species, TreePhoto)
from treemap.models import Tree, Plot, MapFeature, Species, TreePhoto
from treemap.instance import Instance
from treemap.search import Filter
from treemap.lib import format_benefits
from treemap.ecobenefits import get_benefits_for_filter, BenefitCategory
from treemap.tests import (make_instance, make_commander_user,
LocalMediaTestCase)
from treemap.tests.base import OTMTestCase
from treemap.tests.test_ecobenefits import EcoTestCase
from treemap.views.map_feature import update_map_feature

from stormwater.models import Bioswale
Expand Down Expand Up @@ -191,6 +195,105 @@ def test_hash_same_for_plot_and_map_feature(self):
same_hash_msg)


# this decorator is necessary in order to `add_map_feature_types(['Bioswale'])`
# in `setUp`.
@override_settings(FEATURE_BACKEND_FUNCTION=None)
class ResourceEcoBenefitsTest(EcoTestCase):
def setUp(self):
# sets up self.instance, self.user, self.species
super(ResourceEcoBenefitsTest, self).setUp()
self.instance.add_map_feature_types(['Bioswale'])
diversion_rate = .85
Bioswale.set_config_property(self.instance, 'diversion_rate',
diversion_rate)
Bioswale.set_config_property(self.instance, 'should_show_eco', True)
self.instance.annual_rainfall_inches = 8.0

def _center_as_3857(self):
p = self.instance.center
if p.srid != 3857:
p.transform(3857)
return p

def _box_around_point(self, pt, edge=1.0):
half_edge = 0.5 * edge
x_min = pt.x - half_edge
y_min = pt.y - half_edge
x_max = pt.x + half_edge
y_max = pt.y + half_edge
poly = Polygon(((x_min, y_min),
(x_min, y_max),
(x_max, y_max),
(x_max, y_min),
(x_min, y_min)))
return MultiPolygon((poly, ))

def test_resource_ecobenefits(self):
p = self._center_as_3857()
box = self._box_around_point(p)
bioswale = Bioswale(instance=self.instance,
geom=p,
polygon=box,
feature_type='Bioswale',
drainage_area=100.0)
bioswale.save_with_user(self.user)
filter = Filter('', '', self.instance)
benefits, __ = get_benefits_for_filter(filter)

self.assertIn('resource', benefits)
resource_benefits = benefits['resource']
self.assertIn(BenefitCategory.STORMWATER, resource_benefits)
stormwater = resource_benefits[BenefitCategory.STORMWATER]
self.assertTrue(isinstance(stormwater['value'], float))
self.assertGreater(stormwater['value'], 0.0)
self.assertTrue(isinstance(stormwater['currency'], float))
self.assertEqual(stormwater['value'], stormwater['currency'])

def test_all_ecobenefits(self):
p = self._center_as_3857()
plot = Plot(geom=p, instance=self.instance)
plot.save_with_user(self.user)

tree = Tree(plot=plot,
instance=self.instance,
readonly=False,
species=self.species,
diameter=1630)

tree.save_with_user(self.user)

p.x += 1.1
p.y += 1.1
box = self._box_around_point(p)
bioswale = Bioswale(instance=self.instance,
geom=p,
polygon=box,
feature_type='Bioswale',
drainage_area=100.0)
bioswale.save_with_user(self.user)
filter = Filter('', '', self.instance)
benefits, basis = get_benefits_for_filter(filter)

self.assertIn('plot', benefits)
plot_benefits = benefits['plot']
plot_categories = set(plot_benefits.keys())
self.assertSetEqual(plot_categories, set(BenefitCategory.GROUPS))

plot_currencies = {
cat: benefit.get('currency', None)
for cat, benefit in plot_benefits.items()}
self.assertIsNotNone(min(plot_currencies.values()))

expected_total_currency = sum(
[benefit['currency'] for benefit in plot_benefits.values()]) - \
plot_benefits[BenefitCategory.CO2STORAGE]['currency'] + \
benefits['resource'][BenefitCategory.STORMWATER]['currency']

formatted = format_benefits(self.instance, benefits, basis, digits=0)
self.assertAlmostEqual(formatted['benefits_total_currency'],
expected_total_currency, 3)


class UpdatedFieldsTest(LocalMediaTestCase):

def setUp(self):
Expand Down
21 changes: 1 addition & 20 deletions opentreemap/treemap/views/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@

from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _, ungettext
from django.utils.translation import ungettext
from django.shortcuts import get_object_or_404
from django.db import transaction
from django.http import HttpResponseRedirect

from treemap.search import Filter
from treemap.models import Tree, Plot
from treemap.ecobenefits import get_benefits_for_filter
from treemap.ecobenefits import BenefitCategory
from treemap.ecocache import get_cached_plot_count
from treemap.lib import format_benefits
from treemap.lib.tree import add_tree_photo_helper
Expand Down Expand Up @@ -70,24 +69,6 @@ def search_tree_benefits(request, instance):
# Inject the plot count as a basis for tree benefit calcs
basis.get('plot', {})['n_plots'] = total_plots

# We also want to inject the total currency amount saved
# for plot-based items except CO2 stored
total_currency_saved = 0

for benefit_name, benefit in benefits.get('plot', {}).iteritems():
if benefit_name != BenefitCategory.CO2STORAGE:
currency = benefit.get('currency', 0.0)
if currency:
total_currency_saved += currency

# save it as if it were a normal benefit so we get formatting
# and currency conversion
benefits.get('plot', {})['totals'] = {
'value': None,
'currency': total_currency_saved,
'label': _('Total annual benefits')
}

formatted = format_benefits(instance, benefits, basis, digits=0)

n_trees = basis['plot']['n_total']
Expand Down

0 comments on commit 92495b7

Please sign in to comment.