Skip to content

Commit

Permalink
fix: Handle platform closures (#2107)
Browse files Browse the repository at this point in the history
* Added field to struct.

* Added fetch for all platform IDs at a station.

* Added edge cases so widgets can handle single platform closures.

* Added PreFare alert tests.

* Added SubwayStatus tests.

* Credo.

* Fixed check so function can be used by WidgetInstances.

* Fixed platform closure logic for SubwayStatus.

* Name changes.

* Refactor to pattern match

Co-authored-by: sloane <[email protected]>

* Changed reject to filter.

* Formatting.

* Fixed route selection for station closures at JFK.

* Added cldr_messages to help with pluralizing strings.

* Pluralized strings.

* Added more tests.

* Fixed platform names.

* Changed how we determine if ie is for a subway platform.

* Fixed tests.

* Added platform support for GL.

* Use existing function.

* No single pipes.

* Exclude partial closures from dual_screen_alert?.

* Added typespecs.

* Added a struct to describe alerts WidgetInstance.SubwayStatus expects.

* Added provider so we don't see warnings.

* Moved partial closure logic to CG.

* Added a comment.

* Added better type.

* Fixed default.

---------

Co-authored-by: sloane <[email protected]>
  • Loading branch information
cmaddox5 and sloanelybutsurely authored Jul 18, 2024
1 parent 24fb7a6 commit ddfe9ab
Show file tree
Hide file tree
Showing 14 changed files with 1,073 additions and 472 deletions.
5 changes: 5 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ config :ueberauth, Ueberauth,
keycloak: nil
]

config :ex_cldr,
default_locale: "en",
default_backend: Screens.Cldr,
json_library: Jason

config :screens,
gds_dms_username: "[email protected]",
config_fetcher: Screens.Config.Fetch.S3,
Expand Down
29 changes: 29 additions & 0 deletions lib/screens/alerts/alert.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule Screens.Alerts.Alert do
@moduledoc false

alias Screens.Alerts.InformedEntity
alias Screens.Routes.Route
alias Screens.RouteType
alias Screens.Stops.Stop
Expand Down Expand Up @@ -592,4 +593,32 @@ defmodule Screens.Alerts.Alert do

def direction_id(%__MODULE__{informed_entities: informed_entities}),
do: List.first(informed_entities).direction_id

def informed_parent_stations(%__MODULE__{
informed_entities: informed_entities
}) do
Enum.filter(informed_entities, &InformedEntity.parent_station?/1)
end

# Although Alerts UI allows you to create partial closures affecting multiple stations,
# we are assuming that will never happen.
@spec is_partial_station_closure?(__MODULE__.t(), list(Stop.t())) :: boolean()
def is_partial_station_closure?(
%__MODULE__{effect: :station_closure, informed_entities: informed_entities} = alert,
all_platforms_at_informed_station
) do
informed_parent_stations = informed_parent_stations(alert)

case informed_parent_stations do
[_] ->
platform_ids = Enum.map(all_platforms_at_informed_station, & &1.id)
informed_platforms = Enum.filter(informed_entities, &(&1.stop in platform_ids))
length(informed_platforms) != length(all_platforms_at_informed_station)

_ ->
false
end
end

def is_partial_station_closure?(_, _), do: false
end
13 changes: 13 additions & 0 deletions lib/screens/cldr.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Screens.Cldr do
@moduledoc """
Define a backend module that will host our
Cldr configuration and public API.
Most function calls in Cldr will be calls
to functions on this module.
"""
use Cldr,
locales: ["en"],
default_locale: "en",
providers: [Cldr.Number]
end
9 changes: 7 additions & 2 deletions lib/screens/stops/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ defmodule Screens.Stops.Parser do

def parse_stop(%{
"id" => id,
"attributes" => %{"name" => name, "platform_code" => platform_code}
"attributes" => %{
"name" => name,
"platform_code" => platform_code,
"platform_name" => platform_name
}
}) do
%Screens.Stops.Stop{
id: id,
name: name,
platform_code: platform_code
platform_code: platform_code,
platform_name: platform_name
}
end
end
24 changes: 21 additions & 3 deletions lib/screens/stops/stop.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule Screens.Stops.Stop do

alias Screens.LocationContext
alias Screens.RoutePatterns.RoutePattern
alias Screens.Routes
alias Screens.{Routes, Stops}
alias Screens.Routes.Route
alias Screens.RouteType
alias Screens.Stops.StationsWithRoutesAgent
Expand All @@ -22,14 +22,16 @@ defmodule Screens.Stops.Stop do

defstruct id: nil,
name: nil,
platform_code: nil
platform_code: nil,
platform_name: nil

@type id :: String.t()

@type t :: %__MODULE__{
id: id,
name: String.t(),
platform_code: String.t() | nil
platform_code: String.t() | nil,
platform_name: String.t() | nil
}

@type screen_type :: BusEink | BusShelter | GlEink | PreFare | Dup | Triptych
Expand Down Expand Up @@ -361,6 +363,22 @@ defmodule Screens.Stops.Stop do
end
end

