Blame view

flaskr/controllers/main_controller.py 15.4 KB
314c65e2   Antoine Goutenoir   Implement Scenari...
1
2
import geopy
import sqlalchemy
51f564d3   Antoine Goutenoir   Add a big chunk o...
3

a4c03d8e   Antoine Goutenoir   Add the controlle...
4
from flask import Blueprint, render_template, flash, request, redirect, \
67f85bce   Antoine Goutenoir   Generate a CSV fi...
5
    url_for, abort, send_from_directory, Response
461850db   Antoine Goutenoir   Yet another joyfu...
6
from os.path import join
7b4d2926   Goutte   Rework the contro...
7

b9fc86c3   Antoine Goutenoir   Secure the admin ...
8
from flaskr.extensions import cache, basic_auth
7b4d2926   Goutte   Rework the contro...
9
from flaskr.forms import LoginForm, EstimateForm
24f55cde   Antoine Goutenoir   Geocode destinati...
10
11
from flaskr.models import db, User, Estimation, StatusEnum
from flaskr.geocoder import CachedGeocoder
7b4d2926   Goutte   Rework the contro...
12

77e86148   Antoine Goutenoir   Fake support for ...
13
from flaskr.core import generate_unique_id, get_emission_models
51f564d3   Antoine Goutenoir   Add a big chunk o...
14
from flaskr.content import content
7b4d2926   Goutte   Rework the contro...
15

70aa301f   Antoine Goutenoir   Implement another...
16
17
from yaml import safe_dump as yaml_dump

67f85bce   Antoine Goutenoir   Generate a CSV fi...
18
19
20
import csv
# from io import StringIO
from cStringIO import StringIO
24f55cde   Antoine Goutenoir   Geocode destinati...
21

7b4d2926   Goutte   Rework the contro...
22
23
24
main = Blueprint('main', __name__)


9621b8a5   Antoine Goutenoir   Fix the CSV gener...
25
26
27
OUT_ENCODING = 'utf-8'


67f85bce   Antoine Goutenoir   Generate a CSV fi...
28
29
30
# -----------------------------------------------------------------------------
# refactor this outta here, like in core?

67f85bce   Antoine Goutenoir   Generate a CSV fi...
31
32
33
34

# -----------------------------------------------------------------------------


461850db   Antoine Goutenoir   Yet another joyfu...
35
@main.route('/favicon.ico')
7f7c6b10   Antoine Goutenoir   Disable RFI in th...
36
@cache.cached(timeout=10000)
461850db   Antoine Goutenoir   Yet another joyfu...
37
38
39
40
41
42
43
def favicon():  # we want it served from the root, not from static/
    return send_from_directory(
        join(main.root_path, '..', 'static', 'img'),
        'favicon.ico', mimetype='image/vnd.microsoft.icon'
    )


7b4d2926   Goutte   Rework the contro...
44
45
46
@main.route('/')
@cache.cached(timeout=1000)
def home():
4c862b54   Antoine Goutenoir   Add a grouped bar...
47
    models = get_emission_models()
a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
48
49
50
    models_dict = {}
    for model in models:
        models_dict[model.slug] = model.__dict__
4c862b54   Antoine Goutenoir   Add a grouped bar...
51
52
    return render_template(
        'home.html',
a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
53
        models=models_dict,
4c862b54   Antoine Goutenoir   Add a grouped bar...
54
        colors=[model.color for model in models],
a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
55
        labels=[model.name for model in models],
4c862b54   Antoine Goutenoir   Add a grouped bar...
56
    )
7b4d2926   Goutte   Rework the contro...
57
58
59
60


@main.route("/estimate", methods=["GET", "POST"])
def estimate():
77e86148   Antoine Goutenoir   Fake support for ...
61
    models = get_emission_models()
7b4d2926   Goutte   Rework the contro...
62
63
64
65
    form = EstimateForm()

    if form.validate_on_submit():

7b4d2926   Goutte   Rework the contro...
66
67
        id = generate_unique_id()

7b4d2926   Goutte   Rework the contro...
68
        estimation = Estimation()
a4c03d8e   Antoine Goutenoir   Add the controlle...
69
        # estimation.email = form.email.data
