Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions assets/src/components/departures/departure_time.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,16 @@ const DepartureTimePart: ComponentType<DepartureTimePartProps> = ({
);
}

case "status":
return (
<div className="departure-time__status">{time.pages[currentPage]}</div>
);
case "status": {
const pages = time.pages;
if (pages.length === 1) {
return <div className="departure-time__status">{pages[0]}</div>;
} else {
return (
<div className="departure-time__status">{pages[currentPage]}</div>
);
}
}

case "overnight":
return <MoonIcon width={128} height={128} color="black" />;
Expand Down
2 changes: 1 addition & 1 deletion lib/screens/headways.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ defmodule Screens.Headways do
green_c: [70211..70220, 70223..70238],
green_d: [70160..70183, 70186..70187],
green_e: [70239..70258, 70260..70260],
green_trunk: [70151..70159, 70196..70208, 70501..70502, 71150..71151],
green_trunk: [70150..70159, 70196..70208, 70501..70502, 71150..71151],
mattapan_trunk: [70261..70261, 70263..70276],
orange_trunk: [70001..70036, 70278..70279],
red_ashmont: [70085..70094],
Expand Down
122 changes: 112 additions & 10 deletions lib/screens/v2/candidate_generator/dup_new/departures.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ defmodule Screens.V2.CandidateGenerator.DupNew.Departures do

alias Screens.V2.Departure
alias Screens.V2.RDS
alias Screens.V2.RDS.{Countdowns, FirstTrip, NoService, ServiceEnded}
alias Screens.V2.RDS.{Countdowns, FirstTrip, Headways, NoService, ServiceEnded}

alias Screens.V2.WidgetInstance.Departures, as: DeparturesWidget

