Skip to content

Commit

Permalink
cleanup: Add @SPEC annotations to Dotcom.SystemStatus.Subway
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlarson committed Feb 5, 2025
1 parent 02f0d08 commit c00f06a
Showing 1 changed file with 34 additions and 0 deletions.
34 changes: 34 additions & 0 deletions lib/dotcom/system_status/subway.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ defmodule Dotcom.SystemStatus.Subway do

alias Alerts.Alert

@type status_time() :: :current | {:future, DateTime.t()}

@type status_entry() :: %{
status: atom(),
multiple: boolean(),
time: status_time()
}

@type status_entry_group() :: %{branch_ids: [String.t()], status_entries: [status_entry()]}

@lines ["Blue", "Green", "Orange", "Red"]
@green_line_branches ["Green-B", "Green-C", "Green-D", "Green-E"]

Expand Down Expand Up @@ -109,6 +119,7 @@ defmodule Dotcom.SystemStatus.Subway do
"Green" => [%{branch_ids: [], status_entries: [%{time: :current, status: :normal, multiple: false}]}]
}
"""
@spec subway_status([Alert.t()], DateTime.t()) :: %{String.t() => status_entry_group()}
def subway_status(alerts, time) do
@lines
|> Map.new(fn line ->
Expand All @@ -129,6 +140,10 @@ defmodule Dotcom.SystemStatus.Subway do
#
# The exact implementation depends on which line. Green and Red have
# branches, so they have special implementations.
@spec add_nested_statuses_for_line(String.t(), [Alert.t()], DateTime.t()) :: %{
route_id: String.t(),
branches_with_statuses: [status_entry_group()]
}
defp add_nested_statuses_for_line(line_id, alerts, time)

# Green line nested-statuses implementation:
Expand Down Expand Up @@ -204,6 +219,7 @@ defmodule Dotcom.SystemStatus.Subway do
# Service" should come after any other alerts), and then by branch
# ID (so that, say statuses for "Green-B" should come ahead of
# "Green-C").
@spec sort_branches([status_entry_group()]) :: [status_entry_group()]
defp sort_branches(branches_with_statuses) do
branches_with_statuses
|> Enum.sort_by(fn %{status_entries: status_entries, branch_ids: branch_ids} ->
Expand All @@ -213,11 +229,13 @@ defmodule Dotcom.SystemStatus.Subway do

# Sort order used in sort_branches/1 - sorts normal statuses ahead
# of alerts.
@spec status_sort_order([status_entry()]) :: integer()
defp status_sort_order([%{time: :current, status: :normal}]), do: 1
defp status_sort_order(_), do: 0

# Returns a list containing a single status entry group corresponding
# to the alerts for the given route.
@spec branches_with_statuses(String.t(), [Alert.t()], DateTime.t()) :: [status_entry_group()]
defp branches_with_statuses(route_id, alerts, time) do
route_id
|> statuses_for_route(alerts, time)
Expand All @@ -228,6 +246,7 @@ defmodule Dotcom.SystemStatus.Subway do
# Behaves mostly like branches_with_statuses/3 when applied to
# "Mattapan", except that if the status is normal, returns an empty
# list.
@spec mattapan_branches_with_statuses([Alert.t()], DateTime.t()) :: [status_entry_group()]
defp mattapan_branches_with_statuses(alerts, time) do
"Mattapan"
|> alerts_for_route(alerts)
Expand All @@ -245,6 +264,10 @@ defmodule Dotcom.SystemStatus.Subway do
# Exchanges a route_id (a line_id or a branch_id - anything that
# might correspond to an alert) for a map with that route_id and the
# statuses affecting that route.
@spec add_statuses_for_route(String.t(), [Alert.t()], DateTime.t()) :: %{
route_id: String.t(),
statuses: [status_entry()]
}
defp add_statuses_for_route(route_id, alerts, time) do
%{
route_id: route_id,
Expand All @@ -254,6 +277,7 @@ defmodule Dotcom.SystemStatus.Subway do

# Returns a list of statuses corresponding to the alerts for the
# given route.
@spec statuses_for_route(String.t(), [Alert.t()], DateTime.t()) :: [status_entry()]
defp statuses_for_route(route_id, alerts, time) do
route_id
|> alerts_for_route(alerts)
Expand All @@ -263,6 +287,7 @@ defmodule Dotcom.SystemStatus.Subway do
# Returns a branch_with_status entry, to be used in the
# branches_with_statuses field in groups/2. If no branch_ids are
# provided, then uses an empty array.
@spec branch_with_statuses_entry([status_entry()], [String.t()]) :: status_entry_group()
defp branch_with_statuses_entry(statuses, branch_ids \\ []) do
%{
branch_ids: branch_ids,
Expand All @@ -273,6 +298,7 @@ defmodule Dotcom.SystemStatus.Subway do
# Given `alerts` and `route_id`, filters out only the alerts
# applicable to the given route, using the alert's "informed
# entities".
@spec alerts_for_route(String.t(), [Alert.t()]) :: [Alert.t()]
defp alerts_for_route(route_id, alerts) do
alerts
|> Enum.filter(fn %Alert{informed_entity: informed_entity} ->
Expand All @@ -289,6 +315,7 @@ defmodule Dotcom.SystemStatus.Subway do
# - Identical alerts are grouped together and pluralized.
# - Times are given as a kitchen-formatted string, nil, or "Now".
# - Statuses are sorted alphabetically.
@spec alerts_to_statuses([Alert.t()], DateTime.t()) :: [status_entry()]
defp alerts_to_statuses(alerts, time) do
alerts
|> alerts_to_statuses_naive(time)
Expand All @@ -300,6 +327,7 @@ defmodule Dotcom.SystemStatus.Subway do
# status is a simple structure with a route, a status, and a
# few additional fields that determine how it will render in the
# frontend.
@spec alerts_to_statuses_naive([Alert.t()], DateTime.t()) :: [status_entry()]
defp alerts_to_statuses_naive(alerts, time)

# If there are no alerts, then we want a single status indicating
Expand All @@ -322,6 +350,7 @@ defmodule Dotcom.SystemStatus.Subway do
# - If the alert's already active, `time` is set to `nil`.
# - If the alert is in the future, `time` is set to the alert's
# start time
@spec alert_to_status(Alert.t(), DateTime.t()) :: status_entry()
defp alert_to_status(alert, time) do
time = future_start_time(alert.active_period, time)

Expand All @@ -331,6 +360,7 @@ defmodule Dotcom.SystemStatus.Subway do
# - If the active period is in the future, returns its start_time.
# - If the active period indicates that the alert is currently active, returns nil.
# - Raises an error if the alert is completely in the past.
@spec future_start_time([Alert.period_pair()], DateTime.t()) :: status_time()
defp future_start_time(
[{start_time, _end_time} = first_active_period | more_active_periods],
time
Expand All @@ -345,15 +375,18 @@ defmodule Dotcom.SystemStatus.Subway do
# Returns true if the active period ends before the time given. An
# end-time of false indicates an indefinite active period, which
# never ends.
@spec ends_before?(Alert.period_pair(), DateTime.t()) :: boolean()
defp ends_before?({_start_time, nil}, _time), do: false
defp ends_before?({_start_time, end_time}, time), do: Timex.before?(end_time, time)

# Returns true if the active period starts before the time given.
@spec starts_before?(Alert.period_pair(), DateTime.t()) :: boolean()
defp starts_before?({start_time, _end_time}, time), do: Timex.before?(start_time, time)

# Combines statuses that have the same active time and status
# into a single pluralized status (e.g. "Station Closures" instead
# of "Station Closure").
@spec consolidate_duplicates([status_entry()]) :: [status_entry()]
defp consolidate_duplicates(statuses) do
statuses
|> Enum.group_by(fn %{time: time, status: status} -> {time, status} end)
Expand All @@ -374,6 +407,7 @@ defmodule Dotcom.SystemStatus.Subway do
# This should be called before &stringify_times/1, otherwise times
# will get sorted lexically instead of temporally (e.g. 10:00pm will
# get sorted ahead of 9:00pm).
@spec sort_statuses([status_entry()]) :: [status_entry()]
defp sort_statuses(statuses) do
statuses
|> Enum.sort_by(fn %{time: time, status: status} -> {time, status} end)
Expand Down

0 comments on commit c00f06a

Please sign in to comment.