Skip to content

Commit

Permalink
unify prediction message constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
panentheos committed Nov 1, 2024
1 parent b9c3754 commit 59ca4e0
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 149 deletions.
9 changes: 3 additions & 6 deletions lib/content/audio/following_train.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ defmodule Content.Audio.FollowingTrain do
"""

@enforce_keys [:destination, :route_id, :verb, :minutes]
defstruct @enforce_keys ++ [station_code: nil]
defstruct @enforce_keys

@type verb :: :arrives | :departs

@type t :: %__MODULE__{
destination: PaEss.destination(),
route_id: String.t(),
verb: verb(),
minutes: integer(),
station_code: String.t() | nil
minutes: integer()
}

require Logger
Expand All @@ -25,7 +24,6 @@ defmodule Content.Audio.FollowingTrain do
minutes: n,
destination: destination,
prediction: prediction,
station_code: station_code,
terminal?: terminal
})
when is_integer(n) do
Expand All @@ -34,8 +32,7 @@ defmodule Content.Audio.FollowingTrain do
destination: destination,
route_id: prediction.route_id,
minutes: n,
verb: arrives_or_departs(terminal),
station_code: station_code
verb: arrives_or_departs(terminal)
}
]
end
Expand Down
10 changes: 4 additions & 6 deletions lib/content/audio/next_train_countdown.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Content.Audio.NextTrainCountdown do
"""

@enforce_keys [:destination, :route_id, :verb, :minutes, :track_number]
defstruct @enforce_keys ++ [:station_code, :zone, platform: nil]
defstruct @enforce_keys ++ [:special_sign, platform: nil]

@type verb :: :arrives | :departs

Expand All @@ -15,8 +15,7 @@ defmodule Content.Audio.NextTrainCountdown do
minutes: integer(),
track_number: Content.Utilities.track_number() | nil,
platform: Content.platform() | nil,
station_code: String.t() | nil,
zone: String.t() | nil
special_sign: :jfk_mezzanine | :bowdoin_eastbound | nil
}

require Logger
Expand All @@ -31,8 +30,7 @@ defmodule Content.Audio.NextTrainCountdown do
verb: if(message.terminal?, do: :departs, else: :arrives),
track_number: Content.Utilities.stop_track_number(message.prediction.stop_id),
platform: Content.Utilities.stop_platform(message.prediction.stop_id),
station_code: message.station_code,
zone: message.zone
special_sign: message.special_sign
}
]
end
Expand Down Expand Up @@ -74,7 +72,7 @@ defmodule Content.Audio.NextTrainCountdown do
audio.minutes == 1 ->
{:canned, {"142", [dest_var, platform_var(audio), verb_var(audio)], :audio}}

