From 5a06764c92b02d517e2b6e4bb52ba91fc6ff9c07 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 23 May 2023 14:07:02 -0400 Subject: [PATCH 01/20] Added branching logic for picking fullscreen prefare alerts. --- .../widgets/reconstructed_alert.ex | 84 ++++++++++++++++--- .../v2/widget_instance/reconstructed_alert.ex | 6 +- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 4a602aa1e..3cdb9c62c 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -38,24 +38,82 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do {:ok, alerts} <- fetch_alerts_fn.(route_ids: route_ids_at_stop), {:ok, stop_sequences} <- fetch_stop_sequences_by_stop_fn.(stop_id, route_ids_at_stop) do - alerts - |> relevant_alerts(config, stop_sequences, routes_at_stop, now) - |> Enum.map(fn alert -> - %ReconstructedAlert{ - screen: config, - alert: alert, - now: now, - stop_sequences: stop_sequences, - routes_at_stop: routes_at_stop, - informed_stations_string: get_stations(alert, fetch_stop_name_fn), - is_terminal_station: is_terminal?(stop_id, stop_sequences) - } - end) + relevant_alerts = relevant_alerts(alerts, config, stop_sequences, routes_at_stop, now) + + immediate_disruptions = + Enum.filter( + relevant_alerts, + &(BaseAlert.location(&1) in [:inside, :boundary_upstream, :boundary_downstream]) + ) + + downstream_disruptions = + Enum.filter( + relevant_alerts, + &(BaseAlert.location(&1) in [:downstream, :upstream] or + (&1.effect == :delay and &1.severity >= 7)) + ) + + moderate_delays = + Enum.filter( + relevant_alerts, + &(&1.effect == :delay and &1.severity >= 5) + ) + + common_parameters = [ + config: config, + stop_id: stop_id, + stop_sequences: stop_sequences, + routes_at_stop: routes_at_stop, + fetch_stop_name_fn: fetch_stop_name_fn, + now: now + ] + + cond do + Enum.any?(immediate_disruptions) -> + create_alert_instance( + immediate_disruptions, + true, + common_parameters + ) ++ + create_alert_instance(downstream_disruptions, false, common_parameters) ++ + create_alert_instance(moderate_delays, false, common_parameters) + + Enum.any?(downstream_disruptions) -> + create_alert_instance(downstream_disruptions, true, common_parameters) ++ + create_alert_instance(moderate_delays, false, common_parameters) + + true -> + create_alert_instance(moderate_delays, true, common_parameters) + end else :error -> [] end end + defp create_alert_instance( + alerts, + is_full_screen, + config: config, + stop_id: stop_id, + stop_sequences: stop_sequences, + routes_at_stop: routes_at_stop, + fetch_stop_name_fn: fetch_stop_name_fn, + now: now + ) do + Enum.map(alerts, fn alert -> + %ReconstructedAlert{ + screen: config, + alert: alert, + now: now, + stop_sequences: stop_sequences, + routes_at_stop: routes_at_stop, + informed_stations_string: get_stations(alert, fetch_stop_name_fn), + is_terminal_station: is_terminal?(stop_id, stop_sequences), + is_full_screen: is_full_screen + } + end) + end + defp relevant_alerts(alerts, config, stop_sequences, routes_at_stop, now) do Enum.filter(alerts, fn %Alert{effect: effect} = alert -> reconstructed_alert = %ReconstructedAlert{ diff --git a/lib/screens/v2/widget_instance/reconstructed_alert.ex b/lib/screens/v2/widget_instance/reconstructed_alert.ex index 829266e50..3c1f8f40b 100644 --- a/lib/screens/v2/widget_instance/reconstructed_alert.ex +++ b/lib/screens/v2/widget_instance/reconstructed_alert.ex @@ -16,7 +16,8 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlert do stop_sequences: nil, routes_at_stop: nil, informed_stations_string: nil, - is_terminal_station: false + is_terminal_station: false, + is_full_screen: false @type stop_id :: String.t() @@ -29,7 +30,8 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlert do stop_sequences: list(list(stop_id())), routes_at_stop: list(%{route_id: route_id(), active?: boolean()}), informed_stations_string: String.t(), - is_terminal_station: boolean() + is_terminal_station: boolean(), + is_full_screen: boolean() } @route_directions %{ From 9bef72ade9497158ecc0732ac205919ffaabcef6 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 24 May 2023 08:15:25 -0400 Subject: [PATCH 02/20] Moved functions so modules that are not WidgetInstances can use them. Will clean up later. --- lib/screens/alerts/alert.ex | 143 ++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/lib/screens/alerts/alert.ex b/lib/screens/alerts/alert.ex index bfd2769da..7792f57aa 100644 --- a/lib/screens/alerts/alert.ex +++ b/lib/screens/alerts/alert.ex @@ -4,6 +4,7 @@ defmodule Screens.Alerts.Alert do alias Screens.Routes.Route alias Screens.RouteType alias Screens.Stops.Stop + alias Screens.Util alias Screens.V3Api defstruct id: nil, @@ -598,4 +599,146 @@ defmodule Screens.Alerts.Alert do true -> {:up_to, 5 * (severity - 1)} end end + + @spec get_alert_location_for_stop_id( + t(), + String.t(), + list(list(String.t())), + list(%{route_id: String.t(), active?: boolean(), type: RouteType.t()}), + boolean() + ) :: + :boundary_downstream + | :boundary_upstream + | :downstream + | :elsewhere + | :inside + | :upstream + def get_alert_location_for_stop_id( + alert, + stop_id, + stop_sequences, + routes_at_stop, + is_terminal_station + ) do + location_context = %{ + home_stop: stop_id, + upstream_stops: upstream_stop_id_set(stop_id, stop_sequences), + downstream_stops: downstream_stop_id_set(stop_id, stop_sequences), + routes: routes_at_stop, + route_types: + routes_at_stop + |> Enum.map(& &1.type) + |> Enum.uniq() + } + + informed_zones_set = + alert.informed_entities + |> Enum.flat_map(&informed_entity_to_zone(&1, location_context)) + |> Enum.uniq() + |> Enum.sort() + + get_location_atom(informed_zones_set, alert.effect, is_terminal_station) + end + + defp get_location_atom(informed_zones_set, _, _) when informed_zones_set == [:upstream], + do: :upstream + + defp get_location_atom(informed_zones_set, _, _) when informed_zones_set == [:downstream], + do: :downstream + + defp get_location_atom(informed_zones_set, _, _) when informed_zones_set == [:home_stop], + do: :inside + + defp get_location_atom(informed_zones_set, _, _) + when informed_zones_set == [:downstream, :home_stop, :upstream], + do: :inside + + # If station closure, then a boundary_upstream / _downstream is actually :inside + defp get_location_atom(informed_zones_set, effect, _) + when informed_zones_set == [:home_stop, :upstream] and effect === :station_closure, + do: :inside + + defp get_location_atom(informed_zones_set, effect, _) + when informed_zones_set == [:downstream, :home_stop] and effect === :station_closure, + do: :inside + + defp get_location_atom(informed_zones_set, effect, true) + when informed_zones_set in [[:home_stop, :upstream], [:downstream, :home_stop]] and + effect in [:suspension, :shuttle], + do: :inside + + defp get_location_atom(informed_zones_set, _, _) + when informed_zones_set == [:home_stop, :upstream], + do: :boundary_upstream + + defp get_location_atom(informed_zones_set, _, _) + when informed_zones_set == [:downstream, :home_stop], + do: :boundary_downstream + + defp get_location_atom(informed_zones_set, _, _) + when informed_zones_set == [:downstream, :upstream], + do: :downstream + + defp get_location_atom(_, _, _), do: :elsewhere + + @spec informed_entity_to_zone(Alert.informed_entity(), map()) :: + list(:upstream | :home_stop | :downstream) + defp informed_entity_to_zone(informed_entity, location_context) + + # All values nil + defp informed_entity_to_zone(%{stop: nil, route: nil, route_type: nil}, _location_context) do + [] + end + + # Only route type is not nil--this is the only time we consider route type, + # since it's implied by other values when they are not nil + defp informed_entity_to_zone(%{stop: nil, route: nil, route_type: route_type_id}, %{ + route_types: route_types + }) do + if RouteType.from_id(route_type_id) in route_types do + [:upstream, :home_stop, :downstream] + else + [] + end + end + + # Only stop is not nil (route type ignored) + defp informed_entity_to_zone(%{stop: stop, route: nil}, context) do + cond do + stop == context.home_stop -> [:home_stop] + # Stops can be both upstream and downstream simultaneously, on different routes through the home stop. + # We check whether it's downstream first, since that takes priority. + stop in context.downstream_stops -> [:downstream] + stop in context.upstream_stops -> [:upstream] + true -> [] + end + end + + # Only route is not nil (route type ignored) + defp informed_entity_to_zone(%{stop: nil, route: route}, context) do + if route in context.routes, do: [:upstream, :home_stop, :downstream], else: [] + end + + # Both stop and route are not nil (route type ignored) + defp informed_entity_to_zone(%{stop: _stop, route: route} = informed_entity, context) do + if route in context.routes do + informed_entity_to_zone(%{informed_entity | route: nil}, context) + else + [] + end + end + + @spec upstream_stop_id_set(String.t(), list(list(String.t()))) :: MapSet.t(String.t()) + def upstream_stop_id_set(stop_id, stop_sequences) do + stop_sequences + |> Enum.flat_map(fn stop_sequence -> Util.slice_before(stop_sequence, stop_id) end) + |> MapSet.new() + end + + @spec downstream_stop_id_set(String.t(), list(list(String.t()))) :: MapSet.t(String.t()) + defp downstream_stop_id_set(stop_id, stop_sequences) do + stop_sequences + |> Enum.flat_map(fn stop_sequence -> Util.slice_after(stop_sequence, stop_id) end) + |> MapSet.new() + end end From 460fe880e1185cbc00fb598c0a20f1564dfef620 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 24 May 2023 08:16:02 -0400 Subject: [PATCH 03/20] Added a list for common parameters. --- .../widgets/reconstructed_alert.ex | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 3cdb9c62c..e5ce38cb8 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -39,18 +39,35 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do {:ok, stop_sequences} <- fetch_stop_sequences_by_stop_fn.(stop_id, route_ids_at_stop) do relevant_alerts = relevant_alerts(alerts, config, stop_sequences, routes_at_stop, now) + is_terminal_station = is_terminal?(stop_id, stop_sequences) immediate_disruptions = Enum.filter( relevant_alerts, - &(BaseAlert.location(&1) in [:inside, :boundary_upstream, :boundary_downstream]) + &(Alert.get_alert_location_for_stop_id( + &1, + stop_id, + stop_sequences, + routes_at_stop, + is_terminal_station + ) in [ + :inside, + :boundary_upstream, + :boundary_downstream + ]) ) downstream_disruptions = Enum.filter( relevant_alerts, - &(BaseAlert.location(&1) in [:downstream, :upstream] or - (&1.effect == :delay and &1.severity >= 7)) + &(Alert.get_alert_location_for_stop_id( + &1, + stop_id, + stop_sequences, + routes_at_stop, + is_terminal_station + ) in [:downstream, :upstream] and + (&1.effect != :delay or &1.severity >= 7)) ) moderate_delays = @@ -61,43 +78,43 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do common_parameters = [ config: config, - stop_id: stop_id, stop_sequences: stop_sequences, routes_at_stop: routes_at_stop, fetch_stop_name_fn: fetch_stop_name_fn, + is_terminal_station: is_terminal_station, now: now ] cond do Enum.any?(immediate_disruptions) -> - create_alert_instance( + create_alert_instances( immediate_disruptions, true, common_parameters ) ++ - create_alert_instance(downstream_disruptions, false, common_parameters) ++ - create_alert_instance(moderate_delays, false, common_parameters) + create_alert_instances(downstream_disruptions, false, common_parameters) ++ + create_alert_instances(moderate_delays, false, common_parameters) Enum.any?(downstream_disruptions) -> - create_alert_instance(downstream_disruptions, true, common_parameters) ++ - create_alert_instance(moderate_delays, false, common_parameters) + create_alert_instances(downstream_disruptions, true, common_parameters) ++ + create_alert_instances(moderate_delays, false, common_parameters) true -> - create_alert_instance(moderate_delays, true, common_parameters) + create_alert_instances(moderate_delays, true, common_parameters) end else :error -> [] end end - defp create_alert_instance( + defp create_alert_instances( alerts, is_full_screen, config: config, - stop_id: stop_id, stop_sequences: stop_sequences, routes_at_stop: routes_at_stop, fetch_stop_name_fn: fetch_stop_name_fn, + is_terminal_station: is_terminal_station, now: now ) do Enum.map(alerts, fn alert -> @@ -108,7 +125,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do stop_sequences: stop_sequences, routes_at_stop: routes_at_stop, informed_stations_string: get_stations(alert, fetch_stop_name_fn), - is_terminal_station: is_terminal?(stop_id, stop_sequences), + is_terminal_station: is_terminal_station, is_full_screen: is_full_screen } end) From fbf121c9dc89aa5b2303b6439f4bcb814b7283e0 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 24 May 2023 09:49:35 -0400 Subject: [PATCH 04/20] Fixed value. --- lib/screens/alerts/alert.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/screens/alerts/alert.ex b/lib/screens/alerts/alert.ex index 7792f57aa..5e19c6c94 100644 --- a/lib/screens/alerts/alert.ex +++ b/lib/screens/alerts/alert.ex @@ -624,7 +624,10 @@ defmodule Screens.Alerts.Alert do home_stop: stop_id, upstream_stops: upstream_stop_id_set(stop_id, stop_sequences), downstream_stops: downstream_stop_id_set(stop_id, stop_sequences), - routes: routes_at_stop, + routes: + routes_at_stop + |> Enum.map(& &1.route_id) + |> Enum.uniq(), route_types: routes_at_stop |> Enum.map(& &1.type) From 464fd24170ac35fdfb9d2e69a27a8ac4480d1bf4 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 24 May 2023 09:50:11 -0400 Subject: [PATCH 05/20] Added distance logic for alerts with non-GL informed routes. --- .../widgets/reconstructed_alert.ex | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index e5ce38cb8..0b3849d81 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -96,7 +96,13 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do create_alert_instances(moderate_delays, false, common_parameters) Enum.any?(downstream_disruptions) -> - create_alert_instances(downstream_disruptions, true, common_parameters) ++ + fullscreen_alerts = + find_closest_downstream_alerts(downstream_disruptions, stop_id, stop_sequences) + + flex_zone_alerts = downstream_disruptions -- fullscreen_alerts + + create_alert_instances(fullscreen_alerts, true, common_parameters) ++ + create_alert_instances(flex_zone_alerts, false, common_parameters) ++ create_alert_instances(moderate_delays, false, common_parameters) true -> @@ -131,6 +137,48 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end) end + defp find_closest_downstream_alerts(alerts, stop_id, stop_sequences) do + # Map each alert with its distance from home. + Enum.map(alerts, fn %{informed_entities: ies} = alert -> + distance = + ies + |> Enum.filter(&String.starts_with?(&1.stop, "place-")) + |> Enum.map(&get_home_distance_from_ie(stop_id, &1, stop_sequences)) + |> Enum.min() + + {alert, distance} + end) + |> Enum.group_by(&elem(&1, 1), &elem(&1, 0)) + |> Enum.into([]) + |> Enum.sort_by(&elem(&1, 0)) + # The first item will be all alerts with the shortest distance. + |> List.first() + |> elem(1) + end + + # Find the closest informed station and calculate its distance from the home_stop_id. + # Assumes informed_entity is for a parent station. + defp get_home_distance_from_ie(home_stop_id, informed_entity, stop_sequences) + + # GL distance logic to be done later. + defp get_home_distance_from_ie(_home_stop_id, %{route: "Green" <> _}, _stop_sequences), do: 99 + + # Loop through each stop_sequence to find the shortest distance between home and ie. + defp get_home_distance_from_ie(home_stop_id, %{stop: stop_id}, stop_sequences) do + stop_sequences + |> Enum.filter(&(home_stop_id in &1 and stop_id in &1)) + |> Enum.map(&calculate_distance(&1, home_stop_id, stop_id)) + |> Enum.min(fn -> 99 end) + end + + # Alerts can be upstream or downstream. If it is an upstream alert, distance will be negative. Make it positive and use that as the distance. + defp calculate_distance(stop_sequence, home_stop_id, ie_stop_id) do + abs( + Enum.find_index(stop_sequence, &(&1 == home_stop_id)) - + Enum.find_index(stop_sequence, &(&1 == ie_stop_id)) + ) + end + defp relevant_alerts(alerts, config, stop_sequences, routes_at_stop, now) do Enum.filter(alerts, fn %Alert{effect: effect} = alert -> reconstructed_alert = %ReconstructedAlert{ From 56156276d41693ea54bdfd85f09a9569399843f6 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 24 May 2023 11:14:03 -0400 Subject: [PATCH 06/20] Corrected filter. --- .../v2/candidate_generator/widgets/reconstructed_alert.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 0b3849d81..d0cc1320a 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -73,7 +73,13 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do moderate_delays = Enum.filter( relevant_alerts, - &(&1.effect == :delay and &1.severity >= 5) + &(Alert.get_alert_location_for_stop_id( + &1, + stop_id, + stop_sequences, + routes_at_stop, + is_terminal_station + ) in [:elsewhere] and (&1.effect == :delay and &1.severity >= 5)) ) common_parameters = [ From 7db38ef15c54ab628259704a635d66252f2fcd8c Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 24 May 2023 11:14:30 -0400 Subject: [PATCH 07/20] Fixed existing tests. --- .../widgets/reconstructed_alert_test.exs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs b/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs index 100f110a8..5ac41c93e 100644 --- a/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs +++ b/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs @@ -191,27 +191,29 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do effect: :station_closure, informed_entities: [ie(stop: "place-hsmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - } + }, + is_full_screen: true }, expected_common_data ), struct( %ReconstructedAlertWidget{ alert: %Alert{ - id: "2", - effect: :station_closure, - informed_entities: [ie(stop: "place-bckhl")], + id: "3", + effect: :delay, + informed_entities: [ie(stop: "place-hsmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - } + }, + is_full_screen: true }, expected_common_data ), struct( %ReconstructedAlertWidget{ alert: %Alert{ - id: "3", - effect: :delay, - informed_entities: [ie(stop: "place-hsmnl")], + id: "2", + effect: :station_closure, + informed_entities: [ie(stop: "place-bckhl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] } }, @@ -329,27 +331,29 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do effect: :station_closure, informed_entities: [ie(stop: "place-hsmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - } + }, + is_full_screen: true }, expected_common_data ), struct( %ReconstructedAlertWidget{ alert: %Alert{ - id: "2", - effect: :station_closure, - informed_entities: [ie(stop: "place-bckhl")], + id: "3", + effect: :delay, + informed_entities: [ie(stop: "place-hsmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - } + }, + is_full_screen: true }, expected_common_data ), struct( %ReconstructedAlertWidget{ alert: %Alert{ - id: "3", - effect: :delay, - informed_entities: [ie(stop: "place-hsmnl")], + id: "2", + effect: :station_closure, + informed_entities: [ie(stop: "place-bckhl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] } }, @@ -388,7 +392,8 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do stop_sequences: station_sequences, now: now, informed_stations_string: informed_stations_string, - is_terminal_station: true + is_terminal_station: true, + is_full_screen: true } expected_widgets = [ From c2a7d2db5928a081c49d052d2c523c2460631a12 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 24 May 2023 13:57:00 -0400 Subject: [PATCH 08/20] Improved tests. --- .../widgets/reconstructed_alert_test.exs | 301 +++++++++++++----- 1 file changed, 220 insertions(+), 81 deletions(-) diff --git a/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs b/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs index 5ac41c93e..ffdba386c 100644 --- a/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs +++ b/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs @@ -24,93 +24,42 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do struct(Screen, %{ app_id: :pre_fare_v2, app_params: - struct(PreFare, %{reconstructed_alert_widget: %CurrentStopId{stop_id: "place-hsmnl"}}) + struct(PreFare, %{reconstructed_alert_widget: %CurrentStopId{stop_id: "place-ogmnl"}}) }) bad_config = struct(Screen, %{app_params: struct(Solari)}) routes_at_stop = [ %{ - route_id: "Red", + route_id: "Orange", active?: true, direction_destinations: nil, long_name: nil, short_name: nil, type: :subway - }, - %{ - route_id: "Green-B", - active?: false, - direction_destinations: nil, - long_name: nil, - short_name: nil, - type: :light_rail - }, - %{ - route_id: "Green-C", - active?: true, - direction_destinations: nil, - long_name: nil, - short_name: nil, - type: :light_rail - }, - %{ - route_id: "Green-D", - active?: true, - direction_destinations: nil, - long_name: nil, - short_name: nil, - type: :light_rail - }, - %{ - route_id: "Green-E", - active?: true, - direction_destinations: nil, - long_name: nil, - short_name: nil, - type: :light_rail } ] happening_now_active_period = [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - upcoming_active_period = [{~U[2021-01-02T00:00:00Z], ~U[2021-01-03T00:00:00Z]}] alerts = [ %Alert{ id: "1", effect: :station_closure, - informed_entities: [ie(stop: "place-hsmnl")], + informed_entities: [ie(stop: "place-ogmnl")], active_period: happening_now_active_period }, %Alert{ id: "2", effect: :station_closure, - informed_entities: [ie(stop: "place-bckhl")], + informed_entities: [ie(stop: "place-mlmnl")], active_period: happening_now_active_period }, %Alert{ id: "3", effect: :delay, - informed_entities: [ie(stop: "place-hsmnl")], + informed_entities: [ie(stop: "place-ogmnl")], active_period: happening_now_active_period - }, - %Alert{ - id: "4", - effect: :station_closure, - informed_entities: [], - active_period: happening_now_active_period - }, - %Alert{ - id: "5", - effect: :stop_closure, - informed_entities: [ie(stop: "place-rvrwy")], - active_period: happening_now_active_period - }, - %Alert{ - id: "6", - effect: :station_closure, - informed_entities: [ie(stop: "place-hsmnl")], - active_period: upcoming_active_period } ] @@ -118,41 +67,48 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do %Alert{ id: "1", effect: :delay, - informed_entities: [ie(stop: "place-hsmnl", direction_id: 0)], + informed_entities: [ie(stop: "place-ogmnl", direction_id: 0)], active_period: happening_now_active_period }, %Alert{ id: "2", effect: :delay, - informed_entities: [ie(stop: "place-hsmnl")], + informed_entities: [ie(stop: "place-ogmnl")], active_period: happening_now_active_period }, %Alert{ id: "3", effect: :delay, - informed_entities: [ie(stop: "place-hsmnl", direction_id: 1)], + informed_entities: [ie(stop: "place-ogmnl", direction_id: 1)], active_period: happening_now_active_period } ] station_sequences = [ - ["place-hsmnl", "place-bckhl", "place-rvrwy", "place-mispk"] + ["place-ogmnl", "place-mlmnl", "place-welln", "place-astao"] ] + fetch_stop_name_fn = fn + "place-ogmnl" -> "Oak Grove" + "place-mlmnl" -> "Malden Center" + "place-welln" -> "Wellington" + "place-astao" -> "Assembly" + end + %{ config: config, bad_config: bad_config, routes_at_stop: routes_at_stop, station_sequences: station_sequences, now: ~U[2021-01-01T00:00:00Z], - informed_stations_string: "Alewife", + happening_now_active_period: happening_now_active_period, fetch_routes_by_stop_fn: fn _, _, _ -> {:ok, routes_at_stop} end, fetch_parent_station_sequences_through_stop_fn: fn _, _ -> {:ok, station_sequences} end, fetch_alerts_fn: fn _ -> {:ok, alerts} end, fetch_directional_alerts_fn: fn _ -> {:ok, directional_alerts} end, - fetch_stop_name_fn: fn _ -> "Alewife" end, + fetch_stop_name_fn: fetch_stop_name_fn, x_fetch_routes_by_stop_fn: fn _, _, _ -> :error end, x_fetch_parent_station_sequences_through_stop_fn: fn _, _ -> :error end, x_fetch_alerts_fn: fn _ -> :error end, @@ -160,26 +116,47 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do } end - test "returns a list of alert widgets if all queries succeed", context do + test "returns fullscreen instances for immediate disruptions", context do %{ config: config, routes_at_stop: routes_at_stop, station_sequences: station_sequences, now: now, - informed_stations_string: informed_stations_string, + happening_now_active_period: happening_now_active_period, fetch_routes_by_stop_fn: fetch_routes_by_stop_fn, fetch_parent_station_sequences_through_stop_fn: fetch_parent_station_sequences_through_stop_fn, - fetch_alerts_fn: fetch_alerts_fn, fetch_stop_name_fn: fetch_stop_name_fn } = context + alerts = [ + %Alert{ + id: "1", + effect: :station_closure, + informed_entities: [ie(stop: "place-ogmnl")], + active_period: happening_now_active_period + }, + %Alert{ + id: "2", + effect: :station_closure, + informed_entities: [ie(stop: "place-mlmnl")], + active_period: happening_now_active_period + }, + %Alert{ + id: "3", + effect: :delay, + informed_entities: [ie(stop: "place-ogmnl")], + active_period: happening_now_active_period + } + ] + + fetch_alerts_fn = fn _ -> {:ok, alerts} end + expected_common_data = %{ screen: config, routes_at_stop: routes_at_stop, stop_sequences: station_sequences, now: now, - informed_stations_string: informed_stations_string, is_terminal_station: true } @@ -189,10 +166,11 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do alert: %Alert{ id: "1", effect: :station_closure, - informed_entities: [ie(stop: "place-hsmnl")], + informed_entities: [ie(stop: "place-ogmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] }, - is_full_screen: true + is_full_screen: true, + informed_stations_string: "Oak Grove" }, expected_common_data ), @@ -201,10 +179,11 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do alert: %Alert{ id: "3", effect: :delay, - informed_entities: [ie(stop: "place-hsmnl")], + informed_entities: [ie(stop: "place-ogmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] }, - is_full_screen: true + is_full_screen: true, + informed_stations_string: "Oak Grove" }, expected_common_data ), @@ -213,9 +192,169 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do alert: %Alert{ id: "2", effect: :station_closure, - informed_entities: [ie(stop: "place-bckhl")], + informed_entities: [ie(stop: "place-mlmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - } + }, + informed_stations_string: "Malden Center" + }, + expected_common_data + ) + ] + + assert expected_widgets == + reconstructed_alert_instances( + config, + now, + fetch_routes_by_stop_fn, + fetch_parent_station_sequences_through_stop_fn, + fetch_alerts_fn, + fetch_stop_name_fn + ) + end + + test "returns fullscreen instances for closest downstream disruptions if no immediate disruptions", + context do + %{ + config: config, + routes_at_stop: routes_at_stop, + station_sequences: station_sequences, + now: now, + happening_now_active_period: happening_now_active_period, + fetch_routes_by_stop_fn: fetch_routes_by_stop_fn, + fetch_parent_station_sequences_through_stop_fn: + fetch_parent_station_sequences_through_stop_fn, + fetch_stop_name_fn: fetch_stop_name_fn + } = context + + alerts = [ + %Alert{ + id: "1", + effect: :station_closure, + informed_entities: [ie(stop: "place-mlmnl")], + active_period: happening_now_active_period + }, + %Alert{ + id: "2", + effect: :station_closure, + informed_entities: [ie(stop: "place-astao")], + active_period: happening_now_active_period + }, + %Alert{ + id: "3", + effect: :shuttle, + informed_entities: [ie(stop: "place-mlmnl"), ie(stop: "place-welln")], + active_period: happening_now_active_period + } + ] + + fetch_alerts_fn = fn _ -> {:ok, alerts} end + + expected_common_data = %{ + screen: config, + routes_at_stop: routes_at_stop, + stop_sequences: station_sequences, + now: now, + is_terminal_station: true + } + + expected_widgets = [ + struct( + %ReconstructedAlertWidget{ + alert: %Alert{ + id: "1", + effect: :station_closure, + informed_entities: [ie(stop: "place-mlmnl")], + active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] + }, + is_full_screen: true, + informed_stations_string: "Malden Center" + }, + expected_common_data + ), + struct( + %ReconstructedAlertWidget{ + alert: %Alert{ + id: "3", + effect: :shuttle, + informed_entities: [ie(stop: "place-mlmnl"), ie(stop: "place-welln")], + active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] + }, + is_full_screen: true, + informed_stations_string: "Malden Center and Wellington" + }, + expected_common_data + ), + struct( + %ReconstructedAlertWidget{ + alert: %Alert{ + id: "2", + effect: :station_closure, + informed_entities: [ie(stop: "place-astao")], + active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] + }, + informed_stations_string: "Assembly" + }, + expected_common_data + ) + ] + + assert expected_widgets == + reconstructed_alert_instances( + config, + now, + fetch_routes_by_stop_fn, + fetch_parent_station_sequences_through_stop_fn, + fetch_alerts_fn, + fetch_stop_name_fn + ) + end + + test "returns fullscreen instances for moderate disruptions if no immediate/downstream disruptions", + context do + %{ + config: config, + routes_at_stop: routes_at_stop, + station_sequences: station_sequences, + now: now, + happening_now_active_period: happening_now_active_period, + fetch_routes_by_stop_fn: fetch_routes_by_stop_fn, + fetch_parent_station_sequences_through_stop_fn: + fetch_parent_station_sequences_through_stop_fn, + fetch_stop_name_fn: fetch_stop_name_fn + } = context + + alerts = [ + %Alert{ + id: "1", + effect: :delay, + severity: 6, + informed_entities: [ie(stop: "place-mlmnl")], + active_period: happening_now_active_period + } + ] + + fetch_alerts_fn = fn _ -> {:ok, alerts} end + + expected_common_data = %{ + screen: config, + routes_at_stop: routes_at_stop, + stop_sequences: station_sequences, + now: now, + is_terminal_station: true + } + + expected_widgets = [ + struct( + %ReconstructedAlertWidget{ + alert: %Alert{ + id: "1", + effect: :delay, + severity: 6, + informed_entities: [ie(stop: "place-mlmnl")], + active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] + }, + is_full_screen: true, + informed_stations_string: "Malden Center" }, expected_common_data ) @@ -329,7 +468,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do alert: %Alert{ id: "1", effect: :station_closure, - informed_entities: [ie(stop: "place-hsmnl")], + informed_entities: [ie(stop: "place-ogmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] }, is_full_screen: true @@ -341,7 +480,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do alert: %Alert{ id: "3", effect: :delay, - informed_entities: [ie(stop: "place-hsmnl")], + informed_entities: [ie(stop: "place-ogmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] }, is_full_screen: true @@ -353,7 +492,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do alert: %Alert{ id: "2", effect: :station_closure, - informed_entities: [ie(stop: "place-bckhl")], + informed_entities: [ie(stop: "place-mlmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] } }, @@ -378,7 +517,6 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do routes_at_stop: routes_at_stop, station_sequences: station_sequences, now: now, - informed_stations_string: informed_stations_string, fetch_routes_by_stop_fn: fetch_routes_by_stop_fn, fetch_parent_station_sequences_through_stop_fn: fetch_parent_station_sequences_through_stop_fn, @@ -391,7 +529,6 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do routes_at_stop: routes_at_stop, stop_sequences: station_sequences, now: now, - informed_stations_string: informed_stations_string, is_terminal_station: true, is_full_screen: true } @@ -402,9 +539,10 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do alert: %Alert{ id: "1", effect: :delay, - informed_entities: [ie(stop: "place-hsmnl", direction_id: 0)], + informed_entities: [ie(stop: "place-ogmnl", direction_id: 0)], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - } + }, + informed_stations_string: "Oak Grove" }, expected_common_data ), @@ -413,9 +551,10 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do alert: %Alert{ id: "2", effect: :delay, - informed_entities: [ie(stop: "place-hsmnl")], + informed_entities: [ie(stop: "place-ogmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - } + }, + informed_stations_string: "Oak Grove" }, expected_common_data ) From 9dc49204d8debe062b382525d983c32c23b4eee9 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Thu, 25 May 2023 11:06:12 -0400 Subject: [PATCH 09/20] Added a function to more efficiently get distances. Thanks Jon. --- .../widgets/reconstructed_alert.ex | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index d0cc1320a..8981da61b 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -144,45 +144,35 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end 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. Enum.map(alerts, fn %{informed_entities: ies} = alert -> distance = ies |> Enum.filter(&String.starts_with?(&1.stop, "place-")) - |> Enum.map(&get_home_distance_from_ie(stop_id, &1, stop_sequences)) + |> Enum.map(&Map.fetch!(home_stop_distance_map, &1.stop)) |> Enum.min() {alert, distance} end) |> Enum.group_by(&elem(&1, 1), &elem(&1, 0)) - |> Enum.into([]) |> Enum.sort_by(&elem(&1, 0)) # The first item will be all alerts with the shortest distance. |> List.first() |> elem(1) end - # Find the closest informed station and calculate its distance from the home_stop_id. - # Assumes informed_entity is for a parent station. - defp get_home_distance_from_ie(home_stop_id, informed_entity, stop_sequences) - - # GL distance logic to be done later. - defp get_home_distance_from_ie(_home_stop_id, %{route: "Green" <> _}, _stop_sequences), do: 99 - - # Loop through each stop_sequence to find the shortest distance between home and ie. - defp get_home_distance_from_ie(home_stop_id, %{stop: stop_id}, stop_sequences) do - stop_sequences - |> Enum.filter(&(home_stop_id in &1 and stop_id in &1)) - |> Enum.map(&calculate_distance(&1, home_stop_id, stop_id)) - |> Enum.min(fn -> 99 end) - end - - # Alerts can be upstream or downstream. If it is an upstream alert, distance will be negative. Make it positive and use that as the distance. - defp calculate_distance(stop_sequence, home_stop_id, ie_stop_id) do - abs( - Enum.find_index(stop_sequence, &(&1 == home_stop_id)) - - Enum.find_index(stop_sequence, &(&1 == ie_stop_id)) - ) + defp build_distance_map(home_stop_id, stop_sequences) do + Enum.reduce(stop_sequences, %{}, fn stop_sequence, distances_by_stop -> + stop_sequence + # Index each element by its distance from home_stop_id. For example if home_stop_id is at position 2, then indices would start at -2. + |> Enum.with_index(-Enum.find_index(stop_sequence, &(&1 == home_stop_id))) + # Convert negative distances to positive, and put into a map. + |> Map.new(fn {stop, d} -> {stop, abs(d)} end) + # Merge with the distances recorded from previous stop sequences. + # If a stop already has a distance recorded, we use the smaller of the two. <-- *** Unsure if this is always what we'd want to do! *** + |> Map.merge(distances_by_stop, fn _stop, d1, d2 -> min(d1, d2) end) + end) end defp relevant_alerts(alerts, config, stop_sequences, routes_at_stop, now) do From e3ef9c44821ff58b354bcbc8e167dd30c1760c7e Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Fri, 26 May 2023 10:16:46 -0400 Subject: [PATCH 10/20] Added distance logic for GL. --- .../widgets/reconstructed_alert.ex | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 8981da61b..4d13f1a2f 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -14,6 +14,16 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do @relevant_effects ~w[shuttle suspension station_closure delay]a + @gl_eastbound_split_stops [ + "place-mdftf", + "place-balsq", + "place-mgngl", + "place-gilmn", + "place-esomr", + "place-unsqu", + "place-lech" + ] + @doc """ Given the stop_id defined in the header, determine relevant routes Given the routes, fetch all alerts for the route @@ -150,7 +160,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do distance = ies |> Enum.filter(&String.starts_with?(&1.stop, "place-")) - |> Enum.map(&Map.fetch!(home_stop_distance_map, &1.stop)) + |> Enum.map(&get_distance(home_stop_distance_map, &1)) |> Enum.min() {alert, distance} @@ -175,6 +185,16 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end) end + defp get_distance(home_stop_distance_map, %{route: "Green" <> _, stop: stop_id}) + when stop_id in @gl_eastbound_split_stops, + do: Map.fetch!(home_stop_distance_map, "place-lech") + + defp get_distance(home_stop_distance_map, %{route: "Green" <> _}), + do: Map.fetch!(home_stop_distance_map, "place-kencl") + + defp get_distance(home_stop_distance_map, %{stop: stop_id}), + do: Map.fetch!(home_stop_distance_map, stop_id) + defp relevant_alerts(alerts, config, stop_sequences, routes_at_stop, now) do Enum.filter(alerts, fn %Alert{effect: effect} = alert -> reconstructed_alert = %ReconstructedAlert{ From b3f27499de31e04a9189938d60b16039a396896e Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Fri, 26 May 2023 10:26:50 -0400 Subject: [PATCH 11/20] Fixed filter for moderate delays. --- .../widgets/reconstructed_alert.ex | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 4d13f1a2f..074868659 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -81,16 +81,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do ) moderate_delays = - Enum.filter( - relevant_alerts, - &(Alert.get_alert_location_for_stop_id( - &1, - stop_id, - stop_sequences, - routes_at_stop, - is_terminal_station - ) in [:elsewhere] and (&1.effect == :delay and &1.severity >= 5)) - ) + Enum.filter(relevant_alerts, &(&1.effect == :delay and &1.severity in 5..6)) common_parameters = [ config: config, From 83359782424040fae92b2405b5231ce5512575cd Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 30 May 2023 10:22:05 -0400 Subject: [PATCH 12/20] Credo. --- .../v2/candidate_generator/widgets/reconstructed_alert.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 074868659..25a0720bc 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -147,7 +147,8 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert 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. - Enum.map(alerts, fn %{informed_entities: ies} = alert -> + alerts + |> Enum.map(fn %{informed_entities: ies} = alert -> distance = ies |> Enum.filter(&String.starts_with?(&1.stop, "place-")) From b3e376aad1257d3c2e5b13e7bdca2fadc9a5bb7f Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 6 Jun 2023 08:34:42 -0400 Subject: [PATCH 13/20] Fixed dialyzer issues. --- .../widgets/reconstructed_alert.ex | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 3c1262edb..047222927 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -42,7 +42,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do with {:ok, location_context} <- fetch_location_context_fn.(PreFare, stop_id, now), route_ids <- Route.route_ids(location_context.routes), {:ok, alerts} <- fetch_alerts_fn.(route_ids: route_ids) do - relevant_alerts = relevant_alerts(alerts, config, location_context, now) + relevant_alerts = relevant_alerts(alerts, location_context, now) is_terminal_station = is_terminal?(stop_id, location_context.stop_sequences) immediate_disruptions = @@ -174,15 +174,9 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do defp get_distance(home_stop_distance_map, %{stop: stop_id}), do: Map.get(home_stop_distance_map, stop_id, @default_distance) - defp relevant_alerts(alerts, config, location_context, now) do + defp relevant_alerts(alerts, location_context, now) do Enum.filter(alerts, fn %Alert{effect: effect} = alert -> - reconstructed_alert = %ReconstructedAlert{ - screen: config, - alert: alert, - location_context: location_context, - now: now, - informed_stations_string: "A Station" - } + reconstructed_alert = %{alert: alert, location_context: location_context} relevant_effect?(effect) and relevant_location?(reconstructed_alert) and Alert.happening_now?(alert, now) @@ -209,35 +203,27 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end end - defp relevant_inside_alert?( - %ReconstructedAlert{alert: %Alert{effect: :delay}} = reconstructed_alert - ), - do: relevant_delay?(reconstructed_alert) + defp relevant_inside_alert?(%{alert: %Alert{effect: :delay}} = reconstructed_alert), + do: relevant_delay?(reconstructed_alert) defp relevant_inside_alert?(_), do: true - defp relevant_boundary_alert?(%ReconstructedAlert{alert: %Alert{effect: :station_closure}}), + defp relevant_boundary_alert?(%{alert: %Alert{effect: :station_closure}}), do: false - defp relevant_boundary_alert?( - %ReconstructedAlert{ - alert: %Alert{effect: :delay} - } = reconstructed_alert - ), - do: relevant_delay?(reconstructed_alert) + defp relevant_boundary_alert?(%{alert: %Alert{effect: :delay}} = reconstructed_alert), + do: relevant_delay?(reconstructed_alert) defp relevant_boundary_alert?(_), do: true - defp relevant_delay?( - %ReconstructedAlert{alert: %Alert{severity: severity}} = reconstructed_alert - ) do + defp relevant_delay?(%{alert: %Alert{severity: severity}} = reconstructed_alert) do severity > 3 and relevant_direction?(reconstructed_alert) end # This function assumes that stop_sequences is ordered by direction north/east -> south/west. # If the current station's stop_id is the first or last entry in all stop_sequences, # it is a terminal station. Delay alerts heading in the direction of the station are not relevant. - defp relevant_direction?(%ReconstructedAlert{ + defp relevant_direction?(%{ alert: alert, location_context: %{home_stop: stop_id, stop_sequences: stop_sequences} }) do From 9ddc0109bfd5314426b665e792a14a487364e5d5 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 6 Jun 2023 08:45:19 -0400 Subject: [PATCH 14/20] Removed delays from immediate disruptions. --- .../widgets/reconstructed_alert.ex | 62 ++++++++++++------- .../widgets/reconstructed_alert_test.exs | 36 +++++------ 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 047222927..0a8232d9d 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -45,28 +45,9 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do relevant_alerts = relevant_alerts(alerts, location_context, now) is_terminal_station = is_terminal?(stop_id, location_context.stop_sequences) - immediate_disruptions = - Enum.filter( - relevant_alerts, - &(LocalizedAlert.location(%{alert: &1, location_context: location_context}) in [ - :inside, - :boundary_upstream, - :boundary_downstream - ]) - ) - - downstream_disruptions = - Enum.filter( - relevant_alerts, - &(LocalizedAlert.location(%{alert: &1, location_context: location_context}) in [ - :downstream, - :upstream - ] and - (&1.effect != :delay or &1.severity >= 7)) - ) - - moderate_delays = - Enum.filter(relevant_alerts, &(&1.effect == :delay and &1.severity in 5..6)) + immediate_disruptions = get_immediate_disruptions(relevant_alerts, location_context) + downstream_disruptions = get_downstream_disruptions(relevant_alerts, location_context) + moderate_delays = get_moderate_disruptions(relevant_alerts) common_parameters = [ config: config, @@ -108,6 +89,43 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end end + defp get_immediate_disruptions(relevant_alerts, location_context) do + Enum.filter( + relevant_alerts, + fn + %{effect: :delay} -> + false + + alert -> + LocalizedAlert.location(%{alert: alert, location_context: location_context}) in [ + :inside, + :boundary_upstream, + :boundary_downstream + ] + end + ) + end + + defp get_downstream_disruptions(relevant_alerts, location_context) do + Enum.filter( + relevant_alerts, + fn + %{effect: :delay} = alert -> + alert.severity >= 7 + + alert -> + LocalizedAlert.location(%{alert: alert, location_context: location_context}) in [ + :downstream, + :upstream + ] + end + ) + end + + defp get_moderate_disruptions(relevant_alerts) do + Enum.filter(relevant_alerts, &(&1.effect == :delay and &1.severity in 5..6)) + end + defp create_alert_instances( alerts, is_full_screen, diff --git a/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs b/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs index 886c38b7c..63c65cc57 100644 --- a/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs +++ b/test/screens/v2/candidate_generator/widgets/reconstructed_alert_test.exs @@ -182,25 +182,25 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do struct( %ReconstructedAlertWidget{ alert: %Alert{ - id: "3", - effect: :delay, - informed_entities: [ie(stop: "place-ogmnl")], + id: "2", + effect: :station_closure, + informed_entities: [ie(stop: "place-mlmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] }, - is_full_screen: true, - informed_stations_string: "Oak Grove" + informed_stations_string: "Malden Center" }, expected_common_data ), struct( %ReconstructedAlertWidget{ alert: %Alert{ - id: "2", - effect: :station_closure, - informed_entities: [ie(stop: "place-mlmnl")], + id: "3", + effect: :delay, + informed_entities: [ie(stop: "place-ogmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] }, - informed_stations_string: "Malden Center" + is_full_screen: false, + informed_stations_string: "Oak Grove" }, expected_common_data ) @@ -449,23 +449,23 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlertTest do struct( %ReconstructedAlertWidget{ alert: %Alert{ - id: "3", - effect: :delay, - informed_entities: [ie(stop: "place-ogmnl")], + id: "2", + effect: :station_closure, + informed_entities: [ie(stop: "place-mlmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - }, - is_full_screen: true + } }, expected_common_data ), struct( %ReconstructedAlertWidget{ alert: %Alert{ - id: "2", - effect: :station_closure, - informed_entities: [ie(stop: "place-mlmnl")], + id: "3", + effect: :delay, + informed_entities: [ie(stop: "place-ogmnl")], active_period: [{~U[2020-12-31T00:00:00Z], ~U[2021-01-02T00:00:00Z]}] - } + }, + is_full_screen: false }, expected_common_data ) From c60ca0582d36af8c66ba28970da777777aa529b7 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 6 Jun 2023 08:56:24 -0400 Subject: [PATCH 15/20] Changed how distances are merged. --- .../v2/candidate_generator/widgets/reconstructed_alert.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 0a8232d9d..f26d73967 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -176,8 +176,8 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do # Convert negative distances to positive, and put into a map. |> Map.new(fn {stop, d} -> {stop, abs(d)} end) # Merge with the distances recorded from previous stop sequences. - # If a stop already has a distance recorded, we use the smaller of the two. <-- *** Unsure if this is always what we'd want to do! *** - |> Map.merge(distances_by_stop, fn _stop, d1, d2 -> min(d1, d2) end) + # If a stop already has a distance recorded, the distances should be the same. Use the first one. + |> Map.merge(distances_by_stop, fn _stop, d1, _d2 -> d1 end) end) end From 8a4e9bf675130d5b10ce9b9352438a64603d1e55 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 6 Jun 2023 09:04:32 -0400 Subject: [PATCH 16/20] Added a spec. --- .../v2/candidate_generator/widgets/reconstructed_alert.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index f26d73967..68852552e 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -25,6 +25,10 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do @default_distance 99 + @type stop_id :: String.t() + @type distance :: non_neg_integer() + @type home_stop_distance_map :: %{stop_id() => distance()} + @doc """ Given the stop_id defined in the header, determine relevant routes Given the routes, fetch all alerts for the route @@ -182,6 +186,9 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end # Default to 99 if stop_id is not in distance map. + @spec get_distance(home_stop_distance_map(), Alert.informed_entity()) :: distance() + defp get_distance(home_stop_distance_map, informed_entity) + defp get_distance(home_stop_distance_map, %{route: "Green" <> _, stop: stop_id}) when stop_id in @gl_eastbound_split_stops, do: Map.get(home_stop_distance_map, "place-lech", @default_distance) From 043c97ddb819490d69781d8ae3f788e5a2c6b502 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 6 Jun 2023 11:24:32 -0400 Subject: [PATCH 17/20] Improved GL distance logic. --- .../widgets/reconstructed_alert.ex | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 68852552e..73b3d493b 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -23,6 +23,21 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do "place-lech" ] + @gl_trunk_stop_ids [ + "place-unsqu", + "place-lech", + "place-spmnl", + "place-north", + "place-haecl", + "place-gover", + "place-pktrm", + "place-boyls", + "place-armnl", + "place-coecl", + "place-hymnl", + "place-kencl" + ] + @default_distance 99 @type stop_id :: String.t() @@ -160,7 +175,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do distance = ies |> Enum.filter(&String.starts_with?(&1.stop, "place-")) - |> Enum.map(&get_distance(home_stop_distance_map, &1)) + |> Enum.map(&get_distance(stop_id, home_stop_distance_map, &1)) |> Enum.min() {alert, distance} @@ -186,17 +201,18 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end # Default to 99 if stop_id is not in distance map. - @spec get_distance(home_stop_distance_map(), Alert.informed_entity()) :: distance() - defp get_distance(home_stop_distance_map, informed_entity) + @spec get_distance(stop_id(), home_stop_distance_map(), Alert.informed_entity()) :: distance() + defp get_distance(home_stop_id, home_stop_distance_map, informed_entity) - defp get_distance(home_stop_distance_map, %{route: "Green" <> _, stop: stop_id}) - when stop_id in @gl_eastbound_split_stops, + defp get_distance(home_stop_id, home_stop_distance_map, %{route: "Green" <> _, stop: ie_stop_id}) + when home_stop_id in @gl_trunk_stop_ids and ie_stop_id in @gl_eastbound_split_stops, do: Map.get(home_stop_distance_map, "place-lech", @default_distance) - defp get_distance(home_stop_distance_map, %{route: "Green" <> _}), - do: Map.get(home_stop_distance_map, "place-kencl", @default_distance) + defp get_distance(home_stop_id, home_stop_distance_map, %{route: "Green" <> _}) + when home_stop_id in @gl_trunk_stop_ids, + do: Map.get(home_stop_distance_map, "place-kencl", @default_distance) - defp get_distance(home_stop_distance_map, %{stop: stop_id}), + defp get_distance(_, home_stop_distance_map, %{stop: stop_id}), do: Map.get(home_stop_distance_map, stop_id, @default_distance) defp relevant_alerts(alerts, location_context, now) do From 6b431b452d5ac8c060bdf26e08fade53dbf7c750 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 6 Jun 2023 13:50:57 -0400 Subject: [PATCH 18/20] Added a helper function for severity level. --- .../widgets/reconstructed_alert.ex | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index 73b3d493b..ed468d781 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -130,7 +130,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do relevant_alerts, fn %{effect: :delay} = alert -> - alert.severity >= 7 + get_severity_level(alert.severity) == :severe alert -> LocalizedAlert.location(%{alert: alert, location_context: location_context}) in [ @@ -142,7 +142,10 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end defp get_moderate_disruptions(relevant_alerts) do - Enum.filter(relevant_alerts, &(&1.effect == :delay and &1.severity in 5..6)) + Enum.filter( + relevant_alerts, + &(&1.effect == :delay and get_severity_level(&1.severity) == :moderate) + ) end defp create_alert_instances( @@ -258,7 +261,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do defp relevant_boundary_alert?(_), do: true defp relevant_delay?(%{alert: %Alert{severity: severity}} = reconstructed_alert) do - severity > 3 and relevant_direction?(reconstructed_alert) + get_severity_level(severity) != :low and relevant_direction?(reconstructed_alert) end # This function assumes that stop_sequences is ordered by direction north/east -> south/west. @@ -340,4 +343,12 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do List.first(stop_sequence) == stop_id or List.last(stop_sequence) == stop_id end) end + + defp get_severity_level(severity) do + cond do + severity < 5 -> :low + severity < 7 -> :moderate + true -> :severe + end + end end From a632c9c612399101c669c71a38de8016e1f08f63 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Thu, 8 Jun 2023 09:34:13 -0400 Subject: [PATCH 19/20] Added a comment. --- .../v2/candidate_generator/widgets/reconstructed_alert.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index ed468d781..ad7d18494 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -204,6 +204,8 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do end # Default to 99 if stop_id is not in distance map. + # Stops will not be present in the map if informed_entity and home stop are on different branches. + # i.e. Braintree is not present in Ashmont stop_sequences, but is still a relevant alert. @spec get_distance(stop_id(), home_stop_distance_map(), Alert.informed_entity()) :: distance() defp get_distance(home_stop_id, home_stop_distance_map, informed_entity) From 2d2ea6f060ff301224330a49282106f3191e968d Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Thu, 8 Jun 2023 09:36:20 -0400 Subject: [PATCH 20/20] Changed distance logic so only branch stops use Kenmore as a reference point. --- .../v2/candidate_generator/widgets/reconstructed_alert.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex index ad7d18494..0cce45e60 100644 --- a/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex +++ b/lib/screens/v2/candidate_generator/widgets/reconstructed_alert.ex @@ -213,8 +213,8 @@ defmodule Screens.V2.CandidateGenerator.Widgets.ReconstructedAlert do when home_stop_id in @gl_trunk_stop_ids and ie_stop_id in @gl_eastbound_split_stops, do: Map.get(home_stop_distance_map, "place-lech", @default_distance) - defp get_distance(home_stop_id, home_stop_distance_map, %{route: "Green" <> _}) - when home_stop_id in @gl_trunk_stop_ids, + defp get_distance(home_stop_id, home_stop_distance_map, %{route: "Green" <> _, stop: ie_stop_id}) + when home_stop_id in @gl_trunk_stop_ids and ie_stop_id not in @gl_trunk_stop_ids, do: Map.get(home_stop_distance_map, "place-kencl", @default_distance) defp get_distance(_, home_stop_distance_map, %{stop: stop_id}),