diff --git a/app.py b/app.py index 086a631..5ae8691 100644 --- a/app.py +++ b/app.py @@ -25,7 +25,6 @@ import scraper.eurostar as eurostar_scraper import scraper.gwr_fares as gwr_fares_scraper import scraper.realtime_trains as rtt_scraper from trip_planner import ( - INBOUND_MAX_CONNECTION_MINUTES, INBOUND_MIN_CONNECTION_MINUTES, combine_inbound_trips, combine_trips, @@ -35,6 +34,8 @@ from trip_planner import ( import cache import circle_line +CONNECTION_WINDOW_MINUTES = 90 + RTT_PADDINGTON_URL = ( "https://www.realtimetrains.co.uk/search/detailed/" "gb-nr:PAD/from/gb-nr:{crs}/{date}/0000-2359" @@ -88,47 +89,31 @@ DESTINATION_OPTIONS = [ {"slug": "cologne", "city": "Cologne", "destination": "Cologne Hbf"}, ] -DESTINATIONS = { - option["slug"]: option["destination"] for option in DESTINATION_OPTIONS -} +DESTINATIONS = {option["slug"]: option["destination"] for option in DESTINATION_OPTIONS} @app.route("/") def index() -> ResponseReturnValue: today = date.today().isoformat() - default_min, default_max = _get_defaults() + default_min = _get_default_min_connection() return render_template( "index.html", destination_options=DESTINATION_OPTIONS, today=today, stations=STATIONS, default_min_connection=default_min, - default_max_connection=default_max, valid_min_connections=sorted(VALID_MIN_CONNECTIONS), - valid_max_connections=sorted(VALID_MAX_CONNECTIONS), - default_return_date=(date.today() + timedelta(days=7)).isoformat(), + default_inbound_min_connection=INBOUND_MIN_CONNECTION_MINUTES, + valid_inbound_min_connections=sorted(VALID_INBOUND_MIN_CONNECTIONS), + valid_inbound_return_min_connections=sorted( + VALID_INBOUND_RETURN_MIN_CONNECTIONS + ), ) VALID_MIN_CONNECTIONS = {45, 50, 60, 70, 80, 90, 100, 110, 120} -VALID_MAX_CONNECTIONS = {60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180} VALID_INBOUND_MIN_CONNECTIONS = {20, 30, 40, 45, 50, 60, 70, 80, 90, 100, 110, 120} VALID_INBOUND_RETURN_MIN_CONNECTIONS = {30, 40, 50, 60} -VALID_INBOUND_MAX_CONNECTIONS = { - 60, - 70, - 80, - 90, - 100, - 110, - 120, - 130, - 140, - 150, - 160, - 170, - 180, -} VALID_JOURNEY_TYPES = {"outbound", "inbound", "return"} VALID_NR_CLASSES = {"walkon", "advance_std", "advance_1st"} VALID_ES_CLASSES = {"standard", "plus"} @@ -284,11 +269,12 @@ def _eurostar_price_status(price: Any, seats: Any) -> str | None: return "price_not_returned" -def _get_defaults() -> tuple[int, int]: - return ( - app.config["DEFAULT_MIN_CONNECTION"], - app.config["DEFAULT_MAX_CONNECTION"], - ) +def _get_default_min_connection() -> int: + return app.config["DEFAULT_MIN_CONNECTION"] + + +def _max_connection_for(min_connection: int) -> int: + return min_connection + CONNECTION_WINDOW_MINUTES def _parse_connection(raw: str | None, default: int, valid_set: set[int]) -> int: @@ -417,22 +403,21 @@ def search() -> ResponseReturnValue: if station_crs not in STATION_BY_CRS: station_crs = "BRI" if journey_type == "inbound": - default_min, default_max = ( - INBOUND_MIN_CONNECTION_MINUTES, - INBOUND_MAX_CONNECTION_MINUTES, - ) - valid_min, valid_max = ( - VALID_INBOUND_MIN_CONNECTIONS, - VALID_INBOUND_MAX_CONNECTIONS, - ) + default_min = INBOUND_MIN_CONNECTION_MINUTES + valid_min = VALID_INBOUND_MIN_CONNECTIONS else: - default_min, default_max = _get_defaults() - valid_min, valid_max = VALID_MIN_CONNECTIONS, VALID_MAX_CONNECTIONS - min_conn = _parse_connection( - request.args.get("min_connection"), default_min, valid_min + default_min = _get_default_min_connection() + valid_min = VALID_MIN_CONNECTIONS + min_connection_arg = ( + request.args.get("min_connection_back") + if journey_type == "inbound" + else request.args.get("min_connection") ) - max_conn = _parse_connection( - request.args.get("max_connection"), default_max, valid_max + min_conn = _parse_connection(min_connection_arg, default_min, valid_min) + inbound_min_conn = _parse_connection( + request.args.get("min_connection_in"), + INBOUND_MIN_CONNECTION_MINUTES, + VALID_INBOUND_RETURN_MIN_CONNECTIONS, ) nr_class = request.args.get("nr_class", DEFAULT_NR_CLASS) if nr_class not in VALID_NR_CLASSES: @@ -461,7 +446,12 @@ def search() -> ResponseReturnValue: journey_type=journey_type, return_date=return_date if journey_type == "return" else None, min_connection=None if min_conn == default_min else min_conn, - max_connection=None if max_conn == default_max else max_conn, + min_connection_in=( + None + if journey_type != "return" + or inbound_min_conn == INBOUND_MIN_CONNECTION_MINUTES + else inbound_min_conn + ), nr_class=None if nr_class == DEFAULT_NR_CLASS else nr_class, es_class=None if es_class == DEFAULT_ES_CLASS else es_class, ) @@ -512,23 +502,15 @@ def _results( return redirect(url_for("index")) if journey_type == "inbound": - default_min, default_max = ( - INBOUND_MIN_CONNECTION_MINUTES, - INBOUND_MAX_CONNECTION_MINUTES, - ) - valid_min, valid_max = ( - VALID_INBOUND_MIN_CONNECTIONS, - VALID_INBOUND_MAX_CONNECTIONS, - ) + default_min = INBOUND_MIN_CONNECTION_MINUTES + valid_min = VALID_INBOUND_MIN_CONNECTIONS else: - default_min, default_max = _get_defaults() - valid_min, valid_max = VALID_MIN_CONNECTIONS, VALID_MAX_CONNECTIONS + default_min = _get_default_min_connection() + valid_min = VALID_MIN_CONNECTIONS min_connection = _parse_connection( request.args.get("min_connection"), default_min, valid_min ) - max_connection = _parse_connection( - request.args.get("max_connection"), default_max, valid_max - ) + max_connection = _max_connection_for(min_connection) nr_class = request.args.get("nr_class", DEFAULT_NR_CLASS) if nr_class not in VALID_NR_CLASSES: nr_class = DEFAULT_NR_CLASS @@ -587,7 +569,6 @@ def _results( departure_station_name=departure_station_name, journey_type=journey_type, travel_date_display=travel_date_display, - return_date=return_date, return_date_display=return_date_display, stream_url=_results_url( station_crs=station_crs, @@ -684,7 +665,7 @@ def _results( section_max_connection = max_connection if journey_type == "return" and direction == "inbound": section_min_connection = inbound_min_connection - section_max_connection = INBOUND_MAX_CONNECTION_MINUTES + section_max_connection = _max_connection_for(inbound_min_connection) rtt_direction = ( "to_paddington" if direction == "outbound" else "from_paddington" ) @@ -752,6 +733,8 @@ def _results( cached_walkon = ( exact_walkon if exact_walkon is not None else get_cached(walkon_weekday_key) ) + if isinstance(cached_walkon, dict): + gwr_fares = cached_walkon if direction == "outbound": trips = combine_trips( @@ -899,7 +882,6 @@ def _results( rtt_station_url = RTT_STATION_URL.format(crs=station_crs, date=travel_date) url_min = None if min_connection == default_min else min_connection - url_max = None if max_connection == default_max else max_connection url_nr = None if nr_class == DEFAULT_NR_CLASS else nr_class url_es = None if es_class == DEFAULT_ES_CLASS else es_class @@ -908,7 +890,6 @@ def _results( "journey_type": journey_type, "return_date": return_date, "min_connection": url_min, - "max_connection": url_max, "min_connection_in": ( None if inbound_min_connection == INBOUND_MIN_CONNECTION_MINUTES @@ -932,7 +913,6 @@ def _results( "journey_type": journey_type, "return_date": return_date, "min_connection": url_min, - "max_connection": url_max, "nr_class": url_nr, "es_class": url_es, } @@ -1005,7 +985,7 @@ def _results( "id": "inbound", "direction": "inbound", "min_connection": inbound_min_connection, - "max_connection": INBOUND_MAX_CONNECTION_MINUTES, + "max_connection": _max_connection_for(inbound_min_connection), }, ] shell_nr_classes = {"outbound": nr_class_out, "inbound": nr_class_in} @@ -1032,8 +1012,6 @@ def _results( journey_type=journey_type, destination=destination, departure_station_name=departure_station_name, - travel_date=travel_date, - return_date=return_date, travel_date_display=travel_date_display, return_date_display=return_date_display, slug=slug, @@ -1055,12 +1033,9 @@ def _results( rtt_url=rtt_url, rtt_station_url=rtt_station_url, min_connection=min_connection, - max_connection=max_connection, default_min_connection=default_min, - default_max_connection=default_max, default_inbound_min_connection=INBOUND_MIN_CONNECTION_MINUTES, valid_min_connections=sorted(valid_min), - valid_max_connections=sorted(valid_max), inbound_min_connection=inbound_min_connection, valid_inbound_return_min_connections=sorted( VALID_INBOUND_RETURN_MIN_CONNECTIONS @@ -1096,6 +1071,8 @@ def _results( section=section, destination=destination, departure_station_name=departure_station_name, + nr_classes=shell_nr_classes, + es_classes=shell_es_classes, ) yield f"data: {json.dumps({'type': 'section', 'id': section_id, 'html': section_html, 'trip_fares': _section_trip_fares(section), 'advance_fares': section['advance_fares'], 'walkon_cached_fares': section.get('cached_walkon_fares'), 'walkon_api_url': section['walkon_api_url'], 'advance_api_url': section['advance_api_url'], 'advance_stream_url': section['advance_stream_url']})}\n\n" @@ -1186,7 +1163,6 @@ def _results( rtt_station_url = RTT_STATION_URL.format(crs=station_crs, date=travel_date) url_min = None if min_connection == default_min else min_connection - url_max = None if max_connection == default_max else max_connection url_nr = None if nr_class == DEFAULT_NR_CLASS else nr_class url_es = None if es_class == DEFAULT_ES_CLASS else es_class common_url_args: dict[str, Any] @@ -1195,7 +1171,6 @@ def _results( "journey_type": journey_type, "return_date": return_date, "min_connection": url_min, - "max_connection": url_max, "min_connection_in": ( None if inbound_min_connection == INBOUND_MIN_CONNECTION_MINUTES @@ -1211,7 +1186,6 @@ def _results( "journey_type": journey_type, "return_date": return_date, "min_connection": url_min, - "max_connection": url_max, "nr_class": url_nr, "es_class": url_es, } @@ -1342,19 +1316,10 @@ def _results( return render_template( "results.html", sections=sections, - trips=sections[0]["trips"] if sections else [], - result_rows=sections[0]["rows"] if sections else [], - unreachable_morning_services=[], - destinations=DESTINATIONS, destination=destination, - travel_date=travel_date, - return_date=return_date, journey_type=journey_type, slug=slug, - station_crs=station_crs, departure_station_name=departure_station_name, - prev_date=prev_date, - next_date=next_date, prev_results_url=prev_results_url, next_results_url=next_results_url, prev_outbound_url=prev_outbound_url, @@ -1375,15 +1340,7 @@ def _results( rtt_url=rtt_url, rtt_station_url=rtt_station_url, min_connection=min_connection, - max_connection=max_connection, default_min_connection=default_min, - default_max_connection=default_max, - url_min_connection=url_min, - url_max_connection=url_max, - nr_class=nr_class, - es_class=es_class, - url_nr_class=url_nr, - url_es_class=url_es, nr_classes=nr_classes, es_classes=es_classes, nr_classes_json=json.dumps(nr_classes), @@ -1396,14 +1353,7 @@ def _results( advance_api_urls_json=json.dumps(advance_api_urls), advance_stream_urls_json=json.dumps(advance_stream_urls), timetable_refresh_url=timetable_refresh_url, - advance_fares_api_url=url_for( - "api_advance_fares", station_crs=station_crs, travel_date=travel_date - ), - advance_fares_stream_url=url_for( - "api_advance_fares_stream", station_crs=station_crs, travel_date=travel_date - ), valid_min_connections=sorted(valid_min), - valid_max_connections=sorted(valid_max), inbound_min_connection=inbound_min_connection, default_inbound_min_connection=INBOUND_MIN_CONNECTION_MINUTES, valid_inbound_return_min_connections=sorted( diff --git a/config/default.py b/config/default.py index 3d9c4e2..c249656 100644 --- a/config/default.py +++ b/config/default.py @@ -11,4 +11,3 @@ CIRCLE_LINE_XML = os.path.join(TFL_DATA_DIR, "output_txc_01CIR_.xml") # Default connection window (minutes) between Paddington arrival and St Pancras departure DEFAULT_MIN_CONNECTION = 70 -DEFAULT_MAX_CONNECTION = 150 diff --git a/templates/base.html b/templates/base.html index 5aeafa9..4afdefd 100644 --- a/templates/base.html +++ b/templates/base.html @@ -342,7 +342,7 @@ tr.row-selectable:hover:not(.row-selected) { filter: brightness(0.97); } /* Journey flow arrow between column headers */ - .results-table thead th.flow-step { position: relative; padding-right: 1.4rem; } + .results-table thead th.flow-step { position: sticky; padding-right: 1.4rem; } .results-table thead th.flow-step::after { content: '›'; position: absolute; right: 0.2rem; top: 50%; transform: translateY(-50%); diff --git a/templates/index.html b/templates/index.html index 8d29dfe..a3e495c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,6 +1,8 @@ {% extends "base.html" %} {% block content %}