diff --git a/templates/trip/list.html b/templates/trip/list.html index f7a0580..547d1c0 100644 --- a/templates/trip/list.html +++ b/templates/trip/list.html @@ -48,11 +48,12 @@ {% endblock %} {% macro section(heading, item_list) %} - {% if item_list %} {% set items = item_list | list %}

{{ heading }}

+

Trip statistics

{{ items | count }} trips

+ {% if item_list %}
Total distance: {{ format_distance(total_distance) }}
{% for transport_type, distance in distances_by_transport_type %} diff --git a/templates/trip/stats.html b/templates/trip/stats.html new file mode 100644 index 0000000..4b535d5 --- /dev/null +++ b/templates/trip/stats.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + +{% from "macros.html" import format_distance with context %} + +{% set heading = "Trip statistics" %} + +{% block title %}{{ heading }} - Edward Betts{% endblock %} + +{% block content %} +
+

Trip statistics

+
Trips: {{ count }}
+
Total distance: {{ format_distance(total_distance) }}
+ + {% for transport_type, distance in distances_by_transport_type %} +
+ {{ transport_type | title }} + distance: {{format_distance(distance) }} +
+ {% endfor %} + + {% for year, year_stats in yearly_stats | dictsort %} +

{{ year }}

+
Trips in {{ year }}: {{ year_stats.count }}
+
Total distance in {{ year}}: {{ format_distance(year_stats.total_distance) }}
+ {% for transport_type, distance in year_stats.distances_by_transport_type.items() %} +
+ {{ transport_type | title }} + distance: {{format_distance(distance) }} +
+ {% endfor %} + {% endfor %} +
+{% endblock %} diff --git a/web_view.py b/web_view.py index 7bfde9f..5c39a50 100755 --- a/web_view.py +++ b/web_view.py @@ -526,6 +526,47 @@ def birthday_list() -> str: return flask.render_template("birthday_list.html", items=items, today=today) +def calculate_yearly_stats(trips: list[Trip]) -> dict[int, StrDict]: + """Calculate total distance and distance by transport type grouped by year.""" + yearly_stats: defaultdict[int, StrDict] = defaultdict(dict) + for trip in trips: + year = trip.start.year + dist = trip.total_distance() + yearly_stats[year].setdefault("count", 0) + yearly_stats[year]["count"] += 1 + if dist: + yearly_stats[year]["total_distance"] = ( + yearly_stats[year].get("total_distance", 0) + trip.total_distance() + ) + for transport_type, distance in trip.distances_by_transport_type(): + yearly_stats[year].setdefault("distances_by_transport_type", {}) + yearly_stats[year]["distances_by_transport_type"][transport_type] = ( + yearly_stats[year]["distances_by_transport_type"].get(transport_type, 0) + + distance + ) + return dict(yearly_stats) + + +@app.route("/trip/stats") +def trip_stats() -> str: + """Travel stats: distance and price by year and travel type.""" + route_distances = agenda.travel.load_route_distances(app.config["DATA_DIR"]) + trip_list = get_trip_list(route_distances) + today = date.today() + + past = [item for item in trip_list if (item.end or item.start) < today] + + yearly_stats = calculate_yearly_stats(past) + + return flask.render_template( + "trip/stats.html", + count=len(past), + total_distance=calc_total_distance(past), + distances_by_transport_type=sum_distances_by_transport_type(past), + yearly_stats=yearly_stats, + ) + + @app.route("/callback") def auth_callback() -> tuple[str, int] | werkzeug.Response: """Process the authentication callback."""