def fetch_subway_platforms_for_stop(stop_id) do
case Screens.V3Api.get_json("stops/" <> stop_id, %{"include" => "child_stops"}) do
{:ok, %{"included" => child_stop_data}} ->
child_stop_data
|> Enum.filter(fn %{
"attributes" => %{
"location_type" => location_type,
"vehicle_type" => vehicle_type
}
} ->
location_type == 0 and vehicle_type in [0, 1]
end)
|> Enum.map(&Stops.Parser.parse_stop/1)
end
end

# --- END API functions ---

def stop_on_route?(stop_id, stop_sequence) when not is_nil(stop_id) do
Expand Down
31 changes: 27 additions & 4 deletions lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do
now \\ DateTime.utc_now(),
fetch_alerts_fn \\ &Alert.fetch/1,
fetch_stop_name_fn \\ &Stop.fetch_stop_name/1,
fetch_location_context_fn \\ &Stop.fetch_location_context/3
fetch_location_context_fn \\ &Stop.fetch_location_context/3,
fetch_subway_platforms_for_stop_fn \\ &Stop.fetch_subway_platforms_for_stop/1
) do
%PreFare{
reconstructed_alert_widget: %CurrentStopId{stop_id: stop_id}
Expand All @@ -75,7 +76,8 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do
location_context: location_context,
fetch_stop_name_fn: fetch_stop_name_fn,
is_terminal_station: is_terminal_station,
now: now
now: now,
fetch_subway_platforms_for_stop_fn: fetch_subway_platforms_for_stop_fn
]

cond do
Expand Down Expand Up @@ -157,21 +159,42 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do
location_context: location_context,
fetch_stop_name_fn: fetch_stop_name_fn,
is_terminal_station: is_terminal_station,
now: now
now: now,
fetch_subway_platforms_for_stop_fn: fetch_subway_platforms_for_stop_fn
) do
Enum.map(alerts, fn alert ->
all_platforms_names_at_informed_station =
get_platform_names_at_informed_station(alert, fetch_subway_platforms_for_stop_fn)

%ReconstructedAlert{
screen: config,
alert: alert,
now: now,
location_context: location_context,
informed_stations: get_stations(alert, fetch_stop_name_fn),
is_terminal_station: is_terminal_station,
is_full_screen: is_full_screen
is_full_screen: is_full_screen,
partial_closure_platform_names: all_platforms_names_at_informed_station
}
end)
end

defp get_platform_names_at_informed_station(
%Alert{effect: :station_closure, informed_entities: informed_entities} = alert,
fetch_subway_platforms_for_stop_fn
) do
with [informed_parent_station] <- Alert.informed_parent_stations(alert),
platforms <- fetch_subway_platforms_for_stop_fn.(informed_parent_station.stop),
true <- Alert.is_partial_station_closure?(alert, platforms) do
informed_stop_ids = Enum.map(informed_entities, & &1.stop)
platforms |> Enum.filter(&(&1.id in informed_stop_ids)) |> Enum.map(& &1.platform_name)
else
_ -> []
end
end

defp get_platform_names_at_informed_station(_, _), do: []

defp find_closest_downstream_alerts(alerts, stop_id, stop_sequences) do
home_stop_distance_map = build_distance_map(stop_id, stop_sequences)
# Map each alert with its distance from home.
Expand Down
38 changes: 35 additions & 3 deletions lib/screens/v2/candidate_generator/widgets/subway_status.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ defmodule Screens.V2.CandidateGenerator.Widgets.SubwayStatus do
@moduledoc false

alias Screens.Alerts.Alert
alias Screens.Stops.Stop
alias Screens.V2.WidgetInstance.SubwayStatus

def subway_status_instances(config, now \\ DateTime.utc_now()) do
def subway_status_instances(
config,
now \\ DateTime.utc_now(),
fetch_subway_platforms_for_stop_fn \\ &Stop.fetch_subway_platforms_for_stop/1
) do
route_ids = ["Blue", "Orange", "Red", "Green-B", "Green-C", "Green-D", "Green-E"]

case Screens.Alerts.Alert.fetch(route_ids: route_ids) do
{:ok, alerts} ->
relevant_alerts = Enum.filter(alerts, &relevant?(&1, now))
relevant_alerts =
alerts
|> Enum.filter(&relevant?(&1, now))
|> Enum.map(&append_context(&1, fetch_subway_platforms_for_stop_fn))

[%SubwayStatus{screen: config, subway_alerts: relevant_alerts}]

:error ->
Expand All @@ -27,5 +36,28 @@ defmodule Screens.V2.CandidateGenerator.Widgets.SubwayStatus do
defp relevant_effect?(%Alert{effect: effect}),
do: effect in [:suspension, :shuttle, :station_closure]

defp suppressed?(alert), do: alert.id == "529291"
defp suppressed?(_alert), do: false

defp append_context(
%Alert{effect: :station_closure} = alert,
fetch_subway_platforms_for_stop_fn
) do
informed_parent_stations = Alert.informed_parent_stations(alert)

all_platforms_at_informed_station =
case informed_parent_stations do
[informed_parent_station] ->
fetch_subway_platforms_for_stop_fn.(informed_parent_station.stop)

_ ->
[]
end

%SubwayStatus.SubwayStatusAlert{
alert: alert,
context: %{all_platforms_at_informed_station: all_platforms_at_informed_station}
}
end

defp append_context(alert, _), do: struct(SubwayStatus.SubwayStatusAlert, alert: alert)
end
Loading

0 comments on commit ddfe9ab

Please sign in to comment.