From a68df381fe64664522d87f8af742f7b1e4e0d0dc Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Tue, 15 Jul 2025 16:57:19 +0200 Subject: [PATCH] Add CO2 breakdown by transport type to trip list and detail pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements bug #194 by adding CO2 emission display by transport type: - Add co2_by_transport_type() method to Trip class - Display CO2 breakdown on trip list page and individual trip items - Display CO2 breakdown on individual trip detail pages - Show both total CO2 and breakdown by transport type (flight/train/ferry) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- agenda/types.py | 15 +++++++++++++++ templates/macros.html | 10 ++++++++++ templates/trip/list.html | 7 +++++++ templates/trip_page.html | 14 ++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/agenda/types.py b/agenda/types.py index 9c217b1..6ba7f23 100644 --- a/agenda/types.py +++ b/agenda/types.py @@ -230,6 +230,21 @@ class Trip: return list(transport_distances.items()) + def co2_by_transport_type(self) -> list[tuple[str, float]]: + """Calculate the total CO₂ emissions for each type of transport. + + Any travel item with a missing or None 'co2_kg' field is ignored. + """ + transport_co2: defaultdict[str, float] = defaultdict(float) + + for item in self.travel: + co2_kg = item.get("co2_kg") + if co2_kg: + transport_type: str = item.get("type", "unknown") + transport_co2[transport_type] += float(co2_kg) + + return list(transport_co2.items()) + def elements(self) -> list[TripElement]: """Trip elements ordered by time.""" elements: list[TripElement] = [] diff --git a/templates/macros.html b/templates/macros.html index d5592a6..5bdadc5 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -351,6 +351,16 @@
Total COâ‚‚: {{ "{:,.1f}".format(total_co2_kg) }} kg
{% endif %} + {% set co2_by_transport = trip.co2_by_transport_type() %} + {% if co2_by_transport %} + {% for transport_type, co2_kg in co2_by_transport %} +
+ {{ transport_type | title }} + COâ‚‚: {{ "{:,.1f}".format(co2_kg) }} kg +
+ {% endfor %} + {% endif %} + {% if trip.schengen_compliance %}
Schengen: diff --git a/templates/trip/list.html b/templates/trip/list.html index 2e9fe5a..0eaf27f 100644 --- a/templates/trip/list.html +++ b/templates/trip/list.html @@ -64,6 +64,13 @@ {% endfor %}
Total COâ‚‚: {{ "{:,.1f}".format(total_co2_kg / 1000.0) }} tonnes
+ {% for transport_type, co2_kg in co2_by_transport_type %} +
+ {{ transport_type | title }} + COâ‚‚: {{ "{:,.1f}".format(co2_kg) }} kg +
+ {% endfor %} + {% for trip in items %} {{ trip_item(trip) }} {% endfor %} diff --git a/templates/trip_page.html b/templates/trip_page.html index 23d48c3..d7f0295 100644 --- a/templates/trip_page.html +++ b/templates/trip_page.html @@ -78,6 +78,8 @@ {% set end = trip.end %} {% set total_distance = trip.total_distance() %} {% set distances_by_transport_type = trip.distances_by_transport_type() %} +{% set total_co2_kg = trip.total_co2_kg() %} +{% set co2_by_transport_type = trip.co2_by_transport_type() %} {% block content %}
@@ -112,6 +114,18 @@ {% endfor %} {% endif %} + {% if total_co2_kg %} +
Total COâ‚‚: {{ "{:,.1f}".format(total_co2_kg) }} kg
+ {% endif %} + + {% if co2_by_transport_type %} + {% for transport_type, co2_kg in co2_by_transport_type %} +
{{ transport_type | title }} COâ‚‚: + {{ "{:,.1f}".format(co2_kg) }} kg +
+ {% endfor %} + {% endif %} + {% set delta = human_readable_delta(trip.start) %} {% if delta %}