7b4d2926   Goutte   Rework the contro...
70
71
        estimation.first_name = form.first_name.data
        estimation.last_name = form.last_name.data
16f69d07   Antoine Goutenoir   Add an (unsecured...
72
        estimation.institution = form.institution.data
24f55cde   Antoine Goutenoir   Geocode destinati...
73
74
75
        estimation.status = StatusEnum.pending
        estimation.origin_addresses = form.origin_addresses.data
        estimation.destination_addresses = form.destination_addresses.data
b9fc86c3   Antoine Goutenoir   Secure the admin ...
76
        # estimation.compute_optimal_destination = form.compute_optimal_destination.data
77e86148   Antoine Goutenoir   Fake support for ...
77
78
        models_slugs = []
        for model in models:
35fbac1f   Antoine Goutenoir   Fix a blooper.
79
            if getattr(form, 'use_model_%s' % model.slug).data:
77e86148   Antoine Goutenoir   Fake support for ...
80
81
                models_slugs.append(model.slug)
        estimation.models_slugs = "\n".join(models_slugs)
7b4d2926   Goutte   Rework the contro...
82
83
84
85
86

        db.session.add(estimation)
        db.session.commit()

        flash("Estimation request submitted successfully.", "success")
a4c03d8e   Antoine Goutenoir   Add the controlle...
87
88
89
        return redirect(url_for(
            endpoint=".consult_estimation",
            public_id=estimation.public_id,
91751451   Antoine Goutenoir   Shift the API to ...
90
            extension='html'
a4c03d8e   Antoine Goutenoir   Add the controlle...
91
        ))
7b4d2926   Goutte   Rework the contro...
92
93
        # return render_template("estimate-debrief.html", form=form)

77e86148   Antoine Goutenoir   Fake support for ...
94
    return render_template("estimate.html", form=form, models=models)
7b4d2926   Goutte   Rework the contro...
95

4392f295   Goutte   Update the Estima...
96

15e57dca   Antoine Goutenoir   Naming things and...
97
98
99
100
101
102
103
104
105
106
107
108
109
110
@main.route("/invalidate")
def invalidate():
    stuck_estimations = Estimation.query \
        .filter_by(status=StatusEnum.working) \
        .all()

    for estimation in stuck_estimations:
        estimation.status = StatusEnum.failure
        estimation.errors = "Invalidated. Try again."
        db.session.commit()

    return ""


4392f295   Goutte   Update the Estima...
111
@main.route("/compute")
51f564d3   Antoine Goutenoir   Add a big chunk o...
112
def compute():  # process the queue of estimation requests
24f55cde   Antoine Goutenoir   Geocode destinati...
113
114
115
116
117

    def _respond(_msg):
        return "<pre>%s</pre>" % _msg

    def _handle_failure(_estimation, _failure_message):
a4c03d8e   Antoine Goutenoir   Add the controlle...
118
        _estimation.status = StatusEnum.failure
70aa301f   Antoine Goutenoir   Implement another...
119
        _estimation.errors = _failure_message
24f55cde   Antoine Goutenoir   Geocode destinati...
120
121
        db.session.commit()

72460978   Antoine Goutenoir   Use warnings inst...
122
123
124
125
    def _handle_warning(_estimation, _warning_message):
        _estimation.warnings = _warning_message
        db.session.commit()

4392f295   Goutte   Update the Estima...
126
127
    response = ""

03c194bf   Antoine Goutenoir   Actually implemen...
128
129
130
131
132
133
134
    count_working = Estimation.query \
        .filter_by(status=StatusEnum.working) \
        .count()

    if 0 < count_working:
        return _respond("Already working on estimation.")

24f55cde   Antoine Goutenoir   Geocode destinati...
135
136
137
    try:
        estimation = Estimation.query \
            .filter_by(status=StatusEnum.pending) \
1d48272e   Antoine Goutenoir   Compute the mean ...
138
            .order_by(Estimation.id.asc()) \
51f564d3   Antoine Goutenoir   Add a big chunk o...
139
            .first()
24f55cde   Antoine Goutenoir   Geocode destinati...
140
141
    except sqlalchemy.orm.exc.NoResultFound:
        return _respond("No estimation in the queue.")
51f564d3   Antoine Goutenoir   Add a big chunk o...
142
143
144
    except Exception as e:
        return _respond("Database error: %s" % (e,))

a6f63a6d   Antoine Goutenoir   Zzz…
145
146
    if not estimation:
        return _respond("No estimation in the queue.")
24f55cde   Antoine Goutenoir   Geocode destinati...
147

03c194bf   Antoine Goutenoir   Actually implemen...
148
149
150
151
152
153
    estimation.status = StatusEnum.working
    db.session.commit()

    response += u"Processing estimation `%s`...\n" % (
        estimation.public_id
    )
24f55cde   Antoine Goutenoir   Geocode destinati...
154
155
156

    geocoder = CachedGeocoder()

51f564d3   Antoine Goutenoir   Add a big chunk o...
157
158
    # GEOCODE ORIGINS #########################################################

461850db   Antoine Goutenoir   Yet another joyfu...
159
    origins_addresses = estimation.origin_addresses.strip().split("\n")
51f564d3   Antoine Goutenoir   Add a big chunk o...
160
161
162
163
    origins = []

    for i in range(len(origins_addresses)):

118da78e   Antoine Goutenoir   Wrapping up some ...
164
        origin_address = origins_addresses[i].strip()
51f564d3   Antoine Goutenoir   Add a big chunk o...
165
166

        try:
118da78e   Antoine Goutenoir   Wrapping up some ...
167
            origin = geocoder.geocode(origin_address.encode('utf-8'))
51f564d3   Antoine Goutenoir   Add a big chunk o...
168
        except geopy.exc.GeopyError as e:
72460978   Antoine Goutenoir   Use warnings inst...
169
            response += u"Failed to geocode origin `%s`.\n%s" % (
51f564d3   Antoine Goutenoir   Add a big chunk o...
170
171
                origin_address, e,
            )
72460978   Antoine Goutenoir   Use warnings inst...
172
173
            _handle_warning(estimation, response)
            continue
51f564d3   Antoine Goutenoir   Add a big chunk o...
174
175

        if origin is None:
72460978   Antoine Goutenoir   Use warnings inst...
176
            response += u"Failed to geocode origin `%s`." % (
51f564d3   Antoine Goutenoir   Add a big chunk o...
177
178
                origin_address,
            )
72460978   Antoine Goutenoir   Use warnings inst...
179
180
            _handle_warning(estimation, response)
            continue
51f564d3   Antoine Goutenoir   Add a big chunk o...
181
182
183
184
185
186
187
188
189
190

        origins.append(origin)

        response += u"Origin: %s == %s (%f, %f)\n" % (
            origin_address, origin.address,
            origin.latitude, origin.longitude,
        )

    # GEOCODE DESTINATIONS ####################################################

461850db   Antoine Goutenoir   Yet another joyfu...
191
    destinations_addresses = estimation.destination_addresses.strip().split("\n")
24f55cde   Antoine Goutenoir   Geocode destinati...
192
193
194
195
    destinations = []

    for i in range(len(destinations_addresses)):

118da78e   Antoine Goutenoir   Wrapping up some ...
196
        destination_address = destinations_addresses[i].strip()
24f55cde   Antoine Goutenoir   Geocode destinati...
197
198

        try:
118da78e   Antoine Goutenoir   Wrapping up some ...
199
            destination = geocoder.geocode(destination_address.encode('utf-8'))
24f55cde   Antoine Goutenoir   Geocode destinati...
200
        except geopy.exc.GeopyError as e:
1d48272e   Antoine Goutenoir   Compute the mean ...
201
            response += u"Failed to geocode destination `%s`.\n%s" % (
24f55cde   Antoine Goutenoir   Geocode destinati...
202
203
                destination_address, e,
            )
72460978   Antoine Goutenoir   Use warnings inst...
204
205
            _handle_warning(estimation, response)
            continue
24f55cde   Antoine Goutenoir   Geocode destinati...
206
207

        if destination is None:
1d48272e   Antoine Goutenoir   Compute the mean ...
208
            response += u"Failed to geocode destination `%s`." % (
24f55cde   Antoine Goutenoir   Geocode destinati...
209
210
                destination_address,
            )
72460978   Antoine Goutenoir   Use warnings inst...
211
212
            _handle_warning(estimation, response)
            continue
24f55cde   Antoine Goutenoir   Geocode destinati...
213

118da78e   Antoine Goutenoir   Wrapping up some ...
214
        # print(repr(destination.raw))
314c65e2   Antoine Goutenoir   Implement Scenari...
215

24f55cde   Antoine Goutenoir   Geocode destinati...
216
217
218
219
220
221
222
        destinations.append(destination)

        response += u"Destination: %s == %s (%f, %f)\n" % (
            destination_address, destination.address,
            destination.latitude, destination.longitude,
        )

70aa301f   Antoine Goutenoir   Implement another...
223
    # GTFO IF NO ORIGINS OR NO DESTINATIONS ###################################
51f564d3   Antoine Goutenoir   Add a big chunk o...
224
225

    if 0 == len(origins):
70aa301f   Antoine Goutenoir   Implement another...
226
        response += u"Failed to geocode all the origin(s)."
51f564d3   Antoine Goutenoir   Add a big chunk o...
227
228
229
        _handle_failure(estimation, response)
        return _respond(response)
    if 0 == len(destinations):
70aa301f   Antoine Goutenoir   Implement another...
230
        response += u"Failed to geocode all the destination(s)."
51f564d3   Antoine Goutenoir   Add a big chunk o...
231
232
233
        _handle_failure(estimation, response)
        return _respond(response)

70aa301f   Antoine Goutenoir   Implement another...
234
    # GRAB AND CONFIGURE THE EMISSION MODELS ##################################
51f564d3   Antoine Goutenoir   Add a big chunk o...
235

a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
236
237
238
    emission_models = estimation.get_models()
    # mdl_slugs = estimation.models_slugs.split("\n")
    # emission_models = [m for m in get_emission_models() if m.slug in mdl_slugs]
51f564d3   Antoine Goutenoir   Add a big chunk o...
239
240
241
242
243
244
    # print(emission_models)

    # PREPARE RESULT DICTIONARY THAT WILL BE STORED ###########################

    results = {}

70aa301f   Antoine Goutenoir   Implement another...
245
    # UTILITY PRIVATE FUNCTION(S) #############################################
51f564d3   Antoine Goutenoir   Add a big chunk o...
246

314c65e2   Antoine Goutenoir   Implement Scenari...
247
    def get_city_key(_location):
461850db   Antoine Goutenoir   Yet another joyfu...
248
249
250
251
252
253
254
255
256
257
258

        return _location.address.split(',')[0]

        # _city_key = _location.address
        # # if 'address100' in _location.raw['address']:
        # #     _city_key = _location.raw['address']['address100']
        # if 'city' in _location.raw['address']:
        #     _city_key = _location.raw['address']['city']
        # elif 'state' in _location.raw['address']:
        #     _city_key = _location.raw['address']['state']
        # return _city_key
314c65e2   Antoine Goutenoir   Implement Scenari...
259

70aa301f   Antoine Goutenoir   Implement another...
260
261
262
263
264
265
    def compute_one_to_many(
            _origin,
            _destinations,
            use_train_below=0.0
    ):
        _results = {}
51f564d3   Antoine Goutenoir   Add a big chunk o...
266
267
        footprints = {}

584b13cc   Antoine Goutenoir   Order the results...
268
269
        destinations_by_city_key = {}

5634c975   Antoine Goutenoir   Expose travel dis...
270
271
        cities_sum_foot = {}
        cities_sum_dist = {}
51f564d3   Antoine Goutenoir   Add a big chunk o...
272
        for model in emission_models:
584b13cc   Antoine Goutenoir   Order the results...
273
            cities_dict = {}
70aa301f   Antoine Goutenoir   Implement another...
274
            for _destination in _destinations:
51f564d3   Antoine Goutenoir   Add a big chunk o...
275
                footprint = model.compute_travel_footprint(
70aa301f   Antoine Goutenoir   Implement another...
276
277
278
279
                    origin_latitude=_origin.latitude,
                    origin_longitude=_origin.longitude,
                    destination_latitude=_destination.latitude,
                    destination_longitude=_destination.longitude,
51f564d3   Antoine Goutenoir   Add a big chunk o...
280
                )
70aa301f   Antoine Goutenoir   Implement another...
281

314c65e2   Antoine Goutenoir   Implement Scenari...
282
                city_key = get_city_key(_destination)
70aa301f   Antoine Goutenoir   Implement another...
283

584b13cc   Antoine Goutenoir   Order the results...
284
285
286
287
288
289
290
                destinations_by_city_key[city_key] = _destination

                if city_key not in cities_dict:
                    cities_dict[city_key] = {
                        'city': city_key,
                        'address': _destination.address,
                        'footprint': 0.0,
5634c975   Antoine Goutenoir   Expose travel dis...
291
                        'distance': 0.0,
584b13cc   Antoine Goutenoir   Order the results...
292
                    }
5634c975   Antoine Goutenoir   Expose travel dis...
293
294
295
296
297
298
299
300
                cities_dict[city_key]['footprint'] += footprint['co2eq_kg']
                cities_dict[city_key]['distance'] += footprint['distance']
                if city_key not in cities_sum_foot:
                    cities_sum_foot[city_key] = 0.0
                cities_sum_foot[city_key] += footprint['co2eq_kg']
                if city_key not in cities_sum_dist:
                    cities_sum_dist[city_key] = 0.0
                cities_sum_dist[city_key] += footprint['distance']
70aa301f   Antoine Goutenoir   Implement another...
301

584b13cc   Antoine Goutenoir   Order the results...
302
303
304
            cities = [cities_dict[k] for k in cities_dict.keys()]
            cities = sorted(cities, key=lambda c: c['footprint'])

70aa301f   Antoine Goutenoir   Implement another...
305
            footprints[model.slug] = {
51f564d3   Antoine Goutenoir   Add a big chunk o...
306
307
308
                'cities': cities,
            }

70aa301f   Antoine Goutenoir   Implement another...
309
        _results['footprints'] = footprints
51f564d3   Antoine Goutenoir   Add a big chunk o...
310

5634c975   Antoine Goutenoir   Expose travel dis...
311
312
        total_foot = 0.0
        total_dist = 0.0
314c65e2   Antoine Goutenoir   Implement Scenari...
313

584b13cc   Antoine Goutenoir   Order the results...
314
        cities_mean_dict = {}
5634c975   Antoine Goutenoir   Expose travel dis...
315
316
317
        for city in cities_sum_foot.keys():
            city_mean_foot = 1.0 * cities_sum_foot[city] / len(emission_models)
            city_mean_dist = 1.0 * cities_sum_dist[city] / len(emission_models)
584b13cc   Antoine Goutenoir   Order the results...
318
319
320
            cities_mean_dict[city] = {
                'address': destinations_by_city_key[city].address,
                'city': city,
5634c975   Antoine Goutenoir   Expose travel dis...
321
322
                'footprint': city_mean_foot,
                'distance': city_mean_dist,
584b13cc   Antoine Goutenoir   Order the results...
323
            }
5634c975   Antoine Goutenoir   Expose travel dis...
324
325
            total_foot += city_mean_foot
            total_dist += city_mean_dist
1d48272e   Antoine Goutenoir   Compute the mean ...
326

584b13cc   Antoine Goutenoir   Order the results...
327
328
329
        cities_mean = [cities_mean_dict[k] for k in cities_mean_dict.keys()]
        cities_mean = sorted(cities_mean, key=lambda c: c['footprint'])

91751451   Antoine Goutenoir   Shift the API to ...
330
        _results['mean_footprint'] = {  # DEPRECATED?
1d48272e   Antoine Goutenoir   Compute the mean ...
331
332
            'cities': cities_mean
        }
91751451   Antoine Goutenoir   Shift the API to ...
333
        _results['cities'] = cities_mean
1d48272e   Antoine Goutenoir   Compute the mean ...
334

5634c975   Antoine Goutenoir   Expose travel dis...
335
336
337
338
        _results['total'] = total_foot  # DEPRECATED
        _results['footprint'] = total_foot

        _results['distance'] = total_dist
314c65e2   Antoine Goutenoir   Implement Scenari...
339

70aa301f   Antoine Goutenoir   Implement another...
340
341
342
343
344
345
346
347
348
349
350
351
352
353
        return _results

    # SCENARIO A : One Origin, At Least One Destination #######################
    #
    # In this scenario, we compute the sum of each of the travels' footprint,
    # for each of the Emission Models, and present a mean of all Models.
    #
    if 1 == len(origins):
        results = compute_one_to_many(
            _origin=origins[0],
            _destinations=destinations,
            use_train_below=0,
        )

51f564d3   Antoine Goutenoir   Add a big chunk o...
354
355
356
357
    # SCENARIO B : At Least One Origin, One Destination #######################
    #
    # Same as A for now.
    #
1d48272e   Antoine Goutenoir   Compute the mean ...
358
    elif 1 == len(destinations):
70aa301f   Antoine Goutenoir   Implement another...
359
360
361
362
363
        results = compute_one_to_many(
            _origin=destinations[0],
            _destinations=origins,
            use_train_below=0,
        )
24f55cde   Antoine Goutenoir   Geocode destinati...
364

51f564d3   Antoine Goutenoir   Add a big chunk o...
365
366
367
368
    # SCENARIO C : At Least One Origin, At Least One Destination ##############
    #
    # Run Scenario A for each Destination, and expose optimum Destination.
    #
1d48272e   Antoine Goutenoir   Compute the mean ...
369
    else:
94ae2730   Antoine Goutenoir   Ignore duplicates...
370
        unique_city_keys = []
584b13cc   Antoine Goutenoir   Order the results...
371
        result_cities = []
314c65e2   Antoine Goutenoir   Implement Scenari...
372
373
374
        for destination in destinations:
            city_key = get_city_key(destination)

94ae2730   Antoine Goutenoir   Ignore duplicates...
375
376
377
378
379
            if city_key in unique_city_keys:
                continue
            else:
                unique_city_keys.append(city_key)

314c65e2   Antoine Goutenoir   Implement Scenari...
380
            city_results = compute_one_to_many(
584b13cc   Antoine Goutenoir   Order the results...
381
                _origin=destination,
314c65e2   Antoine Goutenoir   Implement Scenari...
382
383
384
385
                _destinations=origins,
                use_train_below=0,
            )
            city_results['city'] = city_key
15e57dca   Antoine Goutenoir   Naming things and...
386
            city_results['address'] = destination.address
584b13cc   Antoine Goutenoir   Order the results...
387
            result_cities.append(city_results)
314c65e2   Antoine Goutenoir   Implement Scenari...
388

584b13cc   Antoine Goutenoir   Order the results...
389
390
391
392
        result_cities = sorted(result_cities, key=lambda c: int(c['total']))
        results = {
            'cities': result_cities,
        }
1d48272e   Antoine Goutenoir   Compute the mean ...
393

a4c03d8e   Antoine Goutenoir   Add the controlle...
394
395
396
397
398
399
400
401
    # WRITE RESULTS INTO THE DATABASE #########################################

    estimation.status = StatusEnum.success
    estimation.output_yaml = yaml_dump(results)
    db.session.commit()

    # FINALLY, RESPOND ########################################################

70aa301f   Antoine Goutenoir   Implement another...
402
    response += yaml_dump(results) + "\n"
4392f295   Goutte   Update the Estima...
403

24f55cde   Antoine Goutenoir   Geocode destinati...
404
    return _respond(response)
a4c03d8e   Antoine Goutenoir   Add the controlle...
405
406


b9fc86c3   Antoine Goutenoir   Secure the admin ...
407
408
@main.route("/estimation/<public_id>.<extension>")
def consult_estimation(public_id, extension):
a4c03d8e   Antoine Goutenoir   Add the controlle...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
    try:
        estimation = Estimation.query \
            .filter_by(public_id=public_id) \
            .one()
    except sqlalchemy.orm.exc.NoResultFound:
        return abort(404)
    except Exception as e:
        # TODO: log
        return abort(500)

    # allowed_formats = ['html']
    # if format not in allowed_formats:
    #     abort(404)

e721cb31   Antoine Goutenoir   Provide a YAML fi...
423
    unavailable_statuses = [StatusEnum.pending, StatusEnum.working]
314c65e2   Antoine Goutenoir   Implement Scenari...
424

b9fc86c3   Antoine Goutenoir   Secure the admin ...
425
    if extension in ['xhtml', 'html', 'htm']:
e721cb31   Antoine Goutenoir   Provide a YAML fi...
426
427

        if estimation.status in unavailable_statuses:
a4c03d8e   Antoine Goutenoir   Add the controlle...
428
429
430
431
432
433
434
            return render_template(
                "estimation-queue-wait.html",
                estimation=estimation
            )
        else:
            return render_template(
                "estimation.html",
91751451   Antoine Goutenoir   Shift the API to ...
435
436
                estimation=estimation,
                estimation_output=estimation.get_output_dict(),
a4c03d8e   Antoine Goutenoir   Add the controlle...
437
438
            )

b9fc86c3   Antoine Goutenoir   Secure the admin ...
439
    elif extension in ['yaml', 'yml']:
e721cb31   Antoine Goutenoir   Provide a YAML fi...
440
441
442
443
444
445

        if estimation.status in unavailable_statuses:
            abort(404)

        return estimation.output_yaml

b9fc86c3   Antoine Goutenoir   Secure the admin ...
446
    elif 'csv' == extension:
a4c03d8e   Antoine Goutenoir   Add the controlle...
447

e721cb31   Antoine Goutenoir   Provide a YAML fi...
448
449
450
        if estimation.status in unavailable_statuses:
            abort(404)

a4c03d8e   Antoine Goutenoir   Add the controlle...
451
452
        si = StringIO()
        cw = csv.writer(si, quoting=csv.QUOTE_ALL)
5634c975   Antoine Goutenoir   Expose travel dis...
453
        cw.writerow([u"city", u"address", u"co2 (kg)", u"distance (km)"])
a4c03d8e   Antoine Goutenoir   Add the controlle...
454
455

        results = estimation.get_output_dict()
5634c975   Antoine Goutenoir   Expose travel dis...
456
457
458
459
460
461
462
463
        for city in results['cities']:
            cw.writerow([
                city['city'].encode(OUT_ENCODING),
                city['address'].encode(OUT_ENCODING),
                round(city['footprint'], 3),
                round(city['distance'], 3),
            ])

67f85bce   Antoine Goutenoir   Generate a CSV fi...
464
465
466
467
468
469
470
471
        # return si.getvalue().strip('\r\n')
        return Response(
            response=si.getvalue().strip('\r\n'),
            headers={
                'Content-type': 'text/csv',
                'Content-disposition': "attachment; filename=%s.csv"%public_id,
            },
        )
a4c03d8e   Antoine Goutenoir   Add the controlle...
472
473
474

    else:
        abort(404)
b9fc86c3   Antoine Goutenoir   Secure the admin ...
475
476


67f85bce   Antoine Goutenoir   Generate a CSV fi...
477
478
@main.route("/scaling_laws.csv")
def get_scaling_laws_csv():
a728e600   Antoine Goutenoir   Allow configurati...
479
    distances = content.laws_plot.distances
67f85bce   Antoine Goutenoir   Generate a CSV fi...
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
    models = get_emission_models()

    si = StringIO()
    cw = csv.writer(si, quoting=csv.QUOTE_ALL)

    header = ['distance'] + [model.slug for model in models]
    cw.writerow(header)

    for distance in distances:
        row = [distance]
        for model in models:
            row.append(model.compute_airplane_distance_footprint(distance))
        cw.writerow(row)

    return Response(
        response=si.getvalue().strip('\r\n'),
        headers={
            'Content-type': 'text/csv',
            'Content-disposition': 'attachment; filename=scaling_laws.csv',
        },
    )


b9fc86c3   Antoine Goutenoir   Secure the admin ...
503
504
505
506
507
508
@main.route("/test")
@basic_auth.required
def dev_test():
    import os

    return os.getenv('ADMIN_USERNAME')