From 3d6729f56b88c54a297743d32ec0a31966292256 Mon Sep 17 00:00:00 2001 From: Cora Grant Date: Tue, 5 Nov 2024 13:57:31 -0500 Subject: [PATCH] refactor: split subway station module from `Stop` --- lib/screens/stops/stop.ex | 328 +----------------- lib/screens/stops/subway.ex | 318 +++++++++++++++++ lib/screens/v2/disruption_diagram/builder.ex | 14 +- .../v2/widget_instance/reconstructed_alert.ex | 20 +- .../v2/widget_instance/subway_status.ex | 14 +- .../reconstructed_alert_property_test.exs | 6 +- .../reconstructed_alert_test.exs | 6 +- .../disruption_diagram_localized_alert.ex | 10 +- 8 files changed, 355 insertions(+), 361 deletions(-) create mode 100644 lib/screens/stops/subway.ex diff --git a/lib/screens/stops/stop.ex b/lib/screens/stops/stop.ex index 642d7da80..9695e91e3 100644 --- a/lib/screens/stops/stop.ex +++ b/lib/screens/stops/stop.ex @@ -1,12 +1,5 @@ defmodule Screens.Stops.Stop do - @moduledoc """ - This file handles involves stop-related fetching / enrichment. - For a while, all stop-related data was fetched from the API, until we needed to provide consistent - abbreviations in the reconstructed alert. Now it's valuable to have a local copy of these stop sequences. - A lot of our code still collects these sequences from the API, though, whether in functions here - or in functions in `route_pattern.ex` (see fetch_tagged_parent_station_sequences_through_stop). - So there's inconsistent use of this local data. - """ + @moduledoc false require Logger @@ -33,224 +26,6 @@ defmodule Screens.Stops.Stop do @type screen_type :: BusEink | BusShelter | GlEink | PreFare | Dup - @blue_line_stops [ - {"place-wondl", {"Wonderland", "Wonderland"}}, - {"place-rbmnl", {"Revere Beach", "Revere Bch"}}, - {"place-bmmnl", {"Beachmont", "Beachmont"}}, - {"place-sdmnl", {"Suffolk Downs", "Suffolk Dns"}}, - {"place-orhte", {"Orient Heights", "Orient Hts"}}, - {"place-wimnl", {"Wood Island", "Wood Island"}}, - {"place-aport", {"Airport", "Airport"}}, - {"place-mvbcl", {"Maverick", "Maverick"}}, - {"place-aqucl", {"Aquarium", "Aquarium"}}, - {"place-state", {"State", "State"}}, - {"place-gover", {"Government Center", "Gov't Ctr"}}, - {"place-bomnl", {"Bowdoin", "Bowdoin"}} - ] - - @orange_line_stops [ - {"place-ogmnl", {"Oak Grove", "Oak Grove"}}, - {"place-mlmnl", {"Malden Center", "Malden Ctr"}}, - {"place-welln", {"Wellington", "Wellington"}}, - {"place-astao", {"Assembly", "Assembly"}}, - {"place-sull", {"Sullivan Square", "Sullivan Sq"}}, - {"place-ccmnl", {"Community College", "Com College"}}, - {"place-north", {"North Station", "North Sta"}}, - {"place-haecl", {"Haymarket", "Haymarket"}}, - {"place-state", {"State", "State"}}, - {"place-dwnxg", {"Downtown Crossing", "Downt'n Xng"}}, - {"place-chncl", {"Chinatown", "Chinatown"}}, - {"place-tumnl", {"Tufts Medical Center", "Tufts Med"}}, - {"place-bbsta", {"Back Bay", "Back Bay"}}, - {"place-masta", {"Massachusetts Avenue", "Mass Ave"}}, - {"place-rugg", {"Ruggles", "Ruggles"}}, - {"place-rcmnl", {"Roxbury Crossing", "Roxbury Xng"}}, - {"place-jaksn", {"Jackson Square", "Jackson Sq"}}, - {"place-sbmnl", {"Stony Brook", "Stony Brook"}}, - {"place-grnst", {"Green Street", "Green St"}}, - {"place-forhl", {"Forest Hills", "Frst Hills"}} - ] - - @red_line_trunk_stops [ - {"place-alfcl", {"Alewife", "Alewife"}}, - {"place-davis", {"Davis", "Davis"}}, - {"place-portr", {"Porter", "Porter"}}, - {"place-harsq", {"Harvard", "Harvard"}}, - {"place-cntsq", {"Central", "Central"}}, - {"place-knncl", {"Kendall/MIT", "Kendall/MIT"}}, - {"place-chmnl", {"Charles/MGH", "Charles/MGH"}}, - {"place-pktrm", {"Park Street", "Park St"}}, - {"place-dwnxg", {"Downtown Crossing", "Downt'n Xng"}}, - {"place-sstat", {"South Station", "South Sta"}}, - {"place-brdwy", {"Broadway", "Broadway"}}, - {"place-andrw", {"Andrew", "Andrew"}}, - {"place-jfk", {"JFK/UMass", "JFK/UMass"}} - ] - - @red_line_ashmont_branch_stops [ - {"place-shmnl", {"Savin Hill", "Savin Hill"}}, - {"place-fldcr", {"Fields Corner", "Fields Cnr"}}, - {"place-smmnl", {"Shawmut", "Shawmut"}}, - {"place-asmnl", {"Ashmont", "Ashmont"}} - ] - - @red_line_braintree_branch_stops [ - {"place-nqncy", {"North Quincy", "N Quincy"}}, - {"place-wlsta", {"Wollaston", "Wollaston"}}, - {"place-qnctr", {"Quincy Center", "Quincy Ctr"}}, - {"place-qamnl", {"Quincy Adams", "Quincy Adms"}}, - {"place-brntn", {"Braintree", "Braintree"}} - ] - - @green_line_b_stops [ - {"place-gover", {"Government Center", "Gov't Ctr"}}, - {"place-pktrm", {"Park Street", "Park St"}}, - {"place-boyls", {"Boylston", "Boylston"}}, - {"place-armnl", {"Arlington", "Arlington"}}, - {"place-coecl", {"Copley", "Copley"}}, - {"place-hymnl", {"Hynes Convention Center", "Hynes"}}, - {"place-kencl", {"Kenmore", "Kenmore"}}, - {"place-bland", {"Blandford Street", "Blandford"}}, - {"place-buest", {"Boston University East", "BU East"}}, - {"place-bucen", {"Boston University Central", "BU Central"}}, - {"place-amory", {"Amory Street", "Amory St"}}, - {"place-babck", {"Babcock Street", "Babcock St"}}, - {"place-brico", {"Packards Corner", "Packards Cn"}}, - {"place-harvd", {"Harvard Avenue", "Harvard Ave"}}, - {"place-grigg", {"Griggs Street", "Griggs St"}}, - {"place-alsgr", {"Allston Street", "Allston St"}}, - {"place-wrnst", {"Warren Street", "Warren St"}}, - {"place-wascm", {"Washington Street", "Washington"}}, - {"place-sthld", {"Sutherland Road", "Sutherland"}}, - {"place-chswk", {"Chiswick Road", "Chiswick Rd"}}, - {"place-chill", {"Chestnut Hill Avenue", "Chestnut Hl"}}, - {"place-sougr", {"South Street", "South St"}}, - {"place-lake", {"Boston College", "Boston Coll"}} - ] - - @green_line_c_stops [ - {"place-gover", {"Government Center", "Gov't Ctr"}}, - {"place-pktrm", {"Park Street", "Park St"}}, - {"place-boyls", {"Boylston", "Boylston"}}, - {"place-armnl", {"Arlington", "Arlington"}}, - {"place-coecl", {"Copley", "Copley"}}, - {"place-hymnl", {"Hynes Convention Center", "Hynes"}}, - {"place-kencl", {"Kenmore", "Kenmore"}}, - {"place-smary", {"Saint Mary's Street", "St. Mary's"}}, - {"place-hwsst", {"Hawes Street", "Hawes St"}}, - {"place-kntst", {"Kent Street", "Kent St"}}, - {"place-stpul", {"Saint Paul Street", "St. Paul St"}}, - {"place-cool", {"Coolidge Corner", "Coolidge Cn"}}, - {"place-sumav", {"Summit Avenue", "Summit Ave"}}, - {"place-bndhl", {"Brandon Hall", "Brandon Hll"}}, - {"place-fbkst", {"Fairbanks Street", "Fairbanks"}}, - {"place-bcnwa", {"Washington Square", "Washington"}}, - {"place-tapst", {"Tappan Street", "Tappan St"}}, - {"place-denrd", {"Dean Road", "Dean Rd"}}, - {"place-engav", {"Englewood Avenue", "Englew'd Av"}}, - {"place-clmnl", {"Cleveland Circle", "Clvlnd Cir"}} - ] - - @green_line_d_stops [ - {"place-unsqu", {"Union Square", "Union Sq"}}, - {"place-lech", {"Lechmere", "Lechmere"}}, - {"place-spmnl", {"Science Park/West End", "Science Pk"}}, - {"place-north", {"North Station", "North Sta"}}, - {"place-haecl", {"Haymarket", "Haymarket"}}, - {"place-gover", {"Government Center", "Gov't Ctr"}}, - {"place-pktrm", {"Park Street", "Park St"}}, - {"place-boyls", {"Boylston", "Boylston"}}, - {"place-armnl", {"Arlington", "Arlington"}}, - {"place-coecl", {"Copley", "Copley"}}, - {"place-hymnl", {"Hynes Convention Center", "Hynes"}}, - {"place-kencl", {"Kenmore", "Kenmore"}}, - {"place-fenwy", {"Fenway", "Fenway"}}, - {"place-longw", {"Longwood", "Longwood"}}, - {"place-bvmnl", {"Brookline Village", "B'kline Vil"}}, - {"place-brkhl", {"Brookline Hills", "B'kline Hls"}}, - {"place-bcnfd", {"Beaconsfield", "B'consfield"}}, - {"place-rsmnl", {"Reservoir", "Reservoir"}}, - {"place-chhil", {"Chestnut Hill", "Chestnut Hl"}}, - {"place-newto", {"Newton Centre", "Newton Ctr"}}, - {"place-newtn", {"Newton Highlands", "Newton Hlnd"}}, - {"place-eliot", {"Eliot", "Eliot"}}, - {"place-waban", {"Waban", "Waban"}}, - {"place-woodl", {"Woodland", "Woodland"}}, - {"place-river", {"Riverside", "Riverside"}} - ] - - @green_line_e_stops [ - {"place-mdftf", {"Medford / Tufts", "Medford"}}, - {"place-balsq", {"Ball Square", "Ball Sq"}}, - {"place-mgngl", {"Magoun Square", "Magoun Sq"}}, - {"place-gilmn", {"Gilman Square", "Gilman Sq"}}, - {"place-esomr", {"East Somerville", "E Somerville"}}, - {"place-lech", {"Lechmere", "Lechmere"}}, - {"place-spmnl", {"Science Park/West End", "Science Pk"}}, - {"place-north", {"North Station", "North Sta"}}, - {"place-haecl", {"Haymarket", "Haymarket"}}, - {"place-gover", {"Government Center", "Gov't Ctr"}}, - {"place-pktrm", {"Park Street", "Park St"}}, - {"place-boyls", {"Boylston", "Boylston"}}, - {"place-armnl", {"Arlington", "Arlington"}}, - {"place-coecl", {"Copley", "Copley"}}, - {"place-prmnl", {"Prudential", "Prudential"}}, - {"place-symcl", {"Symphony", "Symphony"}}, - {"place-nuniv", {"Northeastern University", "Northeast'n"}}, - {"place-mfa", {"Museum of Fine Arts", "MFA"}}, - {"place-lngmd", {"Longwood Medical Area", "Lngwd Med"}}, - {"place-brmnl", {"Brigham Circle", "Brigham Cir"}}, - {"place-fenwd", {"Fenwood Road", "Fenwood Rd"}}, - {"place-mispk", {"Mission Park", "Mission Pk"}}, - {"place-rvrwy", {"Riverway", "Riverway"}}, - {"place-bckhl", {"Back of the Hill", "Back o'Hill"}}, - {"place-hsmnl", {"Heath Street", "Heath St"}} - ] - - @green_line_trunk_stops [ - {"place-lech", {"Lechmere", "Lechmere"}}, - {"place-spmnl", {"Science Park/West End", "Science Pk"}}, - {"place-north", {"North Station", "North Sta"}}, - {"place-haecl", {"Haymarket", "Haymarket"}}, - {"place-gover", {"Government Center", "Gov't Ctr"}}, - {"place-pktrm", {"Park Street", "Park St"}}, - {"place-boyls", {"Boylston", "Boylston"}}, - {"place-armnl", {"Arlington", "Arlington"}}, - {"place-coecl", {"Copley", "Copley"}}, - {"place-hymnl", {"Hynes Convention Center", "Hynes"}}, - {"place-kencl", {"Kenmore", "Kenmore"}} - ] - - @medford_tufts_branch_stops [ - {"place-mdftf", {"Medford / Tufts", "Medford"}}, - {"place-balsq", {"Ball Square", "Ball Sq"}}, - {"place-mgngl", {"Magoun Square", "Magoun Sq"}}, - {"place-gilmn", {"Gilman Square", "Gilman Sq"}}, - {"place-esomr", {"East Somerville", "E Somerville"}} - ] - - @union_square_branch_stops [ - {"place-unsqu", {"Union Square", "Union Sq"}} - ] - - @route_stop_sequences %{ - "Blue" => [@blue_line_stops], - "Orange" => [@orange_line_stops], - "Red" => [ - @red_line_trunk_stops ++ @red_line_ashmont_branch_stops, - @red_line_trunk_stops ++ @red_line_braintree_branch_stops - ], - "Green-B" => [@green_line_b_stops], - "Green-C" => [@green_line_c_stops], - "Green-D" => [@green_line_d_stops], - "Green-E" => [@green_line_e_stops], - "Green" => [@green_line_trunk_stops] - } - - @green_line_branches ["Green-B", "Green-C", "Green-D", "Green-E"] - - # --- These functions involve the API --- - def fetch_parent_station_name_map(get_json_fn \\ &V3Api.get_json/2) do case get_json_fn.("stops", %{ "filter[location_type]" => 1 @@ -342,92 +117,6 @@ defmodule Screens.Stops.Stop do end end - # --- END API functions --- - - def stop_on_route?(stop_id, stop_sequence) when not is_nil(stop_id) do - Enum.any?(stop_sequence, fn {station_id, _} -> station_id == stop_id end) - end - - def to_stop_index(%{stop: stop_id}, stop_sequence) do - Enum.find_index(stop_sequence, fn {station_id, _} -> station_id == stop_id end) - end - - # --- These use the local version of the stop_sequences from the top of this file --- - - @doc """ - Finds a stop sequence which contains all stations in informed_entities. - """ - def get_stop_sequence(informed_entities, "Green") do - Enum.find_value(@green_line_branches, fn branch -> - get_stop_sequence(informed_entities, branch) - end) - end - - def get_stop_sequence(informed_entities, route_id) do - stop_sequences = Map.get(@route_stop_sequences, route_id) - Enum.find(stop_sequences, &sequence_match?(&1, informed_entities)) - end - - def get_route_stop_sequence(route_id) do - @route_stop_sequences - |> Map.get(route_id) - |> Enum.flat_map(fn x -> x end) - |> Enum.map(fn {k, _v} -> k end) - |> Enum.uniq() - end - - def get_all_routes_stop_sequence do - @route_stop_sequences - end - - def get_gl_stop_sequences do - Enum.map(@green_line_branches, &get_route_stop_sequence/1) - end - - @doc """ - Returns an unordered MapSet of all GL stops west of Copley. - """ - @spec get_gl_stops_west_of_copley() :: MapSet.t(id()) - def get_gl_stops_west_of_copley do - get_gl_stop_sequences() - |> Enum.flat_map(fn stop_sequence -> - [_copley | west_of_copley] = Enum.drop_while(stop_sequence, &(&1 != "place-coecl")) - west_of_copley - end) - |> MapSet.new() - end - - defp sequence_match?(stop_sequence, informed_entities) do - ie_stops = - informed_entities - |> Enum.map(fn %{stop: stop_id} -> stop_id end) - |> Enum.reject(&is_nil/1) - - if Enum.empty?(ie_stops) do - nil - else - ie_stops - |> Enum.filter(&String.starts_with?(&1, "place-")) - |> Enum.all?(&stop_on_route?(&1, stop_sequence)) - end - end - - def gl_trunk_stops do - @green_line_trunk_stops - end - - def rl_trunk_stops do - @red_line_trunk_stops - end - - def stop_id_to_name(route_id) do - @route_stop_sequences - |> Map.get(route_id) - |> Enum.flat_map(fn x -> x end) - |> Enum.uniq() - |> Enum.into(%{}) - end - @doc """ Fetches all the location context for a screen given its app type, stop id, and time """ @@ -472,8 +161,7 @@ defmodule Screens.Stops.Stop do end # Returns the route types we care about for the alerts of this screen type / place - @spec get_route_type_filter(screen_type(), String.t()) :: - list(RouteType.t()) + @spec get_route_type_filter(screen_type(), String.t()) :: list(RouteType.t()) def get_route_type_filter(app, _) when app in [BusEink, BusShelter], do: [:bus] def get_route_type_filter(GlEink, _), do: [:light_rail] # Ashmont should not show Mattapan alerts for PreFare or Dup @@ -497,18 +185,6 @@ defmodule Screens.Stops.Stop do |> MapSet.new() end - def on_glx?(stop_id) do - stop_id in Enum.map(@medford_tufts_branch_stops ++ @union_square_branch_stops, &elem(&1, 0)) - end - - def on_ashmont_branch?(stop_id) do - stop_id in Enum.map(@red_line_ashmont_branch_stops, &elem(&1, 0)) - end - - def on_braintree_branch?(stop_id) do - stop_id in Enum.map(@red_line_braintree_branch_stops, &elem(&1, 0)) - end - defp fetch_tagged_stop_sequences_by_app(app, stop_id, _routes_at_stop) when app in [BusEink, BusShelter, GlEink] do RoutePattern.fetch_tagged_stop_sequences_through_stop(stop_id) diff --git a/lib/screens/stops/subway.ex b/lib/screens/stops/subway.ex new file mode 100644 index 000000000..275fa0a8d --- /dev/null +++ b/lib/screens/stops/subway.ex @@ -0,0 +1,318 @@ +defmodule Screens.Stops.Subway do + @moduledoc """ + Functions that operate on a hard-coded copy of the "canonical" parent station sequences for + subway lines. Also encodes abbreviated forms of the stations' names, e.g. for use in disruption + diagrams. + """ + + alias Screens.Alerts.InformedEntity + alias Screens.Routes.Route + alias Screens.Stops.Stop + + @blue_line_stops [ + {"place-wondl", {"Wonderland", "Wonderland"}}, + {"place-rbmnl", {"Revere Beach", "Revere Bch"}}, + {"place-bmmnl", {"Beachmont", "Beachmont"}}, + {"place-sdmnl", {"Suffolk Downs", "Suffolk Dns"}}, + {"place-orhte", {"Orient Heights", "Orient Hts"}}, + {"place-wimnl", {"Wood Island", "Wood Island"}}, + {"place-aport", {"Airport", "Airport"}}, + {"place-mvbcl", {"Maverick", "Maverick"}}, + {"place-aqucl", {"Aquarium", "Aquarium"}}, + {"place-state", {"State", "State"}}, + {"place-gover", {"Government Center", "Gov't Ctr"}}, + {"place-bomnl", {"Bowdoin", "Bowdoin"}} + ] + + @orange_line_stops [ + {"place-ogmnl", {"Oak Grove", "Oak Grove"}}, + {"place-mlmnl", {"Malden Center", "Malden Ctr"}}, + {"place-welln", {"Wellington", "Wellington"}}, + {"place-astao", {"Assembly", "Assembly"}}, + {"place-sull", {"Sullivan Square", "Sullivan Sq"}}, + {"place-ccmnl", {"Community College", "Com College"}}, + {"place-north", {"North Station", "North Sta"}}, + {"place-haecl", {"Haymarket", "Haymarket"}}, + {"place-state", {"State", "State"}}, + {"place-dwnxg", {"Downtown Crossing", "Downt'n Xng"}}, + {"place-chncl", {"Chinatown", "Chinatown"}}, + {"place-tumnl", {"Tufts Medical Center", "Tufts Med"}}, + {"place-bbsta", {"Back Bay", "Back Bay"}}, + {"place-masta", {"Massachusetts Avenue", "Mass Ave"}}, + {"place-rugg", {"Ruggles", "Ruggles"}}, + {"place-rcmnl", {"Roxbury Crossing", "Roxbury Xng"}}, + {"place-jaksn", {"Jackson Square", "Jackson Sq"}}, + {"place-sbmnl", {"Stony Brook", "Stony Brook"}}, + {"place-grnst", {"Green Street", "Green St"}}, + {"place-forhl", {"Forest Hills", "Frst Hills"}} + ] + + @red_line_trunk_stops [ + {"place-alfcl", {"Alewife", "Alewife"}}, + {"place-davis", {"Davis", "Davis"}}, + {"place-portr", {"Porter", "Porter"}}, + {"place-harsq", {"Harvard", "Harvard"}}, + {"place-cntsq", {"Central", "Central"}}, + {"place-knncl", {"Kendall/MIT", "Kendall/MIT"}}, + {"place-chmnl", {"Charles/MGH", "Charles/MGH"}}, + {"place-pktrm", {"Park Street", "Park St"}}, + {"place-dwnxg", {"Downtown Crossing", "Downt'n Xng"}}, + {"place-sstat", {"South Station", "South Sta"}}, + {"place-brdwy", {"Broadway", "Broadway"}}, + {"place-andrw", {"Andrew", "Andrew"}}, + {"place-jfk", {"JFK/UMass", "JFK/UMass"}} + ] + + @red_line_ashmont_branch_stops [ + {"place-shmnl", {"Savin Hill", "Savin Hill"}}, + {"place-fldcr", {"Fields Corner", "Fields Cnr"}}, + {"place-smmnl", {"Shawmut", "Shawmut"}}, + {"place-asmnl", {"Ashmont", "Ashmont"}} + ] + + @red_line_braintree_branch_stops [ + {"place-nqncy", {"North Quincy", "N Quincy"}}, + {"place-wlsta", {"Wollaston", "Wollaston"}}, + {"place-qnctr", {"Quincy Center", "Quincy Ctr"}}, + {"place-qamnl", {"Quincy Adams", "Quincy Adms"}}, + {"place-brntn", {"Braintree", "Braintree"}} + ] + + @green_line_b_stops [ + {"place-gover", {"Government Center", "Gov't Ctr"}}, + {"place-pktrm", {"Park Street", "Park St"}}, + {"place-boyls", {"Boylston", "Boylston"}}, + {"place-armnl", {"Arlington", "Arlington"}}, + {"place-coecl", {"Copley", "Copley"}}, + {"place-hymnl", {"Hynes Convention Center", "Hynes"}}, + {"place-kencl", {"Kenmore", "Kenmore"}}, + {"place-bland", {"Blandford Street", "Blandford"}}, + {"place-buest", {"Boston University East", "BU East"}}, + {"place-bucen", {"Boston University Central", "BU Central"}}, + {"place-amory", {"Amory Street", "Amory St"}}, + {"place-babck", {"Babcock Street", "Babcock St"}}, + {"place-brico", {"Packards Corner", "Packards Cn"}}, + {"place-harvd", {"Harvard Avenue", "Harvard Ave"}}, + {"place-grigg", {"Griggs Street", "Griggs St"}}, + {"place-alsgr", {"Allston Street", "Allston St"}}, + {"place-wrnst", {"Warren Street", "Warren St"}}, + {"place-wascm", {"Washington Street", "Washington"}}, + {"place-sthld", {"Sutherland Road", "Sutherland"}}, + {"place-chswk", {"Chiswick Road", "Chiswick Rd"}}, + {"place-chill", {"Chestnut Hill Avenue", "Chestnut Hl"}}, + {"place-sougr", {"South Street", "South St"}}, + {"place-lake", {"Boston College", "Boston Coll"}} + ] + + @green_line_c_stops [ + {"place-gover", {"Government Center", "Gov't Ctr"}}, + {"place-pktrm", {"Park Street", "Park St"}}, + {"place-boyls", {"Boylston", "Boylston"}}, + {"place-armnl", {"Arlington", "Arlington"}}, + {"place-coecl", {"Copley", "Copley"}}, + {"place-hymnl", {"Hynes Convention Center", "Hynes"}}, + {"place-kencl", {"Kenmore", "Kenmore"}}, + {"place-smary", {"Saint Mary's Street", "St. Mary's"}}, + {"place-hwsst", {"Hawes Street", "Hawes St"}}, + {"place-kntst", {"Kent Street", "Kent St"}}, + {"place-stpul", {"Saint Paul Street", "St. Paul St"}}, + {"place-cool", {"Coolidge Corner", "Coolidge Cn"}}, + {"place-sumav", {"Summit Avenue", "Summit Ave"}}, + {"place-bndhl", {"Brandon Hall", "Brandon Hll"}}, + {"place-fbkst", {"Fairbanks Street", "Fairbanks"}}, + {"place-bcnwa", {"Washington Square", "Washington"}}, + {"place-tapst", {"Tappan Street", "Tappan St"}}, + {"place-denrd", {"Dean Road", "Dean Rd"}}, + {"place-engav", {"Englewood Avenue", "Englew'd Av"}}, + {"place-clmnl", {"Cleveland Circle", "Clvlnd Cir"}} + ] + + @green_line_d_stops [ + {"place-unsqu", {"Union Square", "Union Sq"}}, + {"place-lech", {"Lechmere", "Lechmere"}}, + {"place-spmnl", {"Science Park/West End", "Science Pk"}}, + {"place-north", {"North Station", "North Sta"}}, + {"place-haecl", {"Haymarket", "Haymarket"}}, + {"place-gover", {"Government Center", "Gov't Ctr"}}, + {"place-pktrm", {"Park Street", "Park St"}}, + {"place-boyls", {"Boylston", "Boylston"}}, + {"place-armnl", {"Arlington", "Arlington"}}, + {"place-coecl", {"Copley", "Copley"}}, + {"place-hymnl", {"Hynes Convention Center", "Hynes"}}, + {"place-kencl", {"Kenmore", "Kenmore"}}, + {"place-fenwy", {"Fenway", "Fenway"}}, + {"place-longw", {"Longwood", "Longwood"}}, + {"place-bvmnl", {"Brookline Village", "B'kline Vil"}}, + {"place-brkhl", {"Brookline Hills", "B'kline Hls"}}, + {"place-bcnfd", {"Beaconsfield", "B'consfield"}}, + {"place-rsmnl", {"Reservoir", "Reservoir"}}, + {"place-chhil", {"Chestnut Hill", "Chestnut Hl"}}, + {"place-newto", {"Newton Centre", "Newton Ctr"}}, + {"place-newtn", {"Newton Highlands", "Newton Hlnd"}}, + {"place-eliot", {"Eliot", "Eliot"}}, + {"place-waban", {"Waban", "Waban"}}, + {"place-woodl", {"Woodland", "Woodland"}}, + {"place-river", {"Riverside", "Riverside"}} + ] + + @green_line_e_stops [ + {"place-mdftf", {"Medford / Tufts", "Medford"}}, + {"place-balsq", {"Ball Square", "Ball Sq"}}, + {"place-mgngl", {"Magoun Square", "Magoun Sq"}}, + {"place-gilmn", {"Gilman Square", "Gilman Sq"}}, + {"place-esomr", {"East Somerville", "E Somerville"}}, + {"place-lech", {"Lechmere", "Lechmere"}}, + {"place-spmnl", {"Science Park/West End", "Science Pk"}}, + {"place-north", {"North Station", "North Sta"}}, + {"place-haecl", {"Haymarket", "Haymarket"}}, + {"place-gover", {"Government Center", "Gov't Ctr"}}, + {"place-pktrm", {"Park Street", "Park St"}}, + {"place-boyls", {"Boylston", "Boylston"}}, + {"place-armnl", {"Arlington", "Arlington"}}, + {"place-coecl", {"Copley", "Copley"}}, + {"place-prmnl", {"Prudential", "Prudential"}}, + {"place-symcl", {"Symphony", "Symphony"}}, + {"place-nuniv", {"Northeastern University", "Northeast'n"}}, + {"place-mfa", {"Museum of Fine Arts", "MFA"}}, + {"place-lngmd", {"Longwood Medical Area", "Lngwd Med"}}, + {"place-brmnl", {"Brigham Circle", "Brigham Cir"}}, + {"place-fenwd", {"Fenwood Road", "Fenwood Rd"}}, + {"place-mispk", {"Mission Park", "Mission Pk"}}, + {"place-rvrwy", {"Riverway", "Riverway"}}, + {"place-bckhl", {"Back of the Hill", "Back o'Hill"}}, + {"place-hsmnl", {"Heath Street", "Heath St"}} + ] + + @green_line_trunk_stops [ + {"place-lech", {"Lechmere", "Lechmere"}}, + {"place-spmnl", {"Science Park/West End", "Science Pk"}}, + {"place-north", {"North Station", "North Sta"}}, + {"place-haecl", {"Haymarket", "Haymarket"}}, + {"place-gover", {"Government Center", "Gov't Ctr"}}, + {"place-pktrm", {"Park Street", "Park St"}}, + {"place-boyls", {"Boylston", "Boylston"}}, + {"place-armnl", {"Arlington", "Arlington"}}, + {"place-coecl", {"Copley", "Copley"}}, + {"place-hymnl", {"Hynes Convention Center", "Hynes"}}, + {"place-kencl", {"Kenmore", "Kenmore"}} + ] + + @medford_tufts_branch_stops [ + {"place-mdftf", {"Medford / Tufts", "Medford"}}, + {"place-balsq", {"Ball Square", "Ball Sq"}}, + {"place-mgngl", {"Magoun Square", "Magoun Sq"}}, + {"place-gilmn", {"Gilman Square", "Gilman Sq"}}, + {"place-esomr", {"East Somerville", "E Somerville"}} + ] + + @union_square_branch_stops [ + {"place-unsqu", {"Union Square", "Union Sq"}} + ] + + @route_stop_sequences %{ + "Blue" => [@blue_line_stops], + "Orange" => [@orange_line_stops], + "Red" => [ + @red_line_trunk_stops ++ @red_line_ashmont_branch_stops, + @red_line_trunk_stops ++ @red_line_braintree_branch_stops + ], + "Green-B" => [@green_line_b_stops], + "Green-C" => [@green_line_c_stops], + "Green-D" => [@green_line_d_stops], + "Green-E" => [@green_line_e_stops], + "Green" => [@green_line_trunk_stops] + } + + @green_line_branches ["Green-B", "Green-C", "Green-D", "Green-E"] + + @type station :: {Stop.id(), station_names()} + @type station_names :: {full :: String.t(), abbreviated :: String.t()} + + def all_stop_sequences, do: @route_stop_sequences + def gl_trunk_stops, do: @green_line_trunk_stops + def rl_trunk_stops, do: @red_line_trunk_stops + + @spec ashmont_branch_stop?(Stop.id()) :: boolean() + def ashmont_branch_stop?(stop_id) do + stop_id in Enum.map(@red_line_ashmont_branch_stops, &elem(&1, 0)) + end + + @spec braintree_branch_stop?(Stop.id()) :: boolean() + def braintree_branch_stop?(stop_id) do + stop_id in Enum.map(@red_line_braintree_branch_stops, &elem(&1, 0)) + end + + @spec gl_stop_sequences() :: [[Stop.id()]] + def gl_stop_sequences, do: Enum.map(@green_line_branches, &route_stop_sequence/1) + + @spec gl_stops_west_of_copley() :: MapSet.t(Stop.id()) + def gl_stops_west_of_copley do + gl_stop_sequences() + |> Enum.flat_map(fn stop_sequence -> + [_copley | west_of_copley] = Enum.drop_while(stop_sequence, &(&1 != "place-coecl")) + west_of_copley + end) + |> MapSet.new() + end + + @spec glx_stop?(Stop.id()) :: boolean() + def glx_stop?(stop_id) do + stop_id in Enum.map(@medford_tufts_branch_stops ++ @union_square_branch_stops, &elem(&1, 0)) + end + + @spec stop_index_for_informed_entity(InformedEntity.t(), [station()]) :: non_neg_integer() | nil + def stop_index_for_informed_entity(%{stop: stop_id}, stop_sequence) do + Enum.find_index(stop_sequence, fn {station_id, _} -> station_id == stop_id end) + end + + @spec stop_on_route?(Stop.id(), [station()]) :: boolean() + def stop_on_route?(stop_id, stop_sequence) when not is_nil(stop_id) do + Enum.any?(stop_sequence, fn {station_id, _} -> station_id == stop_id end) + end + + @spec stop_sequence_containing_informed_entities([InformedEntity.t()], Route.id()) :: + [station()] | nil + def stop_sequence_containing_informed_entities(informed_entities, "Green") do + Enum.find_value(@green_line_branches, fn branch -> + stop_sequence_containing_informed_entities(informed_entities, branch) + end) + end + + def stop_sequence_containing_informed_entities(informed_entities, route_id) do + stop_sequences = Map.get(@route_stop_sequences, route_id) + Enum.find(stop_sequences, &sequence_match?(&1, informed_entities)) + end + + @spec route_stop_names(Route.id()) :: %{Stop.id() => station_names()} + def route_stop_names(route_id) do + @route_stop_sequences + |> Map.get(route_id) + |> Enum.flat_map(fn x -> x end) + |> Enum.uniq() + |> Enum.into(%{}) + end + + @spec route_stop_sequence(Route.id()) :: [Stop.id()] + def route_stop_sequence(route_id) do + @route_stop_sequences + |> Map.get(route_id) + |> Enum.flat_map(fn x -> x end) + |> Enum.map(fn {k, _v} -> k end) + |> Enum.uniq() + end + + defp sequence_match?(stop_sequence, informed_entities) do + ie_stops = + informed_entities + |> Enum.map(fn %{stop: stop_id} -> stop_id end) + |> Enum.reject(&is_nil/1) + + if Enum.empty?(ie_stops) do + nil + else + ie_stops + |> Enum.filter(&String.starts_with?(&1, "place-")) + |> Enum.all?(&stop_on_route?(&1, stop_sequence)) + end + end +end diff --git a/lib/screens/v2/disruption_diagram/builder.ex b/lib/screens/v2/disruption_diagram/builder.ex index f4c8eaafb..67f159b06 100644 --- a/lib/screens/v2/disruption_diagram/builder.ex +++ b/lib/screens/v2/disruption_diagram/builder.ex @@ -7,7 +7,7 @@ defmodule Screens.V2.DisruptionDiagram.Builder do alias Aja.Vector alias Screens.Routes.Route - alias Screens.Stops.Stop + alias Screens.Stops.{Stop, Subway} alias Screens.V2.DisruptionDiagram, as: DD alias Screens.V2.DisruptionDiagram.Label alias Screens.V2.LocalizedAlert @@ -116,12 +116,12 @@ defmodule Screens.V2.DisruptionDiagram.Builder do get_builder_data(localized_alert, informed_stop_ids) do line = Route.color(route_id) - stop_id_to_name = Stop.stop_id_to_name(route_id) + stop_names = Subway.route_stop_names(route_id) slot_sequence = stop_sequence |> Vector.new(fn stop_id -> - {full, abbrev} = Map.fetch!(stop_id_to_name, stop_id) + {full, abbrev} = Map.fetch!(stop_names, stop_id) %StopSlot{ id: stop_id, @@ -331,7 +331,7 @@ defmodule Screens.V2.DisruptionDiagram.Builder do diagram_contains_glx = Aja.Enum.any?(builder.sequence, fn - %StopSlot{} = stop_data -> Stop.on_glx?(stop_data.id) + %StopSlot{} = stop_data -> Subway.glx_stop?(stop_data.id) _ -> false end) @@ -425,16 +425,16 @@ defmodule Screens.V2.DisruptionDiagram.Builder do true -> # Orange Line, probably at North Station, Haymarket, State, or Downtown Crossing # or Blue Line, probably at Government Center or State - {:ok, informed_route_id, Stop.get_route_stop_sequence(informed_route_id), :trunk} + {:ok, informed_route_id, Subway.route_stop_sequence(informed_route_id), :trunk} end end defp gl_trunk_stop_sequence do - Enum.map(Stop.gl_trunk_stops(), fn {stop_id, _labels} -> stop_id end) + Enum.map(Subway.gl_trunk_stops(), fn {stop_id, _labels} -> stop_id end) end defp rl_trunk_stop_sequence do - Enum.map(Stop.rl_trunk_stops(), fn {stop_id, _labels} -> stop_id end) + Enum.map(Subway.rl_trunk_stops(), fn {stop_id, _labels} -> stop_id end) end # Adjusts the left and right ends of the sequence before we split them off into `left_end` and `right_end`. diff --git a/lib/screens/v2/widget_instance/reconstructed_alert.ex b/lib/screens/v2/widget_instance/reconstructed_alert.ex index abe0c22df..15047b98c 100644 --- a/lib/screens/v2/widget_instance/reconstructed_alert.ex +++ b/lib/screens/v2/widget_instance/reconstructed_alert.ex @@ -5,7 +5,7 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlert do alias Screens.Alerts.InformedEntity alias Screens.LocationContext alias Screens.Routes.Route - alias Screens.Stops.Stop + alias Screens.Stops.{Stop, Subway} alias Screens.Util alias Screens.V2.DisruptionDiagram alias Screens.V2.LocalizedAlert @@ -189,10 +189,10 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlert do ) when stop_id != nil and location in [:downstream, :boundary_downstream] do cond do - Stop.on_ashmont_branch?(stop_id) -> + Subway.ashmont_branch_stop?(stop_id) -> {0, "Red-Ashmont"} - Stop.on_braintree_branch?(stop_id) -> + Subway.braintree_branch_stop?(stop_id) -> {0, "Red-Braintree"} true -> @@ -208,10 +208,10 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlert do ) when stop_id != nil and location in [:upstream, :boundary_upstream] do cond do - Stop.on_ashmont_branch?(stop_id) -> + Subway.ashmont_branch_stop?(stop_id) -> {1, "Red-Ashmont"} - Stop.on_braintree_branch?(stop_id) -> + Subway.braintree_branch_stop?(stop_id) -> {1, "Red-Braintree"} true -> @@ -1134,15 +1134,15 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlert do end def do_get_endpoints(informed_entities, route_id) do - case Stop.get_stop_sequence(informed_entities, route_id) do + case Subway.stop_sequence_containing_informed_entities(informed_entities, route_id) do nil -> nil stop_sequence -> {min_index, max_index} = informed_entities - |> Enum.filter(&Stop.stop_on_route?(&1.stop, stop_sequence)) - |> Enum.map(&Stop.to_stop_index(&1, stop_sequence)) + |> Enum.filter(&Subway.stop_on_route?(&1.stop, stop_sequence)) + |> Enum.map(&Subway.stop_index_for_informed_entity(&1, stop_sequence)) |> Enum.min_max() {_, min_station_name} = Enum.at(stop_sequence, min_index) @@ -1189,9 +1189,9 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlert do # It's ok if the home stop is duplicated in this list due to also being informed by the alert. relevant_parent_stations = [home_stop | Enum.map(parent_station_ies, & &1.stop)] - includes_glx = Enum.any?(relevant_parent_stations, &Stop.on_glx?/1) + includes_glx = Enum.any?(relevant_parent_stations, &Subway.glx_stop?/1) - stops_west_of_copley = Stop.get_gl_stops_west_of_copley() + stops_west_of_copley = Subway.gl_stops_west_of_copley() includes_west_of_copley = Enum.any?(relevant_parent_stations, &(&1 in stops_west_of_copley)) includes_glx and not includes_west_of_copley diff --git a/lib/screens/v2/widget_instance/subway_status.ex b/lib/screens/v2/widget_instance/subway_status.ex index 1b76b52d7..0e1f37095 100644 --- a/lib/screens/v2/widget_instance/subway_status.ex +++ b/lib/screens/v2/widget_instance/subway_status.ex @@ -6,7 +6,7 @@ defmodule Screens.V2.WidgetInstance.SubwayStatus do alias Screens.Alerts.Alert alias Screens.Alerts.InformedEntity alias Screens.Routes.Route - alias Screens.Stops.Stop + alias Screens.Stops.Subway alias Screens.V2.WidgetInstance.SubwayStatus alias ScreensConfig.Screen alias ScreensConfig.V2.{BusEink, Footer, GlEink, PreFare} @@ -334,15 +334,15 @@ defmodule Screens.V2.WidgetInstance.SubwayStatus do # credo:disable-for-next-line # TODO: get_endpoints is a common function; could be consolidated defp get_endpoints(informed_entities, route_id) do - case Stop.get_stop_sequence(informed_entities, route_id) do + case Subway.stop_sequence_containing_informed_entities(informed_entities, route_id) do nil -> nil stop_sequence -> {min_index, max_index} = informed_entities - |> Enum.filter(&Stop.stop_on_route?(&1.stop, stop_sequence)) - |> Enum.map(&Stop.to_stop_index(&1, stop_sequence)) + |> Enum.filter(&Subway.stop_on_route?(&1.stop, stop_sequence)) + |> Enum.map(&Subway.stop_index_for_informed_entity(&1, stop_sequence)) |> Enum.min_max() {_, {min_full_name, min_abbreviated_name}} = Enum.at(stop_sequence, min_index) @@ -573,7 +573,7 @@ defmodule Screens.V2.WidgetInstance.SubwayStatus do if gl_alert_count == 0 do serialize_single_alert_row_for_route(grouped_alerts, "Green", total_alert_count) else - gl_stop_sets = Enum.map(Stop.get_gl_stop_sequences(), &MapSet.new/1) + gl_stop_sets = Enum.map(Subway.gl_stop_sequences(), &MapSet.new/1) {trunk_alerts, branch_alerts} = Enum.split_with( @@ -720,9 +720,9 @@ defmodule Screens.V2.WidgetInstance.SubwayStatus do end) |> Enum.flat_map(fn %{stop: stop_id, route: route_id} -> - stop_id_to_name = Stop.stop_id_to_name(route_id) + stop_names = Subway.route_stop_names(route_id) - case Map.get(stop_id_to_name, stop_id) do + case Map.get(stop_names, stop_id) do nil -> [] name -> [name] end diff --git a/test/screens/v2/widget_instance/reconstructed_alert_property_test.exs b/test/screens/v2/widget_instance/reconstructed_alert_property_test.exs index d94b886c7..156135f05 100644 --- a/test/screens/v2/widget_instance/reconstructed_alert_property_test.exs +++ b/test/screens/v2/widget_instance/reconstructed_alert_property_test.exs @@ -15,7 +15,7 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlertPropertyTest do alias ScreensConfig.V2.Header.CurrentStopId alias Screens.LocationContext alias Screens.RoutePatterns.RoutePattern - alias Screens.Stops.Stop + alias Screens.Stops.{Stop, Subway} alias Screens.Util alias Screens.V2.CandidateGenerator alias Screens.V2.WidgetInstance.ReconstructedAlert @@ -1121,7 +1121,7 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlertPropertyTest do {:ok, now_datetime} = DateTime.from_unix(1_659_299_615) route_ids_at_stop = - Stop.get_all_routes_stop_sequence() + Subway.all_stop_sequences() |> Enum.filter(fn {_k, v} -> Enum.any?(v, fn sequence -> Enum.any?(sequence, fn stop_object -> @@ -1143,7 +1143,7 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlertPropertyTest do end) tagged_station_sequences = - Map.new(route_ids_at_stop, fn id -> {id, [Stop.get_route_stop_sequence(id)]} end) + Map.new(route_ids_at_stop, fn id -> {id, [Subway.route_stop_sequence(id)]} end) station_sequences = RoutePattern.untag_stop_sequences(tagged_station_sequences) diff --git a/test/screens/v2/widget_instance/reconstructed_alert_test.exs b/test/screens/v2/widget_instance/reconstructed_alert_test.exs index 7823688d7..83e0ac179 100644 --- a/test/screens/v2/widget_instance/reconstructed_alert_test.exs +++ b/test/screens/v2/widget_instance/reconstructed_alert_test.exs @@ -7,7 +7,7 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlertTest do alias ScreensConfig.V2.Header.CurrentStopId alias Screens.LocationContext alias Screens.RoutePatterns.RoutePattern - alias Screens.Stops.Stop + alias Screens.Stops.{Stop, Subway} alias Screens.V2.AlertsWidget alias Screens.V2.CandidateGenerator alias Screens.V2.WidgetInstance @@ -2693,7 +2693,7 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlertTest do ] now = ~U[2022-06-24 12:00:00Z] - tagged_station_sequences = %{"Orange" => [Stop.get_route_stop_sequence("Orange")]} + tagged_station_sequences = %{"Orange" => [Subway.route_stop_sequence("Orange")]} station_sequences = RoutePattern.untag_stop_sequences(tagged_station_sequences) fetch_alerts_fn = fn _ -> {:ok, alerts} end @@ -3110,7 +3110,7 @@ defmodule Screens.V2.WidgetInstance.ReconstructedAlertTest do ] now = ~U[2022-06-24 12:00:00Z] - tagged_station_sequences = %{"Green" => [Stop.get_route_stop_sequence("Green")]} + tagged_station_sequences = %{"Green" => [Subway.route_stop_sequence("Green")]} station_sequences = RoutePattern.untag_stop_sequences(tagged_station_sequences) fetch_alerts_fn = fn _ -> {:ok, alerts} end diff --git a/test/support/disruption_diagram_localized_alert.ex b/test/support/disruption_diagram_localized_alert.ex index b57237f2d..e2e9add87 100644 --- a/test/support/disruption_diagram_localized_alert.ex +++ b/test/support/disruption_diagram_localized_alert.ex @@ -9,7 +9,7 @@ defmodule Screens.TestSupport.DisruptionDiagramLocalizedAlert do alias Screens.Alerts.Alert alias Screens.LocationContext - alias Screens.Stops.Stop + alias Screens.Stops.Subway @doc """ Creates a localized alert with the given effect, located at the given home station. @@ -104,7 +104,7 @@ defmodule Screens.TestSupport.DisruptionDiagramLocalizedAlert do defp stop_range_to_list({first_station_id, last_station_id}) do endpoints_set = MapSet.new([first_station_id, last_station_id]) - Stop.get_all_routes_stop_sequence() + Subway.all_stop_sequences() |> Enum.find_value(fn {_route_id, labeled_sequences} -> Enum.find_value(labeled_sequences, fn labeled_sequence -> @@ -127,7 +127,7 @@ defmodule Screens.TestSupport.DisruptionDiagramLocalizedAlert do # Returns IDs of the subway/light rail route(s) that serve the given station, # using our hardcoded stop sequences rather than API calls. defp subway_routes_at_station(parent_station_id) do - Stop.get_all_routes_stop_sequence() + Subway.all_stop_sequences() |> Enum.filter(fn # Green isn't a real route ID, ignore it. {"Green", _} -> @@ -146,7 +146,7 @@ defmodule Screens.TestSupport.DisruptionDiagramLocalizedAlert do # Returns a %{route => stop_sequences} map for all sequences that that contain the given subway/light rail station. defp tagged_stop_sequences_through_station(parent_station_id) do - Stop.get_all_routes_stop_sequence() + Subway.all_stop_sequences() |> Enum.flat_map(fn # Green isn't a real route ID, ignore it. {"Green", _} -> @@ -166,7 +166,7 @@ defmodule Screens.TestSupport.DisruptionDiagramLocalizedAlert do # Returns IDs of the route(s) whose stop sequence(s) contain all of the given stops. defp routes_containing_all(parent_station_ids) do - Stop.get_all_routes_stop_sequence() + Subway.all_stop_sequences() |> Enum.filter(fn # Green isn't a real route ID, ignore it. {"Green", _} ->