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 %}
+
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."""