Commit a1f124526ebd44f5e09b69bb99510b3de930b81c
1 parent
77ff53d6
Exists in
master
Showing
7 changed files
with
98 additions
and
29 deletions
Show diff stats
README.md
1 | 1 | ||
2 | # Travel Carbon Footprint Calculator | 2 | # Travel Carbon Footprint Calculator |
3 | 3 | ||
4 | +- https://travel-footprint-calculator.apps.goutenoir.com (private demo) | ||
5 | +- http://travel-footprint-calculator.irap.omp.eu (official, for later) | ||
6 | + | ||
7 | + | ||
4 | ## Overview | 8 | ## Overview |
5 | 9 | ||
6 | - Content is in `content.yml`. | 10 | - Content is in `content.yml`. |
content.yml
@@ -599,6 +599,22 @@ estimate: | @@ -599,6 +599,22 @@ estimate: | ||
599 | It may take from a few seconds up to a few minutes, | 599 | It may take from a few seconds up to a few minutes, |
600 | depending on the amount of locations you provided. | 600 | depending on the amount of locations you provided. |
601 | 601 | ||
602 | + help: | ||
603 | + first_name: Fill these to say hello. | ||
604 | + last_name: We will never share your data with anyone. | ||
605 | + origin_addresses: | | ||
606 | + Use <code>en_US</code> city and country names, without diacritics. | ||
607 | + | ||
608 | + The comma matters. | ||
609 | + <br> | ||
610 | + This is either a home city and a country | ||
611 | + or the cities and countries of the participants to the conference, meeting… | ||
612 | + destination_addresses: | | ||
613 | + This is either the cities and countries to travel to | ||
614 | + or the host city and country of the conference, meeting… | ||
615 | + <br> | ||
616 | + Please provide multiple cities and countries to compute the location | ||
617 | + of the minimum emission. | ||
602 | 618 | ||
603 | # Labels accept HTML, but not markdown | 619 | # Labels accept HTML, but not markdown |
604 | # Descriptions accept neither, since we use the HTML title attribute | 620 | # Descriptions accept neither, since we use the HTML title attribute |
@@ -677,6 +693,23 @@ estimation: | @@ -677,6 +693,23 @@ estimation: | ||
677 | Sorry about that. Please find the error message below. | 693 | Sorry about that. Please find the error message below. |
678 | <br> | 694 | <br> |
679 | Thank you for using our service. | 695 | Thank you for using our service. |
696 | + lolliplot: | ||
697 | + one_to_one: | | ||
698 | + The carbon dioxide equivalent emission is provided for each city of destination. | ||
699 | + Identical trips (i.e. identical destinations) are summed | ||
700 | + and the cumulative distance is provided. | ||
701 | + one_to_many: | | ||
702 | + The carbon dioxide equivalent emission is provided for each city of destination. | ||
703 | + Identical trips (i.e. identical destinations) are summed | ||
704 | + and the cumulative distance is provided. | ||
705 | + many_to_one: | | ||
706 | + The carbon dioxide equivalent emission is provided for each city of origin. | ||
707 | + Identical trips (i.e. identical origins) are summed and the cumulative distance is provided. | ||
708 | + many_to_many: | | ||
709 | + The carbon dioxide equivalent emission is summed | ||
710 | + over all cities of origin and provided for each city of destination. | ||
711 | + The cumulative distance to each city of destination is provided. | ||
712 | + Duplicates in the destinations are removed. | ||
680 | 713 | ||
681 | footer: | 714 | footer: |
682 | credits: | | 715 | credits: | |
flaskr/controllers/main_controller.py
@@ -9,7 +9,7 @@ from os.path import join | @@ -9,7 +9,7 @@ from os.path import join | ||
9 | 9 | ||
10 | from flaskr.extensions import cache, basic_auth | 10 | from flaskr.extensions import cache, basic_auth |
11 | from flaskr.forms import LoginForm, EstimateForm | 11 | from flaskr.forms import LoginForm, EstimateForm |
12 | -from flaskr.models import db, User, Estimation, StatusEnum | 12 | +from flaskr.models import db, User, Estimation, StatusEnum, ScenarioEnum |
13 | from flaskr.geocoder import CachedGeocoder | 13 | from flaskr.geocoder import CachedGeocoder |
14 | 14 | ||
15 | from flaskr.core import generate_unique_id, get_emission_models | 15 | from flaskr.core import generate_unique_id, get_emission_models |
@@ -366,6 +366,7 @@ def compute(): # process the queue of estimation requests | @@ -366,6 +366,7 @@ def compute(): # process the queue of estimation requests | ||
366 | # for each of the Emission Models, and present a mean of all Models. | 366 | # for each of the Emission Models, and present a mean of all Models. |
367 | # | 367 | # |
368 | if 1 == len(origins): | 368 | if 1 == len(origins): |
369 | + estimation.scenario = ScenarioEnum.one_to_many | ||
369 | results = compute_one_to_many( | 370 | results = compute_one_to_many( |
370 | _origin=origins[0], | 371 | _origin=origins[0], |
371 | _destinations=destinations, | 372 | _destinations=destinations, |
@@ -377,6 +378,7 @@ def compute(): # process the queue of estimation requests | @@ -377,6 +378,7 @@ def compute(): # process the queue of estimation requests | ||
377 | # Same as A for now. | 378 | # Same as A for now. |
378 | # | 379 | # |
379 | elif 1 == len(destinations): | 380 | elif 1 == len(destinations): |
381 | + estimation.scenario = ScenarioEnum.many_to_one | ||
380 | results = compute_one_to_many( | 382 | results = compute_one_to_many( |
381 | _origin=destinations[0], | 383 | _origin=destinations[0], |
382 | _destinations=origins, | 384 | _destinations=origins, |
@@ -388,6 +390,7 @@ def compute(): # process the queue of estimation requests | @@ -388,6 +390,7 @@ def compute(): # process the queue of estimation requests | ||
388 | # Run Scenario A for each Destination, and expose optimum Destination. | 390 | # Run Scenario A for each Destination, and expose optimum Destination. |
389 | # | 391 | # |
390 | else: | 392 | else: |
393 | + estimation.scenario = ScenarioEnum.many_to_many | ||
391 | unique_city_keys = [] | 394 | unique_city_keys = [] |
392 | result_cities = [] | 395 | result_cities = [] |
393 | for destination in destinations: | 396 | for destination in destinations: |
flaskr/models.py
@@ -24,6 +24,13 @@ class StatusEnum(enum.Enum): | @@ -24,6 +24,13 @@ class StatusEnum(enum.Enum): | ||
24 | failure = 'failure' | 24 | failure = 'failure' |
25 | 25 | ||
26 | 26 | ||
27 | +class ScenarioEnum(enum.Enum): | ||
28 | + one_to_one = 'one_to_one' | ||
29 | + many_to_one = 'many_to_one' | ||
30 | + one_to_many = 'one_to_many' | ||
31 | + many_to_many = 'many_to_many' | ||
32 | + | ||
33 | + | ||
27 | class Estimation(db.Model): | 34 | class Estimation(db.Model): |
28 | id = db.Column(db.Integer(), primary_key=True) | 35 | id = db.Column(db.Integer(), primary_key=True) |
29 | public_id = db.Column( | 36 | public_id = db.Column( |
@@ -42,12 +49,14 @@ class Estimation(db.Model): | @@ -42,12 +49,14 @@ class Estimation(db.Model): | ||
42 | origin_addresses = db.Column(db.UnicodeText()) | 49 | origin_addresses = db.Column(db.UnicodeText()) |
43 | destination_addresses = db.Column(db.UnicodeText()) | 50 | destination_addresses = db.Column(db.UnicodeText()) |
44 | 51 | ||
45 | - # One slug per line (or blankchar?) | 52 | + # One slug per line (or blank char?) |
46 | models_slugs = db.Column(db.UnicodeText()) | 53 | models_slugs = db.Column(db.UnicodeText()) |
47 | 54 | ||
48 | # Deprecated, we detect this scenario from the amount of locations. | 55 | # Deprecated, we detect this scenario from the amount of locations. |
49 | compute_optimal_destination = db.Column(db.Boolean()) | 56 | compute_optimal_destination = db.Column(db.Boolean()) |
50 | 57 | ||
58 | + # Outputs | ||
59 | + scenario = db.Column(db.Enum(ScenarioEnum), default=ScenarioEnum.many_to_many) | ||
51 | output_yaml = db.Column(db.UnicodeText()) | 60 | output_yaml = db.Column(db.UnicodeText()) |
52 | warnings = db.Column(db.UnicodeText()) | 61 | warnings = db.Column(db.UnicodeText()) |
53 | errors = db.Column(db.UnicodeText()) | 62 | errors = db.Column(db.UnicodeText()) |
@@ -60,10 +69,20 @@ class Estimation(db.Model): | @@ -60,10 +69,20 @@ class Estimation(db.Model): | ||
60 | def get_output_dict(self): | 69 | def get_output_dict(self): |
61 | if self._output_dict is None: | 70 | if self._output_dict is None: |
62 | self._output_dict = yaml_load(self.output_yaml) | 71 | self._output_dict = yaml_load(self.output_yaml) |
63 | - return self._output_dict | 72 | + return self._output_dict |
73 | + pass | ||
74 | + | ||
75 | + def is_one_to_one(self): | ||
76 | + return self.scenario == ScenarioEnum.one_to_one | ||
77 | + | ||
78 | + def is_one_to_many(self): | ||
79 | + return self.scenario == ScenarioEnum.one_to_many | ||
80 | + | ||
81 | + def is_many_to_one(self): | ||
82 | + return self.scenario == ScenarioEnum.many_to_one | ||
64 | 83 | ||
65 | - def has_many_to_many(self): | ||
66 | - return 'cities' in self.get_output_dict() | 84 | + def is_many_to_many(self): |
85 | + return self.scenario == ScenarioEnum.many_to_many | ||
67 | 86 | ||
68 | _models = None | 87 | _models = None |
69 | 88 | ||
@@ -82,6 +101,7 @@ class EstimationView(ModelView): | @@ -82,6 +101,7 @@ class EstimationView(ModelView): | ||
82 | 'first_name', | 101 | 'first_name', |
83 | 'last_name', | 102 | 'last_name', |
84 | 'models_slugs', | 103 | 'models_slugs', |
104 | + 'scenario', | ||
85 | 'origin_addresses', | 105 | 'origin_addresses', |
86 | 'destination_addresses', | 106 | 'destination_addresses', |
87 | 'warnings', | 107 | 'warnings', |
flaskr/static/css/common/main.css
@@ -73,7 +73,7 @@ span.required-asterisk { | @@ -73,7 +73,7 @@ span.required-asterisk { | ||
73 | /** LISTS *********************************************************************/ | 73 | /** LISTS *********************************************************************/ |
74 | 74 | ||
75 | .numbered-list { | 75 | .numbered-list { |
76 | - list-style: upper-roman; | 76 | + list-style: decimal-leading-zero; |
77 | } | 77 | } |
78 | 78 | ||
79 | 79 |
flaskr/templates/estimate.html
@@ -70,11 +70,11 @@ | @@ -70,11 +70,11 @@ | ||
70 | <div class="form-group row"> | 70 | <div class="form-group row"> |
71 | <div class="col-md-6"> | 71 | <div class="col-md-6"> |
72 | {{ render_field(form.first_name) }} | 72 | {{ render_field(form.first_name) }} |
73 | - <small class="form-text text-muted">Fill these to say hello.</small> | 73 | + <small class="form-text text-muted">{{ content.estimate.help.first_name | safe }}</small> |
74 | </div> | 74 | </div> |
75 | <div class="col-md-6"> | 75 | <div class="col-md-6"> |
76 | {{ render_field(form.last_name) }} | 76 | {{ render_field(form.last_name) }} |
77 | - <small class="form-text text-muted">We will never share your data with anyone.</small> | 77 | + <small class="form-text text-muted">{{ content.estimate.help.last_name | safe }}</small> |
78 | </div> | 78 | </div> |
79 | </div> | 79 | </div> |
80 | <div class="form-group"> | 80 | <div class="form-group"> |
@@ -83,19 +83,13 @@ | @@ -83,19 +83,13 @@ | ||
83 | <div class="form-group"> | 83 | <div class="form-group"> |
84 | {{ render_field(form.origin_addresses) }} | 84 | {{ render_field(form.origin_addresses) }} |
85 | <small class="form-text text-muted"> | 85 | <small class="form-text text-muted"> |
86 | - Use <code>en_US</code> city and country names, without diacritics. | ||
87 | - | ||
88 | - The comma matters. | ||
89 | - <br> | ||
90 | - These usually are the addresses of your participants. | 86 | + {{ content.estimate.help.origin_addresses | safe }} |
91 | </small> | 87 | </small> |
92 | </div> | 88 | </div> |
93 | <div class="form-group"> | 89 | <div class="form-group"> |
94 | {{ render_field(form.destination_addresses) }} | 90 | {{ render_field(form.destination_addresses) }} |
95 | <small class="form-text text-muted"> | 91 | <small class="form-text text-muted"> |
96 | - Provide <strong>multiple destinations</strong> to compare them and <strong>compute optimum</strong>. | ||
97 | - <br> | ||
98 | - These usually are the possible locations for an event. | 92 | + {{ content.estimate.help.destination_addresses | safe }} |
99 | </small> | 93 | </small> |
100 | </div> | 94 | </div> |
101 | {# <div class="form-check form-group">#} | 95 | {# <div class="form-check form-group">#} |
flaskr/templates/estimation.html
@@ -79,21 +79,31 @@ | @@ -79,21 +79,31 @@ | ||
79 | 79 | ||
80 | {% if not estimation.has_failed() %} | 80 | {% if not estimation.has_failed() %} |
81 | {#{% set estimation_output = estimation.get_output_dict() %}#} | 81 | {#{% set estimation_output = estimation.get_output_dict() %}#} |
82 | -{% if estimation.has_many_to_many() %} | ||
83 | - <div class="col-md-6"> | ||
84 | - <p> | ||
85 | - For each destination city, the sum of the travels from all the origins. | ||
86 | - </p> | 82 | +<div class="col-md-6"> |
83 | +{% if estimation.is_one_to_one() %} | ||
84 | + {{ content.estimation.lolliplot.one_to_one | markdown | safe }} | ||
85 | +{# <p>#} | ||
86 | +{# For each destination city, the sum of the travels from all the origins.#} | ||
87 | +{# </p>#} | ||
87 | {{ render_cities(estimation_output.cities) }} | 88 | {{ render_cities(estimation_output.cities) }} |
88 | - </div> | ||
89 | -{% else %} | ||
90 | - <div class="col-md-6"> | ||
91 | - <p> | ||
92 | - Carbon footprint for each city. | ||
93 | - </p> | 89 | +{% elif estimation.is_many_to_one() %} |
90 | + {{ content.estimation.lolliplot.many_to_one | markdown | safe }} | ||
91 | +{# <p>#} | ||
92 | +{# For each destination city, the sum of the travels from all the origins.#} | ||
93 | +{# </p>#} | ||
94 | + {{ render_cities(estimation_output.cities) }} | ||
95 | +{% elif estimation.is_one_to_many() %} | ||
96 | + {{ content.estimation.lolliplot.one_to_many | markdown | safe }} | ||
97 | + | ||
98 | + {{ render_cities(estimation_output.cities) }} | ||
99 | +{% elif estimation.is_many_to_many() %} | ||
100 | + {{ content.estimation.lolliplot.many_to_many | markdown | safe }} | ||
101 | +{# <p>#} | ||
102 | +{# Carbon footprint for each city.#} | ||
103 | +{# </p>#} | ||
94 | {{ render_cities(estimation_output.cities) }} | 104 | {{ render_cities(estimation_output.cities) }} |
95 | - </div> | ||
96 | {% endif %} | 105 | {% endif %} |
106 | +</div> | ||
97 | 107 | ||
98 | <div class="col-md-6"> | 108 | <div class="col-md-6"> |
99 | <ul class="nav"> | 109 | <ul class="nav"> |
@@ -153,8 +163,13 @@ | @@ -153,8 +163,13 @@ | ||
153 | {# </pre>#} | 163 | {# </pre>#} |
154 | {# </div>#} | 164 | {# </div>#} |
155 | {#</div>#} | 165 | {#</div>#} |
156 | -{% endif %} | 166 | + |
167 | +{% endif %}{# estimation.has_failed() #} | ||
157 | {% endblock %} | 168 | {% endblock %} |
169 | + | ||
170 | +{#############################################################################} | ||
171 | +{#############################################################################} | ||
172 | + | ||
158 | {% block js %} | 173 | {% block js %} |
159 | <script src="/static/js/vendor/d3.v4.js"></script> | 174 | <script src="/static/js/vendor/d3.v4.js"></script> |
160 | <script src="/static/js/vendor/d3-legend.js"></script> | 175 | <script src="/static/js/vendor/d3-legend.js"></script> |