Skip to content

Commit

Permalink
feat(TripPlanner): render Massport and Logan Exp. icons and more (#2123)
Browse files Browse the repository at this point in the history
* cleanup(TripPlanner.OpenTripPlanner): consolidate

- cleanup the config values
- rename TripPlan.Api.OpenTripPlanner to TripPlanner.OpenTripPlanner and
-   make it more responsible for fetching and parsing OTP results by moving relevant functionality from Dotcom.TripPlan.Query
- add some testing

* deps(mix): update open_trip_planner_client & deps

* (incomplete) refactor(TripPlanner.Parser): use OTP data

- the upgraded OTP client returns more data, which can be parsed here in lieu of making our own Routes.Repo or Stops.Repo calls to supplement the data.
- use Agency info to populate route.external_agency_name
- special parsing for route names for Logan Express and Massport
- overwrite route colors for Logan Express routes because their GTFS doesn't match their website branding (!)
- convert miles and minutes units here instead of in the views

refactor(TripPlanner.FarePasses): extract the fare/passes computing into its own module
- add Massport/Logan Express fares

* feat(TripPlanner): render Massport and Logan Exp. icons and more

fix everything else to work with the following changes:
- chore(TripPlan.IntermediateStop): use stop struct instead of stop_id
- chore(TripPlan.Location): use stop struct instead of stop_id
- chore(TripPlan.NamedPosition): use stop struct instead of stop_id
- chore(TripPlan.TransitDetail): use route struct instead of route_id

* tests(TripPlanner): refactor using OpenTripPlannerClient.Factory functions

- fix(Fares): correct fare atom for rail replacement buses

* fixup(TripPlanner): render Massport and Logan Exp. icons and more
  • Loading branch information
thecristen authored Jul 11, 2024
1 parent 6e26036 commit 1f0e83a
Show file tree
Hide file tree
Showing 70 changed files with 1,687 additions and 2,051 deletions.
5 changes: 5 additions & 0 deletions assets/css/_stop-bubbles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ $location-line-width: $space-6;
@include stop-bubble-mode-color('.green-line-e', $brand-green-line);
@include stop-bubble-mode-color('.bus', $brand-bus);
@include stop-bubble-mode-color('.logan-express', $brand-logan-express);
@include stop-bubble-mode-color('.logan-express-FH', #ff505d);
@include stop-bubble-mode-color('.logan-express-BB', #f16823);
@include stop-bubble-mode-color('.logan-express-BT', #0055a0);
@include stop-bubble-mode-color('.logan-express-WO', #00954c);
@include stop-bubble-mode-color('.logan-express-PB', #704c9f);
@include stop-bubble-mode-color('.massport-shuttle', $brand-massport-shuttle);
@include stop-bubble-mode-color('.silver-line', $brand-silver-line);
@include stop-bubble-mode-color('.ferry', $brand-ferry);
Expand Down
1 change: 1 addition & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ config :dotcom, :redis, Dotcom.Cache.Multilevel.Redis
config :dotcom, :redix, Redix
config :dotcom, :redix_pub_sub, Redix.PubSub

config :dotcom, :otp_module, OpenTripPlannerClient
config :dotcom, :req_module, Req

for config_file <- Path.wildcard("config/{deps,dotcom}/*.exs") do
Expand Down
5 changes: 0 additions & 5 deletions config/dotcom/trip_planner.exs

This file was deleted.

1 change: 1 addition & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ config :dotcom, :redis, Dotcom.Redis.Mock
config :dotcom, :redix, Dotcom.Redix.Mock
config :dotcom, :redix_pub_sub, Dotcom.Redix.PubSub.Mock

config :dotcom, :otp_module, OpenTripPlannerClient.Mock
config :dotcom, :req_module, Req.Mock

config :dotcom, :trip_plan_feedback_cache, Dotcom.Cache.TestCache
Expand Down
35 changes: 15 additions & 20 deletions lib/dotcom/trip_plan/alerts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,12 @@ defmodule Dotcom.TripPlan.Alerts do
alias Alerts.InformedEntity, as: IE
alias TripPlan.{Itinerary, Leg, TransitDetail}

@default_opts [
trip_by_id: &Schedules.Repo.trip/1
]
@routes_repo Application.compile_env!(:dotcom, :repo_modules)[:routes]

@doc "Filters a list of Alerts to those relevant to the Itinerary"
@spec filter_for_itinerary([Alert.t()], Itinerary.t(), Keyword.t()) :: [Alert.t()]
def filter_for_itinerary(alerts, itinerary, opts \\ []) do
opts = Keyword.merge(@default_opts, opts)

@spec filter_for_itinerary([Alert.t()], Itinerary.t()) :: [Alert.t()]
def filter_for_itinerary(alerts, itinerary) do
Alerts.Match.match(
alerts,
Enum.concat(intermediate_entities(itinerary), entities(itinerary, opts)),
Enum.concat(intermediate_entities(itinerary), entities(itinerary)),
itinerary.start
)
end
Expand All @@ -35,23 +28,25 @@ defmodule Dotcom.TripPlan.Alerts do
|> Enum.map(&%IE{stop: &1})
end

@spec entities(Itinerary.t(), Keyword.t()) :: [IE.t()]
defp entities(itinerary, opts) do
@spec entities(Itinerary.t()) :: [IE.t()]
defp entities(itinerary) do
itinerary
|> Enum.flat_map(&leg_entities(&1, opts))
|> Enum.flat_map(&leg_entities(&1))
|> Enum.uniq()
end

defp leg_entities(%Leg{mode: mode} = leg, opts) do
for entity <- mode_entities(mode, opts),
defp leg_entities(%Leg{mode: mode} = leg) do
for entity <- mode_entities(mode),
stop_id <- Leg.stop_ids(leg) do
%{entity | stop: stop_id}
end
end

defp mode_entities(%TransitDetail{route_id: route_id, trip_id: trip_id}, opts) do
route = @routes_repo.get(route_id)
trip = Keyword.get(opts, :trip_by_id).(trip_id)
defp mode_entities(%TransitDetail{route: route, trip_id: trip_id}) do
trip =
if is_nil(route.external_agency_name) do
Schedules.Repo.trip(trip_id)
end

route_type =
if route do
Expand All @@ -63,10 +58,10 @@ defmodule Dotcom.TripPlan.Alerts do
trip.direction_id
end

[%IE{route_type: route_type, route: route_id, trip: trip_id, direction_id: direction_id}]
[%IE{route_type: route_type, route: route.id, trip: trip_id, direction_id: direction_id}]
end

defp mode_entities(_, _opts) do
defp mode_entities(_) do
[]
end
end
4 changes: 2 additions & 2 deletions lib/dotcom/trip_plan/intermediate_stop.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
defmodule Dotcom.TripPlan.IntermediateStop do
defstruct description: nil,
stop_id: nil,
stop: nil,
alerts: []

@type t :: %__MODULE__{
description: iodata,
stop_id: Stops.Stop.id_t(),
stop: Stops.Stop.t(),
alerts: [Alerts.Alert.t()]
}
end
51 changes: 24 additions & 27 deletions lib/dotcom/trip_plan/itinerary_row.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ defmodule Dotcom.TripPlan.ItineraryRow do
distance: 0.0,
duration: 0

@routes_repo Application.compile_env!(:dotcom, :repo_modules)[:routes]

@typep name_and_id :: {String.t(), String.t() | nil}
@typep step :: String.t()
@type t :: %__MODULE__{
Expand All @@ -32,9 +34,6 @@ defmodule Dotcom.TripPlan.ItineraryRow do
duration: Integer.t()
}

@routes_repo Application.compile_env!(:dotcom, :repo_modules)[:routes]
@stops_repo Application.compile_env!(:dotcom, :repo_modules)[:stops]

defmodule Dependencies do
@moduledoc false

Expand All @@ -56,16 +55,24 @@ defmodule Dotcom.TripPlan.ItineraryRow do
def route_type(%__MODULE__{route: %Route{type: type}}), do: type
def route_type(_row), do: nil

def route_name(%__MODULE__{route: %Route{external_agency_name: agency, long_name: name}})
when is_binary(agency) and is_binary(name),
do: name

def route_name(%__MODULE__{route: %Route{name: name}}), do: name
def route_name(_row), do: nil

@doc """
Builds an ItineraryRow struct from the given leg and options
"""
@spec from_leg(Leg.t(), Dependencies.t(), Leg.t() | nil) :: t
def from_leg(leg, deps, next_leg) do
trip = leg |> Leg.trip_id() |> parse_trip_id(deps.trip_mapper)
route = leg |> Leg.route_id() |> parse_route_id()
@spec from_leg(Leg.t(), Leg.t() | nil) :: t
def from_leg(leg, next_leg) do
transit? = Leg.transit?(leg)
route = if(transit?, do: leg.mode.route)

trip =
if(route && is_nil(route.external_agency_name), do: leg |> Leg.trip_id() |> parse_trip_id())

stop = name_from_position(leg.from)

%__MODULE__{
Expand Down Expand Up @@ -134,12 +141,12 @@ defmodule Dotcom.TripPlan.ItineraryRow do
Enum.map(steps, &match_step(&1, alerts))
end

def match_step(%IntermediateStop{stop_id: nil} = step, _alerts) do
def match_step(%IntermediateStop{stop: nil} = step, _alerts) do
step
end

def match_step(step, alerts) do
%{step | alerts: Alerts.Stop.match(alerts, step.stop_id, activities: ~w(ride)a)}
%{step | alerts: Alerts.Stop.match(alerts, step.stop.id, activities: ~w(ride)a)}
end

def intermediate_alerts?(%__MODULE__{steps: steps}) do
Expand All @@ -148,12 +155,8 @@ defmodule Dotcom.TripPlan.ItineraryRow do

@spec name_from_position(NamedPosition.t()) ::
{String.t(), String.t()}
def name_from_position(%NamedPosition{stop_id: stop_id, name: name})
when not is_nil(stop_id) do
case @stops_repo.get_parent(stop_id) do
nil -> {name, nil}
stop -> {stop.name, stop.id}
end
def name_from_position(%NamedPosition{stop: %Stops.Stop{id: id}, name: name}) do
{name, id}
end

def name_from_position(%NamedPosition{name: name}) do
Expand All @@ -168,25 +171,19 @@ defmodule Dotcom.TripPlan.ItineraryRow do
defp get_steps(%PersonalDetail{steps: steps}, _next_leg),
do: Enum.map(steps, &format_personal_to_personal_step/1)

defp get_steps(%TransitDetail{intermediate_stop_ids: stop_ids}, _next_leg) do
for {:ok, stop} <- Task.async_stream(stop_ids, fn id -> @stops_repo.get_parent(id) end),
stop do
defp get_steps(%TransitDetail{intermediate_stops: stops}, _next_leg) do
for stop <- stops, stop do
%IntermediateStop{
description: stop.name,
stop_id: stop.id
stop: stop
}
end
end

@spec parse_route_id(:error | {:ok, String.t()}) ::
Routes.Route.t() | nil
defp parse_route_id(:error), do: nil
defp parse_route_id({:ok, route_id}), do: @routes_repo.get(route_id)

@spec parse_trip_id(:error | {:ok, String.t()}, Dependencies.trip_mapper()) ::
@spec parse_trip_id(:error | {:ok, String.t()}) ::
Schedules.Trip.t() | nil
defp parse_trip_id(:error, _trip_mapper), do: nil
defp parse_trip_id({:ok, trip_id}, trip_mapper), do: trip_mapper.(trip_id)
defp parse_trip_id(:error), do: nil
defp parse_trip_id({:ok, trip_id}), do: Schedules.Repo.trip(trip_id)

defp format_personal_to_personal_step(%{relative_direction: :depart, street_name: "Transfer"}),
do: %IntermediateStop{description: "Depart"}
Expand Down
26 changes: 12 additions & 14 deletions lib/dotcom/trip_plan/itinerary_row_list.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ defmodule Dotcom.TripPlan.ItineraryRowList do
%Itinerary{legs: legs, accessible?: accessible?} = itinerary,
opts \\ []
) do
deps = %ItineraryRow.Dependencies{}
alerts = get_alerts(itinerary, deps)
rows = get_rows(itinerary, deps, opts, alerts)
alerts = get_alerts(itinerary)
rows = get_rows(itinerary, opts, alerts)

%__MODULE__{
rows: rows,
Expand All @@ -45,36 +44,35 @@ defmodule Dotcom.TripPlan.ItineraryRowList do
}
end

@spec get_rows(Itinerary.t(), ItineraryRow.Dependencies.t(), opts, [Alerts.Alert.t()]) :: [
@spec get_rows(Itinerary.t(), opts, [Alerts.Alert.t()]) :: [
ItineraryRow.t()
]
defp get_rows(itinerary, deps, opts, alerts) do
defp get_rows(itinerary, opts, alerts) do
rows =
for {leg, index} <- Enum.with_index(itinerary.legs) do
leg
|> ItineraryRow.from_leg(deps, Enum.at(itinerary.legs, index + 1))
|> ItineraryRow.from_leg(Enum.at(itinerary.legs, index + 1))
|> ItineraryRow.fetch_alerts(alerts)
end

update_from_name(rows, opts[:from])
end

@spec get_alerts(Itinerary.t(), ItineraryRow.Dependencies.t()) :: [Alerts.Alert.t()]
defp get_alerts(itinerary, deps) do
@spec get_alerts(Itinerary.t()) :: [Alerts.Alert.t()]
defp get_alerts(itinerary) do
itinerary.start
|> deps.alerts_repo.()
|> Dotcom.TripPlan.Alerts.filter_for_itinerary(
itinerary,
trip_by_id: deps.trip_mapper
)
|> Alerts.Repo.all()
|> Dotcom.TripPlan.Alerts.filter_for_itinerary(itinerary)
end

@spec get_destination([TripPlan.Leg.t()], Keyword.t(), [Alerts.Alert.t()]) :: destination
defp get_destination(legs, opts, alerts) do
last_leg = List.last(legs)

{name, stop_id} =
last_leg |> Map.get(:to) |> ItineraryRow.name_from_position()
last_leg
|> Map.get(:to)
|> ItineraryRow.name_from_position()

alerts = Alerts.Stop.match(alerts, stop_id)
{destination_name(name, opts[:to]), stop_id, last_leg.stop, alerts}
Expand Down
6 changes: 2 additions & 4 deletions lib/dotcom/trip_plan/location.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule Dotcom.TripPlan.Location do
alias TripPlan.NamedPosition

@location_service Application.compile_env!(:dotcom, :location_service)
@stops_repo Application.compile_env!(:dotcom, :repo_modules)[:stops]

@spec validate(Query.t(), map) :: Query.t()
def validate(
Expand Down Expand Up @@ -80,7 +81,7 @@ defmodule Dotcom.TripPlan.Location do
latitude: lat,
longitude: lng,
name: encode_name(name),
stop_id: nil_if_empty(stop_id)
stop: if(stop_id && stop_id != "", do: @stops_repo.get(stop_id))
}

query
Expand All @@ -92,9 +93,6 @@ defmodule Dotcom.TripPlan.Location do
end
end

defp nil_if_empty(""), do: nil
defp nil_if_empty(value), do: value

@spec encode_name(String.t()) :: String.t()
defp encode_name(name) do
name
Expand Down
Loading

0 comments on commit 1f0e83a

Please sign in to comment.