Skip to content

Commit 3dfe833

Browse files
authored
Merge pull request #8 from mbta/jz-support-all-subway-terminals
feat: Add service day column to output tables; Support all subway terminals
2 parents 8a6d9dc + 0de2ba3 commit 3dfe833

24 files changed

+686
-307
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
echo "${{ github.event.pull_request.head.sha }}" > cover/PR_SHA
7070
if: github.event.pull_request
7171
- name: Upload coverage artifact
72-
uses: actions/upload-artifact@v2
72+
uses: actions/upload-artifact@v4
7373
with:
7474
name: elixir-lcov
7575
path: cover/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.multiselect-group-container {
2+
margin-bottom: 8px;
3+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
export function init(ctx, { terminals, groups }) {
2+
ctx.importCSS("main.css");
3+
4+
const root = ctx.root;
5+
const container = document.createElement("div");
6+
container.className = "multiselect-container";
7+
8+
root.append(container);
9+
10+
const checkboxContainers = terminals.map(([id, { label, checked }]) => {
11+
const inputContainer = document.createElement("div");
12+
13+
const inputEl = document.createElement("input");
14+
inputEl.type = "checkbox";
15+
inputEl.name = id;
16+
inputEl.id = id;
17+
inputEl.checked = checked;
18+
inputEl.addEventListener("click", (_event) => {
19+
ctx.pushEvent("toggle_terminal", id);
20+
});
21+
22+
const labelEl = document.createElement("label");
23+
labelEl.htmlFor = id;
24+
labelEl.innerText = label;
25+
26+
inputContainer.append(inputEl, labelEl);
27+
return inputContainer;
28+
});
29+
30+
const groupCheckboxContainers = groups.map(([id, { label, checked, indeterminate }]) => {
31+
const inputContainer = document.createElement("div");
32+
33+
const inputEl = document.createElement("input");
34+
inputEl.type = "checkbox";
35+
inputEl.name = id;
36+
inputEl.id = id;
37+
inputEl.checked = checked;
38+
inputEl.indeterminate = indeterminate;
39+
inputEl.addEventListener("click", (_event) => {
40+
ctx.pushEvent("toggle_group", id);
41+
});
42+
43+
const labelEl = document.createElement("label");
44+
labelEl.htmlFor = id;
45+
labelEl.innerText = label;
46+
47+
inputContainer.append(inputEl, labelEl);
48+
return inputContainer;
49+
});
50+
51+
const groupContainer = document.createElement("div");
52+
groupContainer.className = "multiselect-group-container";
53+
groupContainer.style.marginBottom = "8px";
54+
55+
if (groupCheckboxContainers.length > 0) {
56+
const groupsHeader = document.createElement("h3");
57+
groupsHeader.className = "multiselect-group-header";
58+
groupsHeader.innerText = "Stop Groups";
59+
const hr = document.createElement("hr");
60+
groupContainer.append(groupsHeader, ...groupCheckboxContainers, hr);
61+
} else {
62+
groupContainer.hidden = true;
63+
}
64+
65+
const stopsHeader = document.createElement("h3");
66+
stopsHeader.className = "multiselect-stops-header";
67+
stopsHeader.innerText = "Stops";
68+
69+
container.append(groupContainer, stopsHeader, ...checkboxContainers);
70+
71+
ctx.handleEvent("update", ({ terminals, groups }) => {
72+
groups.forEach(([id, { checked, indeterminate }]) => {
73+
const input = document.getElementById(id);
74+
if (input) {
75+
input.checked = checked;
76+
input.indeterminate = indeterminate;
77+
}
78+
});
79+
80+
terminals.forEach(([id, { checked }]) => {
81+
const input = document.getElementById(id);
82+
if (input) { input.checked = checked; }
83+
});
84+
});
85+
};

lib/transit_data/glides_report/countdown_clocks_simulation.ex

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@ defmodule TransitData.GlidesReport.CountdownClocksSimulation do
22
@moduledoc "Simulates countdown clock signs."
33

44
alias TransitData.GlidesReport
5+
alias TransitData.GlidesReport.Terminal
56

6-
@type t :: %{stop_id => GlidesReport.Sign.t()}
7-
8-
@type stop_id :: String.t()
7+
@type t :: %{Terminal.id() => GlidesReport.Sign.t()}
98

109
@type timestamp :: integer
1110

1211
@doc """
13-
Returns a set of {stop_id, timestamp} tuples, each representing an instance where
14-
a predicted time (timestamp) appeared on the countdown clock for a stop (stop_id).
12+
Returns a set of {terminal_id, timestamp} tuples, each representing an instance where
13+
a predicted time (timestamp) appeared on the countdown clock at a terminal (terminal_id).
1514
"""
16-
@spec get_all_top_two_times(Enumerable.t(stop_id)) :: MapSet.t({stop_id, timestamp})
17-
def get_all_top_two_times(stop_ids) do
18-
trip_updates_for_simulation(stop_ids)
15+
@spec get_all_top_two_times(Enumerable.t(Terminal.id())) ::
16+
MapSet.t({Terminal.id(), timestamp})
17+
def get_all_top_two_times(terminal_ids) do
18+
trip_updates_for_simulation(terminal_ids)
1919
|> Enum.reduce(%{}, fn tr_upd, signs -> apply_trip_update(signs, tr_upd) end)
20-
|> Stream.map(fn {stop_id, sign} ->
21-
MapSet.new(sign.top_twos, fn timestamp -> {stop_id, timestamp} end)
20+
|> Stream.map(fn {terminal_id, sign} ->
21+
MapSet.new(sign.top_twos, fn timestamp -> {terminal_id, timestamp} end)
2222
end)
2323
|> Enum.reduce(MapSet.new(), &MapSet.union/2)
2424
end
@@ -33,20 +33,20 @@ defmodule TransitData.GlidesReport.CountdownClocksSimulation do
3333

3434
Map.update(
3535
signs,
36-
stop_time_update.stop_id,
36+
stop_time_update.terminal_id,
3737
GlidesReport.Sign.new(trip_id, departure_time, timestamp),
3838
&GlidesReport.Sign.apply_stop_time_update(&1, trip_id, departure_time, timestamp)
3939
)
4040
end)
4141
end
4242

43-
defp trip_updates_for_simulation(stop_ids) do
43+
defp trip_updates_for_simulation(terminal_ids) do
4444
:TripUpdates
4545
|> GlidesReport.Util.stream_values()
4646
|> Stream.map(&GlidesReport.TripUpdate.normalize_stop_ids/1)
47-
# Filter each trip update's stop_time_update to just the user's selected stops.
47+
# Filter each trip update's stop_time_update to just the user's selected terminals.
4848
# If filtered list is empty for any trip update, the trip update is removed entirely.
49-
|> Stream.map(&GlidesReport.TripUpdate.filter_stops(&1, stop_ids))
49+
|> Stream.map(&GlidesReport.TripUpdate.filter_terminals(&1, terminal_ids))
5050
|> Stream.reject(&is_nil/1)
5151
|> Enum.sort_by(& &1.trip_update.timestamp)
5252
end

lib/transit_data/glides_report/departure.ex

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ defmodule TransitData.GlidesReport.Departure do
55
"""
66

77
alias TransitData.GlidesReport.Spec.Common
8+
alias TransitData.GlidesReport.Terminal
89
alias TransitData.GlidesReport.Util
910

1011
@type t :: %__MODULE__{
1112
trip: Common.trip_id(),
12-
stop: Common.stop_id(),
13+
terminal: Terminal.id(),
1314
timestamp: Common.timestamp(),
1415
# Hour part of the timestamp (in Eastern TZ)
1516
hour: 0..23,
@@ -19,13 +20,13 @@ defmodule TransitData.GlidesReport.Departure do
1920

2021
@type minute :: 0..59
2122

22-
@enforce_keys [:trip, :stop, :timestamp, :hour, :minute]
23+
@enforce_keys [:trip, :terminal, :timestamp, :hour, :minute]
2324
defstruct @enforce_keys
2425

25-
@spec new(Common.trip_id(), Common.stop_id(), Common.timestamp()) :: t()
26-
def new(trip, stop, timestamp) do
26+
@spec new(Common.trip_id(), Terminal.id(), Common.timestamp()) :: t()
27+
def new(trip, {:terminal, _} = terminal, timestamp) do
2728
hour = Util.unix_timestamp_to_local_hour(timestamp)
2829
minute = Util.unix_timestamp_to_local_minute(timestamp)
29-
%__MODULE__{trip: trip, stop: stop, timestamp: timestamp, hour: hour, minute: minute}
30+
%__MODULE__{trip: trip, terminal: terminal, timestamp: timestamp, hour: hour, minute: minute}
3031
end
3132
end

lib/transit_data/glides_report/settings/filter.ex

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
defmodule TransitData.GlidesReport.Settings.Filter do
22
@moduledoc "User-selected filtering settings."
33

4+
alias TransitData.GlidesReport.Terminal
5+
46
@type t :: %__MODULE__{
5-
# A set of parent stop IDs
6-
stop_ids: MapSet.t(String.t()),
7+
# A set of terminal IDs
8+
terminal_ids: MapSet.t(Terminal.id()),
79
limit_to_next_2_predictions: boolean,
810
min_advance_notice_sec: pos_integer | nil
911
}
1012

1113
defstruct [
12-
:stop_ids,
14+
:terminal_ids,
1315
:limit_to_next_2_predictions,
1416
:min_advance_notice_sec
1517
]
1618

17-
@spec new(MapSet.t(String.t()), boolean, pos_integer | nil) :: t()
18-
def new(stop_ids, limit_to_next_2_predictions, min_advance_notice) do
19+
@spec new(MapSet.t(Terminal.id()), boolean, pos_integer | nil) :: t()
20+
def new(terminal_ids, limit_to_next_2_predictions, min_advance_notice) do
1921
%__MODULE__{
20-
stop_ids: stop_ids,
22+
terminal_ids: terminal_ids,
2123
limit_to_next_2_predictions: limit_to_next_2_predictions,
2224
min_advance_notice_sec:
2325
case min_advance_notice do

lib/transit_data/glides_report/sign.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
defmodule TransitData.GlidesReport.Sign do
2-
@moduledoc "Simulates a countdown clock at one platform (child stop ID)."
2+
@moduledoc "Simulates a countdown clock at one terminal."
33

44
@type t :: %__MODULE__{
55
predictions: list({trip_id :: String.t(), departure_time :: integer}),

lib/transit_data/glides_report/spec/trip_update.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule TransitData.GlidesReport.Spec.TripUpdate do
66
"""
77

88
alias TransitData.GlidesReport.Spec.Common
9+
alias TransitData.GlidesReport.Terminal
910

1011
@type t :: {key, value}
1112

@@ -27,9 +28,9 @@ defmodule TransitData.GlidesReport.Spec.TripUpdate do
2728
list(%{
2829
departure: %{time: Common.timestamp()},
2930
# A child stop ID initially, but we convert it to the
30-
# parent stop ID of the relevant terminal by calling
31+
# ID of the relevant terminal by calling
3132
# GlidesReport.TripUpdate.normalize_stop_id/1 on it.
32-
stop_id: Common.stop_id()
33+
terminal_id: Terminal.id()
3334
}),
3435
trip: %{trip_id: Common.trip_id()}
3536
}

lib/transit_data/glides_report/spec/vehicle_position.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule TransitData.GlidesReport.Spec.VehiclePosition do
66
"""
77

88
alias TransitData.GlidesReport.Spec.Common
9+
alias TransitData.GlidesReport.Terminal
910

1011
@type t :: {key, value}
1112

@@ -19,9 +20,9 @@ defmodule TransitData.GlidesReport.Spec.VehiclePosition do
1920
# "IN_TRANSIT_TO" | "STOPPED_AT" | "INCOMING_AT"
2021
current_status: String.t(),
2122
# A child stop ID initially, but we convert it to the
22-
# parent stop ID of the relevant terminal by calling
23+
# ID of the relevant terminal by calling
2324
# GlidesReport.VehiclePosition.normalize_stop_id/1 on it.
24-
stop_id: Common.stop_id(),
25+
terminal_id: Terminal.id(),
2526
timestamp: Common.timestamp(),
2627
trip: %{
2728
trip_id: Common.trip_id()

0 commit comments

Comments
 (0)