alias Screens.V2.WidgetInstance.Departures.{
HeadwaySection,
NoDataSection,
NormalSection,
NoServiceSection,
Expand Down Expand Up @@ -117,7 +118,9 @@ defmodule Screens.V2.CandidateGenerator.DupNew.Departures do
num_departures_per_section = div(@max_departures_per_rotation, section_count)

cond do
# all headways -> HeadwaySection()
headways?(rds_list) ->
create_headway_section(rds_list)

no_service?(rds_list) ->
%NoServiceSection{
routes:
Expand Down Expand Up @@ -160,6 +163,67 @@ defmodule Screens.V2.CandidateGenerator.DupNew.Departures do
Enum.all?(rds_list, &is_struct(&1.state, ServiceEnded))
end

defp headways?(rds_list) do
Enum.all?(rds_list, &is_struct(&1.state, Headways))
end

@spec create_headway_section([RDS.t()]) :: HeadwaySection.t()

# Bidirectional -> Use no headsign for the trains message
defp create_headway_section([
%RDS{state: %{route_id: route_id, direction_name: direction_name_one, range: range}}
| [%RDS{state: %{route_id: route_id, direction_name: direction_name_two, range: range}}]
])
when direction_name_one != direction_name_two do
%HeadwaySection{
route: route_id,
time_range: range,
headsign: nil
}
end

# Use the headsign if the destinations have the same headsign,
# use the direction name if they have the same direction name,
# otherwise default to no headsign
defp create_headway_section(
[
%RDS{
headsign: first_headsign,
state: %Headways{
route_id: first_route_id,
direction_name: first_direction_name,
range: first_range
}
}
| _
] = destinations
) do
%HeadwaySection{
route: first_route_id,
time_range: first_range,
headsign:
cond do
Enum.all?(destinations, fn %RDS{headsign: headsign} ->
headsign == first_headsign
end) ->
first_headsign

Enum.all?(
destinations,
fn %RDS{
state: %Headways{route_id: other_route_id, direction_name: other_direction_name}
} ->
other_direction_name == first_direction_name and other_route_id == first_route_id
end
) ->
first_direction_name

true ->
nil
end
}
end

defp build_instances(
slot_names,
_departure_sections,
Expand Down Expand Up @@ -238,10 +302,19 @@ defmodule Screens.V2.CandidateGenerator.DupNew.Departures do

@spec create_and_sort_rows([RDS.t()]) :: [NormalSection.row()]
defp create_and_sort_rows(rds_list) do
{service_ended_rds, rds} =
Enum.split_with(rds_list, &match?(%RDS{state: %ServiceEnded{}}, &1))
grouped_rds =
Enum.group_by(rds_list, fn
%RDS{state: %ServiceEnded{}} -> :service_ended
%RDS{state: %Headways{}} -> :headways
_ -> :other
end)

service_ended_rds = Map.get(grouped_rds, :service_ended, [])
headway_rds = Map.get(grouped_rds, :headways, [])
rds = Map.get(grouped_rds, :other, [])

sorted_departures_from_rds(rds) ++
headways_from_rds(headway_rds) ++
sorted_departures_from_rds(service_ended_rds, true)
end

Expand All @@ -268,7 +341,7 @@ defmodule Screens.V2.CandidateGenerator.DupNew.Departures do
end

rds
|> Enum.flat_map(&departures_from_state(&1))
|> Enum.flat_map(&departure_rows_from_state(&1))
|> Enum.sort_by(
&departure_time(&1),
{
Expand All @@ -278,22 +351,48 @@ defmodule Screens.V2.CandidateGenerator.DupNew.Departures do
)
end

@spec departures_from_state(RDS.t()) :: [Departure.t()]
defp departures_from_state(%RDS{state: %Countdowns{departures: departures}}), do: departures
@spec headways_from_rds([RDS.t()]) :: [NormalSection.headway_row()]
defp headways_from_rds(headway_rds) do
headway_rds
|> Enum.group_by(fn %RDS{line: line, state: %Headways{departure: departure}} ->
direction_id = Departure.direction_id(departure)
route = Departure.route(departure)

direction_name =
route
|> Route.normalized_direction_names()
|> Enum.at(direction_id, nil)

defp departures_from_state(%RDS{
{line, direction_name}
end)
|> Enum.flat_map(fn
{{_line, _direction_name}, [%RDS{state: %Headways{departure: departure, range: range}}]} ->
[{departure, range, nil, :headways}]

# If there are multiple headways with the same line but different headsigns,
# combine them and use the direction name
{{_line, direction_name}, [%RDS{state: %Headways{departure: departure, range: range}} | _]} ->
[{departure, range, direction_name, :headways}]
end)
end

@spec departure_rows_from_state(RDS.t()) ::
[Departure.t()] | [{Departure.t(), NormalSection.special_trip_type()}]
defp departure_rows_from_state(%RDS{state: %Countdowns{departures: departures}}), do: departures

defp departure_rows_from_state(%RDS{
state: %FirstTrip{first_scheduled_departure: first_scheduled_departure}
}) do
[{first_scheduled_departure, :first_trip}]
end

defp departures_from_state(%RDS{
defp departure_rows_from_state(%RDS{
state: %ServiceEnded{last_scheduled_departure: last_scheduled_departure}
}) do
[{last_scheduled_departure, :last_trip}]
end

defp departures_from_state(%RDS{state: %NoService{}}), do: []
defp departure_rows_from_state(%RDS{state: %NoService{}}), do: []

@spec departure_time(Departure.t()) :: DateTime.t()
defp departure_time(%Departure{} = departure), do: Departure.time(departure)
Expand All @@ -311,4 +410,7 @@ defmodule Screens.V2.CandidateGenerator.DupNew.Departures do

defp departure_direction_id({last_scheduled_departure, :last_trip}),
do: Departure.direction_id(last_scheduled_departure)

defp departure_direction_id({departure, _range, _headsign, :headways}),
do: Departure.direction_id(departure)
end
43 changes: 35 additions & 8 deletions lib/screens/v2/rds.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Screens.V2.RDS do
alias Screens.Alerts.Alert
alias Screens.Alerts.InformedEntity
alias Screens.Config.Cache
alias Screens.Headways
alias Screens.Headways, as: Headway
alias Screens.Lines.Line
alias Screens.RoutePatterns.RoutePattern
alias Screens.Routes.Route
Expand Down Expand Up @@ -105,9 +105,25 @@ defmodule Screens.V2.RDS do
defstruct ~w[last_scheduled_departure]a
end

defmodule Headways do
@moduledoc """
State for if we're in an active period, but we have no predictions
and there are no alerts associated with the destination.

Shows an every “X-Y” minutes message.
"""
@type t :: %__MODULE__{
departure: Departure.t(),
route_id: Route.id(),
direction_name: String.t(),
range: Headway.range()
}
defstruct ~w[departure route_id direction_name range]a
end

@alert injected(Alert)
@departure injected(Departure)
@headways injected(Headways)
@headways injected(Headway)
@route_pattern injected(RoutePattern)
@schedule injected(Schedule)
@stop injected(Stop)
Expand Down Expand Up @@ -212,7 +228,7 @@ defmodule Screens.V2.RDS do
destinations =
(tuples_from_departures(departures, now) ++
tuples_from_patterns(typical_patterns, child_stops))
|> Enum.uniq()
|> Enum.uniq_by(fn {stop, line, headsign} -> {stop.id, line.id, headsign} end)

# Destinations that are affected by current alerts at the present stop ID
impacted_destinations = informed_destinations(destinations, alerts, typical_patterns)
Expand Down Expand Up @@ -299,7 +315,7 @@ defmodule Screens.V2.RDS do
[Departure.t()],
Stop.id(),
[Route.t()],
Headways.range() | nil,
Headway.range() | nil,
boolean(),
DateTime.t()
) :: rds_state()
Expand Down Expand Up @@ -342,14 +358,25 @@ defmodule Screens.V2.RDS do
:after_scheduled_end ->
%ServiceEnded{last_scheduled_departure: last_scheduled_departure}

:active_period ->
%Countdowns{departures: departures_for_headsign}

:service_impacted ->
%Countdowns{departures: departures_for_headsign}

:no_service ->
%NoService{routes: routes_for_section}

:active_period ->
route = Departure.route(first_scheduled_departure)
direction_id = Departure.direction_id(first_scheduled_departure)

%Headways{
departure: first_scheduled_departure,
route_id: route.id,
direction_name:
route
|> Route.normalized_direction_names()
|> Enum.at(direction_id, nil),
range: headway_for_stop
}
end
end

Expand Down Expand Up @@ -424,7 +451,7 @@ defmodule Screens.V2.RDS do
@spec classify_service_state(
Departure.t(),
Departure.t(),
Headways.range() | nil,
Headway.range() | nil,
boolean(),
DateTime.t()
) :: service_state()
Expand Down
41 changes: 40 additions & 1 deletion lib/screens/v2/widget_instance/departures.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Screens.V2.WidgetInstance.Departures do
Provides real-time departure information, consisting of an ordered list of "sections".
"""

alias Screens.Headways
alias Screens.Predictions.Prediction
alias Screens.Routes.Route
alias Screens.Schedules.Schedule
Expand All @@ -22,7 +23,13 @@ defmodule Screens.V2.WidgetInstance.Departures do

@type special_trip_type :: :first_trip | :last_trip

@type row :: Departure.t() | {Departure.t(), special_trip_type()} | FreeTextLine.t()
@type headway_row :: {Departure.t(), Headways.range(), String.t() | nil, :headways}

@type row ::
Departure.t()
| {Departure.t(), special_trip_type()}
| headway_row()
| FreeTextLine.t()

@type t :: %__MODULE__{
header: Header.t(),
Expand Down Expand Up @@ -477,6 +484,38 @@ defmodule Screens.V2.WidgetInstance.Departures do
}
end

defp serialize_departure_group(
[
{departure, {lo, hi}, headsign, :headways}
| _
],
screen,
_now,
route_pill_serializer
) do
departure_id = Departure.id(departure)
departures = [departure]

%{
id: hash_and_encode(departure_id),
type: :departure_row,
route: serialize_route(departures, route_pill_serializer),
headsign:
if headsign do
%{headsign: headsign}
else
serialize_headsign(departures, screen)
end,
times_with_crowding: [
%{
id: departure_id,
time: %{type: :status, pages: ["every #{lo}-#{hi}m"]}
}
],
direction_id: serialize_direction_id(departures)
}
end

defp serialize_departure_group([%FreeTextLine{} = text], _screen, _now, _pill_serializer) do
%{
type: :notice_row,
Expand Down
Loading
Loading