audio.destination == :alewife and audio.station_code == "RJFK" and audio.zone == "m" and
audio.destination == :alewife and audio.special_sign == :jfk_mezzanine and
audio.minutes > 5 ->
platform_tbd_params(
audio,
Expand Down
2 changes: 1 addition & 1 deletion lib/content/audio/train_is_boarding.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule Content.Audio.TrainIsBoarding do
track_number: Content.Utilities.stop_track_number(message.prediction.stop_id)
}
] ++
if message.station_code == "BBOW" && message.zone == "e" do
if message.special_sign == :bowdoin_eastbound do
[%Audio.BoardingButton{}]
else
[]
Expand Down
110 changes: 28 additions & 82 deletions lib/content/message/predictions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,123 +10,78 @@ defmodule Content.Message.Predictions do
yourself.
"""

require Logger
require Content.Utilities

@terminal_brd_seconds 30
@terminal_prediction_offset_seconds -60
@reverse_prediction_certainty 360

@enforce_keys [:destination, :minutes]
defstruct [
:destination,
:minutes,
:approximate?,
:prediction,
:station_code,
:zone,
terminal?: false
]
@enforce_keys [:destination, :minutes, :approximate?, :prediction, :special_sign, :terminal?]
defstruct @enforce_keys

@type t :: %__MODULE__{
destination: PaEss.destination(),
minutes: integer() | :boarding | :arriving | :approaching,
approximate?: boolean(),
prediction: Predictions.Prediction.t(),
station_code: String.t() | nil,
zone: String.t() | nil,
special_sign: :jfk_mezzanine | :bowdoin_eastbound | nil,
terminal?: boolean()
}

@spec non_terminal(Predictions.Prediction.t(), String.t(), String.t()) :: t()
def non_terminal(prediction, station_code, zone) do
# e.g., North Station which is non-terminal but has trips that begin there
predicted_time = prediction.seconds_until_arrival || prediction.seconds_until_departure

certainty =
if prediction.seconds_until_arrival,
do: prediction.arrival_certainty,
else: prediction.departure_certainty

{minutes, approximate?} =
cond do
prediction.stops_away == 0 -> {:boarding, false}
predicted_time <= 30 -> {:arriving, false}
predicted_time <= 60 -> {:approaching, false}
true -> compute_minutes(predicted_time, certainty)
@spec new(Predictions.Prediction.t(), boolean(), :jfk_mezzanine | :bowdoin_eastbound | nil) ::
t()
def new(%Predictions.Prediction{} = prediction, terminal?, special_sign) do
sec =
if terminal? do
prediction.seconds_until_departure - 60
else
prediction.seconds_until_arrival || prediction.seconds_until_departure
end

%__MODULE__{
destination: Content.Utilities.destination_for_prediction(prediction),
minutes: minutes,
approximate?: approximate?,
prediction: prediction,
station_code: station_code,
zone: zone
}
end

@spec terminal(Predictions.Prediction.t(), String.t(), String.t()) :: t()
def terminal(prediction, station_code, zone) do
min = round(sec / 60)
stopped_at? = prediction.stops_away == 0
reverse_prediction? = Signs.Utilities.Predictions.reverse_prediction?(prediction, terminal?)

{minutes, approximate?} =
case prediction.seconds_until_departure + @terminal_prediction_offset_seconds do
x when x <= @terminal_brd_seconds and stopped_at? -> {:boarding, false}
x when x <= @terminal_brd_seconds -> {1, false}
x -> compute_minutes(x, prediction.departure_certainty)
cond do
stopped_at? and (!terminal? or sec <= 30) -> {:boarding, false}
!terminal? and sec <= 30 -> {:arriving, false}
!terminal? and sec <= 60 -> {:approaching, false}
min > 60 -> {60, true}
reverse_prediction? and min > 20 -> {div(min, 10) * 10, true}
true -> {max(min, 1), false}
end

%__MODULE__{
destination: Content.Utilities.destination_for_prediction(prediction),
minutes: minutes,
approximate?: approximate?,
prediction: prediction,
station_code: station_code,
zone: zone,
terminal?: true
special_sign: special_sign,
terminal?: terminal?
}
end

defp compute_minutes(sec, certainty) do
min = round(sec / 60)

cond do
min > 60 -> {60, true}
certainty == @reverse_prediction_certainty && min > 20 -> {div(min, 10) * 10, true}
true -> {min, false}
end
end

defimpl Content.Message do
require Logger

@width 18
@boarding "BRD"
@arriving "ARR"

def to_string(%{
def to_string(%Content.Message.Predictions{
destination: destination,
minutes: minutes,
approximate?: approximate?,
prediction: %{stop_id: stop_id},
station_code: station_code,
zone: zone
special_sign: special_sign
}) do
headsign = PaEss.Utilities.destination_to_sign_string(destination)

duration_string =
case minutes do
:boarding -> @boarding
:arriving -> @arriving
:boarding -> "BRD"
:arriving -> "ARR"
:approaching -> "1 min"
n -> "#{n}#{if approximate?, do: "+", else: ""} min"
end

track_number = Content.Utilities.stop_track_number(stop_id)

cond do
station_code == "RJFK" and destination == :alewife and zone == "m" ->
special_sign == :jfk_mezzanine and destination == :alewife ->
platform_name = Content.Utilities.stop_platform_name(stop_id)

{headsign_message, platform_message} =
Expand All @@ -137,11 +92,7 @@ defmodule Content.Message.Predictions do
end

[
{Content.Utilities.width_padded_string(
headsign_message,
"#{duration_string}",
@width
), 6},
{Content.Utilities.width_padded_string(headsign_message, duration_string, @width), 6},
{headsign <> platform_message, 6}
]

Expand All @@ -155,10 +106,5 @@ defmodule Content.Message.Predictions do
Content.Utilities.width_padded_string(headsign, duration_string, @width)
end
end

def to_string(e) do
Logger.error("cannot_to_string: #{inspect(e)}")
""
end
end
end
2 changes: 1 addition & 1 deletion lib/signs/utilities/audio.ex
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ defmodule Signs.Utilities.Audio do

# This only occurs at the RJFK mezzanine zone
{%Message.Predictions{}, %Message.PlatformPredictionBottom{}} ->
[{:predictions, [%{top | zone: "m"}]}]
[{:predictions, [%{top | special_sign: :jfk_mezzanine}]}]

{top, _} when top.__struct__ in [Message.Predictions, Message.StoppedTrain] ->
[{:predictions, [top]}]
Expand Down
9 changes: 3 additions & 6 deletions lib/signs/utilities/messages.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule Signs.Utilities.Messages do
"""

