diff --git a/app.py b/app.py
index 8a81ded..debc5a1 100644
--- a/app.py
+++ b/app.py
@@ -226,8 +226,12 @@ def results(station_crs, slug, travel_date):
trip["eurostar_plus_price"] = es.get("plus_price")
trip["eurostar_plus_seats"] = es.get("plus_seats")
gwr_p = trip.get("ticket_price")
+ circle_svcs = trip.get("circle_services")
+ circle_fare = circle_svcs[0]["fare"] if circle_svcs else 0
trip["total_price"] = (
- gwr_p + es_price if (gwr_p is not None and es_price is not None) else None
+ gwr_p + es_price + circle_fare
+ if (gwr_p is not None and es_price is not None)
+ else None
)
# If the API returned journeys but every price is None, tickets aren't on sale yet
diff --git a/templates/results.html b/templates/results.html
index 2645e3d..4c37e33 100644
--- a/templates/results.html
+++ b/templates/results.html
@@ -181,10 +181,10 @@
{{ row.connection_duration }}{% if row.connection_minutes < 80 %} ⚠️{% endif %}
{% if row.circle_services %}
{% set c = row.circle_services[0] %}
-
Circle {{ c.depart }} → KX {{ c.arrive_kx }}
+
Circle {{ c.depart }} → KX {{ c.arrive_kx }} · £{{ "%.2f"|format(c.fare) }}
{% if row.circle_services | length > 1 %}
{% set c2 = row.circle_services[1] %}
-
next {{ c2.depart }} → KX {{ c2.arrive_kx }}
+
next {{ c2.depart }} → KX {{ c2.arrive_kx }} · £{{ "%.2f"|format(c2.fare) }}
{% endif %}
{% endif %}
diff --git a/tfl_fare.py b/tfl_fare.py
new file mode 100644
index 0000000..0d3108e
--- /dev/null
+++ b/tfl_fare.py
@@ -0,0 +1,30 @@
+"""TfL single fare calculations for journeys within Zone 1."""
+
+from datetime import datetime, time
+
+import holidays
+
+CIRCLE_LINE_PEAK = 3.10
+CIRCLE_LINE_OFF_PEAK = 3.00
+
+_ENGLAND_HOLIDAYS = holidays.country_holidays("GB", subdiv="ENG")
+
+_AM_PEAK_START = time(6, 30)
+_AM_PEAK_END = time(9, 30)
+_PM_PEAK_START = time(16, 0)
+_PM_PEAK_END = time(19, 0)
+
+
+def circle_line_fare(depart_dt: datetime) -> float:
+ """Return the TfL Circle line single fare for a given departure datetime.
+
+ Peak (£3.10): Monday–Friday (excluding public holidays),
+ 06:30–09:30 and 16:00–19:00.
+ Off-peak (£3.00): all other times, weekends, and public holidays.
+ """
+ if depart_dt.date() in _ENGLAND_HOLIDAYS or depart_dt.weekday() >= 5:
+ return CIRCLE_LINE_OFF_PEAK
+ t = depart_dt.time()
+ if _AM_PEAK_START <= t < _AM_PEAK_END or _PM_PEAK_START <= t < _PM_PEAK_END:
+ return CIRCLE_LINE_PEAK
+ return CIRCLE_LINE_OFF_PEAK
diff --git a/trip_planner.py b/trip_planner.py
index ba7948d..324a703 100644
--- a/trip_planner.py
+++ b/trip_planner.py
@@ -5,6 +5,7 @@ Combine GWR station→Paddington trains with Eurostar St Pancras→destination t
from datetime import datetime, timedelta
import circle_line
+from tfl_fare import circle_line_fare
MIN_CONNECTION_MINUTES = 50
MAX_CONNECTION_MINUTES = 110
@@ -31,7 +32,11 @@ def _circle_line_services(arrive_paddington: datetime) -> list[dict]:
)
services = circle_line.upcoming_services(earliest_board, count=2)
return [
- {"depart": dep.strftime(TIME_FMT), "arrive_kx": arr.strftime(TIME_FMT)}
+ {
+ "depart": dep.strftime(TIME_FMT),
+ "arrive_kx": arr.strftime(TIME_FMT),
+ "fare": circle_line_fare(dep),
+ }
for dep, arr in services
]