From 5da8899502fa1778cf3917cfa9e6eb3158b3345e Mon Sep 17 00:00:00 2001 From: Cristen Jones Date: Tue, 3 Feb 2026 11:50:22 -0500 Subject: [PATCH 1/4] feat(UpcomingDepartures): use vehicle status --- .../schedule_finder/upcoming_departures.ex | 74 ++++++++++++----- .../upcoming_departures_test.exs | 81 +++++++++++++++++++ 2 files changed, 133 insertions(+), 22 deletions(-) diff --git a/lib/dotcom/schedule_finder/upcoming_departures.ex b/lib/dotcom/schedule_finder/upcoming_departures.ex index 5f96bf9a66..898004d1f5 100644 --- a/lib/dotcom/schedule_finder/upcoming_departures.ex +++ b/lib/dotcom/schedule_finder/upcoming_departures.ex @@ -18,6 +18,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do @predictions_repo Application.compile_env!(:dotcom, :repo_modules)[:predictions] @schedules_repo Application.compile_env!(:dotcom, :repo_modules)[:schedules] @stops_repo Application.compile_env!(:dotcom, :repo_modules)[:stops] + @vehicles_repo Application.compile_env!(:dotcom, :repo_modules)[:vehicles] defmodule UpcomingDeparture do @moduledoc """ @@ -245,9 +246,20 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do route_type: route_type, stop_id: stop_id }) do + stop = predicted_schedule |> PredictedSchedule.stop() trip = predicted_schedule |> PredictedSchedule.trip() stop_sequence = PredictedSchedule.stop_sequence(predicted_schedule) + vehicle = + if predicted_schedule.prediction do + @vehicles_repo.get(predicted_schedule.prediction.vehicle_id) + end + + vehicle_status = + if vehicle && vehicle.stop_id in stop.child_ids do + vehicle.status + end + trip_details = trip_details(%{ predicted_schedules_by_trip_id: predicted_schedules_by_trip_id, @@ -262,7 +274,8 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do predicted_schedule: predicted_schedule, route_type: route_type, status: PredictedSchedule.status(predicted_schedule), - now: now + now: now, + vehicle_status: vehicle_status }), arrival_substatus: arrival_substatus(%{ @@ -326,7 +339,8 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do now: DateTime.t(), predicted_schedule: PredictedSchedule.t(), route_type: Route.route_type(), - status: nil | String.t() + status: nil | String.t(), + vehicle_status: nil | Vehicles.Vehicle.status() }) :: __MODULE__.UpcomingDeparture.arrival_status_t() defp arrival_status(%{ predicted_schedule: %PredictedSchedule{prediction: nil}, @@ -375,7 +389,8 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do defp arrival_status(%{ predicted_schedule: %PredictedSchedule{prediction: prediction}, route_type: route_type, - now: now + now: now, + vehicle_status: vehicle_status }) when prediction != nil do arrival_seconds = seconds_between(prediction.arrival_time, now) @@ -384,7 +399,8 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do realtime_arrival_status(%{ arrival_seconds: arrival_seconds, departure_seconds: departure_seconds, - route_type: route_type + route_type: route_type, + vehicle_status: vehicle_status }) end @@ -406,17 +422,10 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do @spec realtime_arrival_status(%{ arrival_seconds: integer(), departure_seconds: integer(), - route_type: Route.route_type() + route_type: Route.route_type(), + vehicle_status: nil | Vehicles.Vehicle.status() }) :: __MODULE__.UpcomingDeparture.realtime_arrival_status_t() - defp realtime_arrival_status(%{ - arrival_seconds: arrival_seconds, - departure_seconds: departure_seconds, - route_type: :subway - }) - when (arrival_seconds <= 0 or arrival_seconds == nil) and departure_seconds <= 90, - do: :boarding - defp realtime_arrival_status(%{ arrival_seconds: arrival_seconds, departure_seconds: departure_seconds, @@ -425,25 +434,46 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do when (arrival_seconds <= 0 or arrival_seconds == nil) and departure_seconds <= 90, do: :now + defp realtime_arrival_status(%{arrival_seconds: seconds, route_type: :bus}) when seconds <= 30, + do: :now + + defp realtime_arrival_status(%{route_type: :subway, vehicle_status: vehicle_status}) + when vehicle_status != nil do + arrival_status_from_vehicle(vehicle_status) + end + defp realtime_arrival_status(%{ arrival_seconds: nil, - departure_seconds: seconds - }), - do: {:departure_seconds, seconds} + departure_seconds: departure_seconds, + route_type: :subway + }) + when departure_seconds <= 90, + do: :boarding - defp realtime_arrival_status(%{arrival_seconds: seconds, route_type: :bus}) when seconds <= 30, - do: :now + defp realtime_arrival_status(%{ + arrival_seconds: arrival_seconds, + departure_seconds: departure_seconds, + route_type: :subway + }) + when arrival_seconds < 0 and departure_seconds >= 0, do: :boarding - defp realtime_arrival_status(%{arrival_seconds: seconds, route_type: :subway}) - when seconds <= 30, + defp realtime_arrival_status(%{arrival_seconds: arrival_seconds, route_type: :subway}) + when arrival_seconds <= 30, do: :arriving - defp realtime_arrival_status(%{arrival_seconds: seconds, route_type: :subway}) - when seconds <= 60, + defp realtime_arrival_status(%{arrival_seconds: arrival_seconds, route_type: :subway}) + when arrival_seconds <= 60, do: :approaching + defp realtime_arrival_status(%{arrival_seconds: nil, departure_seconds: seconds}), + do: {:departure_seconds, seconds} + defp realtime_arrival_status(%{arrival_seconds: seconds}), do: {:arrival_seconds, seconds} + def arrival_status_from_vehicle(:in_transit), do: :approaching + def arrival_status_from_vehicle(:incoming), do: :arriving + def arrival_status_from_vehicle(:stopped), do: :boarding + @spec arrival_substatus(%{ predicted_schedule: PredictedSchedule.t(), route_type: Route.route_type() diff --git a/test/dotcom/schedule_finder/upcoming_departures_test.exs b/test/dotcom/schedule_finder/upcoming_departures_test.exs index d70eeeb2f9..2a49e24a21 100644 --- a/test/dotcom/schedule_finder/upcoming_departures_test.exs +++ b/test/dotcom/schedule_finder/upcoming_departures_test.exs @@ -17,6 +17,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do end) stub(Vehicles.Repo.Mock, :trip, fn _ -> Factories.Vehicles.Vehicle.build(:vehicle) end) + stub(Vehicles.Repo.Mock, :get, fn _ -> Factories.Vehicles.Vehicle.build(:vehicle) end) stub(Schedules.Repo.Mock, :by_route_ids, fn _, _ -> [] end) stub(Predictions.Repo.Mock, :all, fn _ -> [] end) @@ -2697,6 +2698,86 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do # Verify assert departures |> Enum.count() == 1 end + + test "uses vehicle status for subway arrival_status, if applicable" do + # Setup + now = Dotcom.Utils.DateTime.now() + + subway_route = Factories.Routes.Route.build(:route, type: 1) + direction_id = Faker.Util.pick([0, 1]) + stop = Factories.Stops.Stop.build(:stop, child_ids: FactoryHelpers.build_list(7, :id)) + + vehicle_at_stop = + Factories.Vehicles.Vehicle.build(:vehicle, stop_id: Faker.Util.pick(stop.child_ids)) + + departure_time = + Generators.DateTime.random_time_range_date_time( + {now, ServiceDateTime.end_of_service_day(now)} + ) + + predictions = + Factories.Predictions.Prediction.build_list(1, :prediction, + departure_time: departure_time, + route: subway_route, + stop: stop, + vehicle_id: vehicle_at_stop.id + ) + + expect(Predictions.Repo.Mock, :all, fn _ -> predictions end) + expect(Vehicles.Repo.Mock, :get, fn _ -> vehicle_at_stop end) + + # Exercise + [departure] = + UpcomingDepartures.upcoming_departures(%{ + direction_id: direction_id, + now: now, + route: subway_route, + stop_id: stop.id + }) + + # Verify + expected_status = UpcomingDepartures.arrival_status_from_vehicle(vehicle_at_stop.status) + assert departure.arrival_status == expected_status + end + + test "does not use vehicle status if vehicle not at stop" do + # Setup + now = Dotcom.Utils.DateTime.now() + + subway_route = Factories.Routes.Route.build(:route, type: 1) + direction_id = Faker.Util.pick([0, 1]) + stop = Factories.Stops.Stop.build(:stop, child_ids: []) + vehicle = Factories.Vehicles.Vehicle.build(:vehicle) + + departure_time = + Generators.DateTime.random_time_range_date_time( + {now, ServiceDateTime.end_of_service_day(now)} + ) + + predictions = + Factories.Predictions.Prediction.build_list(1, :prediction, + departure_time: departure_time, + route: subway_route, + stop: stop, + vehicle_id: vehicle.id + ) + + expect(Predictions.Repo.Mock, :all, fn _ -> predictions end) + expect(Vehicles.Repo.Mock, :get, fn _ -> vehicle end) + + # Exercise + [departure] = + UpcomingDepartures.upcoming_departures(%{ + direction_id: direction_id, + now: now, + route: subway_route, + stop_id: stop.id + }) + + # Verify + arrival_status_from_vehicle = UpcomingDepartures.arrival_status_from_vehicle(vehicle.status) + refute departure.arrival_status == arrival_status_from_vehicle + end end describe "last_trip_time/4" do From 039ee0203ff70d8fe8eb626c19e6d40b3dc5fbda Mon Sep 17 00:00:00 2001 From: Cristen Jones Date: Wed, 4 Feb 2026 21:26:54 -0500 Subject: [PATCH 2/4] feat(Vehicles.Vehicle): save current_stop_sequence --- lib/vehicles/parser.ex | 1 + lib/vehicles/supervisor.ex | 2 +- lib/vehicles/vehicle.ex | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/vehicles/parser.ex b/lib/vehicles/parser.ex index 74ba4a53a0..6c18b93dd4 100644 --- a/lib/vehicles/parser.ex +++ b/lib/vehicles/parser.ex @@ -11,6 +11,7 @@ defmodule Vehicles.Parser do stop_id: optional_id(relationships["stop"]), direction_id: attributes["direction_id"], status: status(attributes["current_status"]), + stop_sequence: attributes["current_stop_sequence"], longitude: attributes["longitude"], latitude: attributes["latitude"], bearing: attributes["bearing"] || 0, diff --git a/lib/vehicles/supervisor.ex b/lib/vehicles/supervisor.ex index 4a9bd2b563..df1e812025 100644 --- a/lib/vehicles/supervisor.ex +++ b/lib/vehicles/supervisor.ex @@ -38,7 +38,7 @@ defmodule Vehicles.Supervisor do MBTA.Api.Stream.build_options( name: Vehicles.Api.SSES, path: - "/vehicles?fields[vehicle]=direction_id,current_status,longitude,latitude,bearing,occupancy_status" + "/vehicles?fields[vehicle]=direction_id,current_status,current_stop_sequence,longitude,latitude,bearing,occupancy_status" ) [ diff --git a/lib/vehicles/vehicle.ex b/lib/vehicles/vehicle.ex index fc311eaeb2..44182d386e 100644 --- a/lib/vehicles/vehicle.ex +++ b/lib/vehicles/vehicle.ex @@ -9,6 +9,7 @@ defmodule Vehicles.Vehicle do :longitude, :latitude, :status, + :stop_sequence, :bearing, :crowding ] @@ -28,6 +29,7 @@ defmodule Vehicles.Vehicle do latitude: float, bearing: non_neg_integer, status: status, + stop_sequence: non_neg_integer, crowding: crowding | nil } end From 148df4edd82d26d2d1db420c30f52f5dba7614b5 Mon Sep 17 00:00:00 2001 From: Cristen Jones Date: Fri, 6 Feb 2026 10:46:39 -0500 Subject: [PATCH 3/4] feat(PredictedSchedule): get the vehicle status --- lib/predicted_schedule.ex | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/predicted_schedule.ex b/lib/predicted_schedule.ex index 9398758d7a..1f22959f89 100644 --- a/lib/predicted_schedule.ex +++ b/lib/predicted_schedule.ex @@ -21,6 +21,7 @@ defmodule PredictedSchedule do @predictions_repo Application.compile_env!(:dotcom, :repo_modules)[:predictions] @schedules_repo Application.compile_env!(:dotcom, :repo_modules)[:schedules] + @vehicles_repo Application.compile_env!(:dotcom, :repo_modules)[:vehicles] def get(route_id, stop_id, opts \\ []) do now = Keyword.get(opts, :now, Util.now()) @@ -278,6 +279,34 @@ defmodule PredictedSchedule do def status(%PredictedSchedule{prediction: %Prediction{status: status}}), do: status def status(_predicted_schedule), do: nil + @doc """ + Retrieves predicted schedule vehicle + """ + @spec vehicle(PredictedSchedule.t()) :: Vehicles.Vehicle.t() | nil + def vehicle(%PredictedSchedule{prediction: %Prediction{vehicle_id: vehicle_id}}) + when not is_nil(vehicle_id) do + @vehicles_repo.get(vehicle_id) + end + + def vehicle(_), do: nil + + @doc """ + Retrieves status from predicted schedule vehicle if a vehicle is at or approaching the predicted schedule trip/stop/stop_sequence + """ + @spec vehicle_at_stop_status(PredictedSchedule.t()) :: Vehicles.Vehicle.status() | nil + def vehicle_at_stop_status(ps) do + stop = stop(ps) + stop_sequence = stop_sequence(ps) + vehicle = vehicle(ps) + + if not is_nil(vehicle) and vehicle.stop_sequence == stop_sequence and + vehicle.stop_id in [stop.id | stop.child_ids] do + vehicle.status + end + end + + def vehicle_status(_predicted_schedule), do: nil + @doc """ Determines if the given predicted schedule occurs after the given time """ From 56175d7b979e925e4524b92aea86ce63df65b2f6 Mon Sep 17 00:00:00 2001 From: Cristen Jones Date: Fri, 6 Feb 2026 10:48:33 -0500 Subject: [PATCH 4/4] refactor(UpcomingDepartures): use vehicle status --- .../schedule_finder/upcoming_departures.ex | 60 ++---- .../upcoming_departures_test.exs | 188 +++++++++++------- 2 files changed, 142 insertions(+), 106 deletions(-) diff --git a/lib/dotcom/schedule_finder/upcoming_departures.ex b/lib/dotcom/schedule_finder/upcoming_departures.ex index 898004d1f5..ede3c8493e 100644 --- a/lib/dotcom/schedule_finder/upcoming_departures.ex +++ b/lib/dotcom/schedule_finder/upcoming_departures.ex @@ -18,7 +18,6 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do @predictions_repo Application.compile_env!(:dotcom, :repo_modules)[:predictions] @schedules_repo Application.compile_env!(:dotcom, :repo_modules)[:schedules] @stops_repo Application.compile_env!(:dotcom, :repo_modules)[:stops] - @vehicles_repo Application.compile_env!(:dotcom, :repo_modules)[:vehicles] defmodule UpcomingDeparture do @moduledoc """ @@ -211,6 +210,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do !prediction_departure_time && !schedule_departure_time end + # don't reject if there's associated vehicle with status for this stop defp reject_past_schedules(predicted_schedules, now) do predicted_schedules |> Enum.reject(fn @@ -246,19 +246,9 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do route_type: route_type, stop_id: stop_id }) do - stop = predicted_schedule |> PredictedSchedule.stop() trip = predicted_schedule |> PredictedSchedule.trip() stop_sequence = PredictedSchedule.stop_sequence(predicted_schedule) - - vehicle = - if predicted_schedule.prediction do - @vehicles_repo.get(predicted_schedule.prediction.vehicle_id) - end - - vehicle_status = - if vehicle && vehicle.stop_id in stop.child_ids do - vehicle.status - end + vehicle_at_stop_status = PredictedSchedule.vehicle_at_stop_status(predicted_schedule) trip_details = trip_details(%{ @@ -275,7 +265,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do route_type: route_type, status: PredictedSchedule.status(predicted_schedule), now: now, - vehicle_status: vehicle_status + vehicle_at_stop_status: vehicle_at_stop_status }), arrival_substatus: arrival_substatus(%{ @@ -340,7 +330,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do predicted_schedule: PredictedSchedule.t(), route_type: Route.route_type(), status: nil | String.t(), - vehicle_status: nil | Vehicles.Vehicle.status() + vehicle_at_stop_status: nil | Vehicles.Vehicle.status() }) :: __MODULE__.UpcomingDeparture.arrival_status_t() defp arrival_status(%{ predicted_schedule: %PredictedSchedule{prediction: nil}, @@ -390,7 +380,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do predicted_schedule: %PredictedSchedule{prediction: prediction}, route_type: route_type, now: now, - vehicle_status: vehicle_status + vehicle_at_stop_status: vehicle_at_stop_status }) when prediction != nil do arrival_seconds = seconds_between(prediction.arrival_time, now) @@ -400,7 +390,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do arrival_seconds: arrival_seconds, departure_seconds: departure_seconds, route_type: route_type, - vehicle_status: vehicle_status + vehicle_at_stop_status: vehicle_at_stop_status }) end @@ -423,7 +413,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do arrival_seconds: integer(), departure_seconds: integer(), route_type: Route.route_type(), - vehicle_status: nil | Vehicles.Vehicle.status() + vehicle_at_stop_status: nil | Vehicles.Vehicle.status() }) :: __MODULE__.UpcomingDeparture.realtime_arrival_status_t() defp realtime_arrival_status(%{ @@ -437,43 +427,35 @@ defmodule Dotcom.ScheduleFinder.UpcomingDepartures do defp realtime_arrival_status(%{arrival_seconds: seconds, route_type: :bus}) when seconds <= 30, do: :now - defp realtime_arrival_status(%{route_type: :subway, vehicle_status: vehicle_status}) - when vehicle_status != nil do - arrival_status_from_vehicle(vehicle_status) - end - + # vehicle says it's at the stop defp realtime_arrival_status(%{ - arrival_seconds: nil, + arrival_seconds: arrival_seconds, departure_seconds: departure_seconds, - route_type: :subway + route_type: :subway, + vehicle_at_stop_status: :stopped }) - when departure_seconds <= 90, + when (arrival_seconds < 0 and departure_seconds >= 0) or departure_seconds <= 90, do: :boarding defp realtime_arrival_status(%{ arrival_seconds: arrival_seconds, - departure_seconds: departure_seconds, - route_type: :subway + route_type: :subway, + vehicle_at_stop_status: status }) - when arrival_seconds < 0 and departure_seconds >= 0, do: :boarding + when status != nil and arrival_seconds <= 30, do: :arriving - defp realtime_arrival_status(%{arrival_seconds: arrival_seconds, route_type: :subway}) - when arrival_seconds <= 30, - do: :arriving - - defp realtime_arrival_status(%{arrival_seconds: arrival_seconds, route_type: :subway}) - when arrival_seconds <= 60, - do: :approaching + defp realtime_arrival_status(%{ + arrival_seconds: arrival_seconds, + route_type: :subway, + vehicle_at_stop_status: status + }) + when status != nil and arrival_seconds <= 60, do: :approaching defp realtime_arrival_status(%{arrival_seconds: nil, departure_seconds: seconds}), do: {:departure_seconds, seconds} defp realtime_arrival_status(%{arrival_seconds: seconds}), do: {:arrival_seconds, seconds} - def arrival_status_from_vehicle(:in_transit), do: :approaching - def arrival_status_from_vehicle(:incoming), do: :arriving - def arrival_status_from_vehicle(:stopped), do: :boarding - @spec arrival_substatus(%{ predicted_schedule: PredictedSchedule.t(), route_type: Route.route_type() diff --git a/test/dotcom/schedule_finder/upcoming_departures_test.exs b/test/dotcom/schedule_finder/upcoming_departures_test.exs index 2a49e24a21..1f8d160f60 100644 --- a/test/dotcom/schedule_finder/upcoming_departures_test.exs +++ b/test/dotcom/schedule_finder/upcoming_departures_test.exs @@ -1155,7 +1155,7 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do assert departures |> Enum.count() == 1 end - test "shows subway arrival_status as :approaching if it's between 30 and 60 seconds out" do + test "shows subway arrival_status as :approaching if it has an associated vehicle and it's between 30 and 60 seconds out" do # Setup now = Dotcom.Utils.DateTime.now() @@ -1163,25 +1163,38 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do route_id = route.id stop_id = FactoryHelpers.build(:id) trip_id = FactoryHelpers.build(:id) + vehicle_id = FactoryHelpers.build(:id) direction_id = Faker.Util.pick([0, 1]) seconds_until_arrival = Faker.random_between(31, 60) arrival_time = now |> DateTime.shift(second: seconds_until_arrival) + prediction = + Factories.Predictions.Prediction.build(:prediction, + arrival_time: arrival_time, + stop: Factories.Stops.Stop.build(:stop, id: stop_id), + trip: Factories.Schedules.Trip.build(:trip, id: trip_id), + vehicle_id: vehicle_id + ) + expect(Predictions.Repo.Mock, :all, fn [ route: ^route_id, direction_id: ^direction_id, include_terminals: true ] -> - [ - Factories.Predictions.Prediction.build(:prediction, - arrival_time: arrival_time, - stop: Factories.Stops.Stop.build(:stop, id: stop_id), - trip: Factories.Schedules.Trip.build(:trip, id: trip_id) - ) - ] + [prediction] end) + vehicle = + Factories.Vehicles.Vehicle.build(:vehicle, + id: vehicle_id, + status: :incoming, + stop_id: stop_id, + stop_sequence: prediction.stop_sequence + ) + + expect(Vehicles.Repo.Mock, :get, fn ^vehicle_id -> vehicle end) + # Exercise departures = UpcomingDepartures.upcoming_departures(%{ @@ -1248,7 +1261,8 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do stop_id = FactoryHelpers.build(:id) trip_id = FactoryHelpers.build(:id) direction_id = Faker.Util.pick([0, 1]) - + vehicle_id = FactoryHelpers.build(:id) + stop_sequence = Faker.random_between(0, 300) seconds_until_arrival = Faker.random_between(1, 30) arrival_time = now |> DateTime.shift(second: seconds_until_arrival) @@ -1261,11 +1275,23 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do Factories.Predictions.Prediction.build(:prediction, arrival_time: arrival_time, stop: Factories.Stops.Stop.build(:stop, id: stop_id), - trip: Factories.Schedules.Trip.build(:trip, id: trip_id) + trip: Factories.Schedules.Trip.build(:trip, id: trip_id), + stop_sequence: stop_sequence, + vehicle_id: vehicle_id ) ] end) + vehicle = + Factories.Vehicles.Vehicle.build(:vehicle, + id: vehicle_id, + status: :incoming, + stop_id: stop_id, + stop_sequence: stop_sequence + ) + + expect(Vehicles.Repo.Mock, :get, fn ^vehicle_id -> vehicle end) + # Exercise departures = UpcomingDepartures.upcoming_departures(%{ @@ -1290,11 +1316,21 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do stop_id = FactoryHelpers.build(:id) stop = Factories.Stops.Stop.build(:stop, id: stop_id) trip_id = FactoryHelpers.build(:id) + vehicle_id = FactoryHelpers.build(:id) direction_id = Faker.Util.pick([0, 1]) seconds_until_departure = Faker.random_between(1, 90) + stop_sequence = Faker.random_between(0, 300) departure_time = now |> DateTime.shift(second: seconds_until_departure) + vehicle = + Factories.Vehicles.Vehicle.build(:vehicle, + id: vehicle_id, + status: :stopped, + stop_id: stop.id, + stop_sequence: stop_sequence + ) + expect(Predictions.Repo.Mock, :all, fn [ route: ^route_id, direction_id: ^direction_id, @@ -1305,11 +1341,15 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do arrival_time: now |> DateTime.shift(second: -30), departure_time: departure_time, stop: stop, - trip: Factories.Schedules.Trip.build(:trip, id: trip_id) + stop_sequence: stop_sequence, + trip: Factories.Schedules.Trip.build(:trip, id: trip_id), + vehicle_id: vehicle.id ) ] end) + expect(Vehicles.Repo.Mock, :get, fn _ -> vehicle end) + # Exercise departures = UpcomingDepartures.upcoming_departures(%{ @@ -1330,28 +1370,34 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do now = Dotcom.Utils.DateTime.now() route = Factories.Routes.Route.build(:subway_route) - route_id = route.id stop_id = FactoryHelpers.build(:id) stop = Factories.Stops.Stop.build(:stop, id: stop_id) trip_id = FactoryHelpers.build(:id) direction_id = Faker.Util.pick([0, 1]) + vehicle_id = FactoryHelpers.build(:id) seconds_until_departure = Faker.random_between(1, 90) departure_time = now |> DateTime.shift(second: seconds_until_departure) - expect(Predictions.Repo.Mock, :all, fn [ - route: ^route_id, - direction_id: ^direction_id, - include_terminals: true - ] -> - [ - Factories.Predictions.Prediction.build(:prediction, - arrival_time: nil, - departure_time: departure_time, - stop: stop, - trip: Factories.Schedules.Trip.build(:trip, id: trip_id) - ) - ] + prediction = + Factories.Predictions.Prediction.build(:prediction, + arrival_time: nil, + departure_time: departure_time, + stop: stop, + trip: Factories.Schedules.Trip.build(:trip, id: trip_id), + vehicle_id: vehicle_id + ) + + expect(Predictions.Repo.Mock, :all, fn _ -> [prediction] end) + + expect(Vehicles.Repo.Mock, :get, fn _ -> + Factories.Vehicles.Vehicle.build(:vehicle, + id: vehicle_id, + status: :stopped, + stop_id: prediction.stop.id, + trip_id: prediction.trip.id, + stop_sequence: prediction.stop_sequence + ) end) # Exercise @@ -2699,84 +2745,92 @@ defmodule Dotcom.ScheduleFinder.UpcomingDeparturesTest do assert departures |> Enum.count() == 1 end - test "uses vehicle status for subway arrival_status, if applicable" do + test "uses vehicle :stopped status for subway arrival_status, if applicable" do # Setup now = Dotcom.Utils.DateTime.now() - subway_route = Factories.Routes.Route.build(:route, type: 1) - direction_id = Faker.Util.pick([0, 1]) - stop = Factories.Stops.Stop.build(:stop, child_ids: FactoryHelpers.build_list(7, :id)) - - vehicle_at_stop = - Factories.Vehicles.Vehicle.build(:vehicle, stop_id: Faker.Util.pick(stop.child_ids)) + subway_route = Factories.Routes.Route.build(:subway_route) + vehicle_id = FactoryHelpers.build(:id) + seconds_until_departure = Faker.random_between(0, 90) + arrival_time = now |> DateTime.shift(second: -5) + departure_time = now |> DateTime.shift(second: seconds_until_departure) - departure_time = - Generators.DateTime.random_time_range_date_time( - {now, ServiceDateTime.end_of_service_day(now)} + prediction = + Factories.Predictions.Prediction.build(:prediction, + arrival_time: arrival_time, + departure_time: departure_time, + route: subway_route ) - predictions = - Factories.Predictions.Prediction.build_list(1, :prediction, - departure_time: departure_time, - route: subway_route, - stop: stop, - vehicle_id: vehicle_at_stop.id + vehicle_at_stop = + Factories.Vehicles.Vehicle.build(:vehicle, + id: vehicle_id, + status: :stopped, + stop_id: prediction.stop.id, + trip_id: prediction.trip.id, + stop_sequence: prediction.stop_sequence ) - expect(Predictions.Repo.Mock, :all, fn _ -> predictions end) - expect(Vehicles.Repo.Mock, :get, fn _ -> vehicle_at_stop end) + prediction = Map.put(prediction, :vehicle_id, vehicle_id) + + expect(Predictions.Repo.Mock, :all, fn _ -> [prediction] end) + expect(Vehicles.Repo.Mock, :get, fn ^vehicle_id -> vehicle_at_stop end) # Exercise [departure] = UpcomingDepartures.upcoming_departures(%{ - direction_id: direction_id, + direction_id: prediction.direction_id, now: now, route: subway_route, - stop_id: stop.id + stop_id: prediction.stop.id }) # Verify - expected_status = UpcomingDepartures.arrival_status_from_vehicle(vehicle_at_stop.status) - assert departure.arrival_status == expected_status + assert departure.arrival_status == :boarding end - test "does not use vehicle status if vehicle not at stop" do + test "does not use vehicle :stopped status if vehicle not at stop" do # Setup now = Dotcom.Utils.DateTime.now() - subway_route = Factories.Routes.Route.build(:route, type: 1) - direction_id = Faker.Util.pick([0, 1]) - stop = Factories.Stops.Stop.build(:stop, child_ids: []) - vehicle = Factories.Vehicles.Vehicle.build(:vehicle) + subway_route = Factories.Routes.Route.build(:subway_route) + other_stop_id = FactoryHelpers.build(:id) + vehicle_id = FactoryHelpers.build(:id) + seconds_until_departure = Faker.random_between(0, 90) + arrival_time = now |> DateTime.shift(second: -5) + departure_time = now |> DateTime.shift(second: seconds_until_departure) - departure_time = - Generators.DateTime.random_time_range_date_time( - {now, ServiceDateTime.end_of_service_day(now)} + prediction = + Factories.Predictions.Prediction.build(:prediction, + arrival_time: arrival_time, + departure_time: departure_time, + route: subway_route ) - predictions = - Factories.Predictions.Prediction.build_list(1, :prediction, - departure_time: departure_time, - route: subway_route, - stop: stop, - vehicle_id: vehicle.id + vehicle_at_stop = + Factories.Vehicles.Vehicle.build(:vehicle, + id: vehicle_id, + status: :stopped, + stop_id: other_stop_id, + trip_id: prediction.trip.id ) - expect(Predictions.Repo.Mock, :all, fn _ -> predictions end) - expect(Vehicles.Repo.Mock, :get, fn _ -> vehicle end) + prediction = Map.put(prediction, :vehicle_id, vehicle_id) + + expect(Predictions.Repo.Mock, :all, fn _ -> [prediction] end) + expect(Vehicles.Repo.Mock, :get, fn ^vehicle_id -> vehicle_at_stop end) # Exercise [departure] = UpcomingDepartures.upcoming_departures(%{ - direction_id: direction_id, + direction_id: prediction.direction_id, now: now, route: subway_route, - stop_id: stop.id + stop_id: prediction.stop.id }) # Verify - arrival_status_from_vehicle = UpcomingDepartures.arrival_status_from_vehicle(vehicle.status) - refute departure.arrival_status == arrival_status_from_vehicle + refute departure.arrival_status == :boarding end end