alias Content.Message.Alert
@reverse_prediction_certainty 360
@early_am_start ~T[03:29:00]
@early_am_buffer -40

Expand Down Expand Up @@ -146,13 +145,12 @@ defmodule Signs.Utilities.Messages do

defp expand_message(
%Content.Message.Predictions{
station_code: "RJFK",
zone: "m",
special_sign: :jfk_mezzanine,
prediction: %{stop_id: stop_id},
minutes: minutes
} = prediction
) do
{%{prediction | zone: nil},
{%{prediction | special_sign: nil},
%Content.Message.PlatformPredictionBottom{stop_id: stop_id, minutes: minutes}}
end

Expand Down Expand Up @@ -206,8 +204,7 @@ defmodule Signs.Utilities.Messages do
# except for Prudential or Symphony EB
Enum.reject(
predictions,
&(Signs.Utilities.Predictions.prediction_certainty(&1, config) ==
@reverse_prediction_certainty and
&(Signs.Utilities.Predictions.reverse_prediction?(&1, config.terminal?) and
&1.stop_id not in ["70240", "70242"])
)
end
Expand Down
41 changes: 21 additions & 20 deletions lib/signs/utilities/predictions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ defmodule Signs.Utilities.Predictions do
SourceConfig.config(),
Signs.Realtime.t()
) :: Signs.Realtime.sign_messages() | nil
def prediction_messages(
predictions,
%{terminal?: terminal?},
%{pa_ess_loc: station_code, text_zone: zone} = sign
) do
def prediction_messages(predictions, %{terminal?: terminal?}, sign) do
predictions
|> Enum.filter(fn p ->
p.seconds_until_departure && p.schedule_relationship != :skipped
Expand All @@ -46,18 +42,19 @@ defmodule Signs.Utilities.Predictions do
end)
|> filter_large_red_line_gaps()
|> Enum.map(fn prediction ->
cond do
stopped_train?(prediction) ->
Content.Message.StoppedTrain.from_prediction(prediction)

terminal? ->
Content.Message.Predictions.terminal(prediction, station_code, zone)
special_sign =
case sign do
%{pa_ess_loc: "RJFK", text_zone: "m"} -> :jfk_mezzanine
%{pa_ess_loc: "BBOW", text_zone: "e"} -> :bowdoin_eastbound
_ -> nil
end

true ->
Content.Message.Predictions.non_terminal(prediction, station_code, zone)
if stopped_train?(prediction) do
Content.Message.StoppedTrain.from_prediction(prediction)
else
Content.Message.Predictions.new(prediction, terminal?, special_sign)
end
end)
|> Enum.reject(&is_nil(&1))
# Take next two predictions, but if the list has multiple destinations, prefer showing
# distinct ones. This helps e.g. the red line trunk where people may need to know about
# a particular branch.
Expand All @@ -74,12 +71,16 @@ defmodule Signs.Utilities.Predictions do
end
end

def prediction_certainty(prediction, config) do
if config.terminal? || !prediction.seconds_until_arrival do
prediction.departure_certainty
else
prediction.arrival_certainty
end
@spec reverse_prediction?(Predictions.Prediction.t(), boolean()) :: boolean()
def reverse_prediction?(%Predictions.Prediction{} = prediction, terminal?) do
certainty =
if terminal? || !prediction.seconds_until_arrival do
prediction.departure_certainty
else
prediction.arrival_certainty
end

certainty == @reverse_prediction_certainty
end

defp get_unique_destination_predictions(predictions, "Green") do
Expand Down
8 changes: 6 additions & 2 deletions test/content/audio/following_train_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ defmodule Content.Audio.FollowingTrainTest do
destination: :ashmont,
prediction: %Predictions.Prediction{route_id: "Mattapan"},
minutes: 5,
terminal?: false
approximate?: false,
terminal?: false,
special_sign: nil
}

audio = Content.Audio.FollowingTrain.from_predictions_message(message)
Expand All @@ -38,7 +40,9 @@ defmodule Content.Audio.FollowingTrainTest do
destination: :ashmont,
prediction: %Predictions.Prediction{route_id: "Mattapan"},
minutes: 5,
terminal?: true
approximate?: false,
terminal?: true,
special_sign: nil
}

audio = Content.Audio.FollowingTrain.from_predictions_message(message)
Expand Down
Loading

0 comments on commit 59ca4e0

Please sign in to comment.