-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
VehicleBeforeStop: group filter to restore stop updates
- Loading branch information
1 parent
0f81cfd
commit c0a67d0
Showing
7 changed files
with
254 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
lib/concentrate/group_filter/cache/vehicle_before_stop.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
defmodule Concentrate.GroupFilter.Cache.VehicleBeforeStop do | ||
@moduledoc """ | ||
Server to maintain a cache of previously seen StopTimeUpdates for a given trip. | ||
As the vehicle moves through, we'll remove the older updates. Periodically, | ||
we'll scan for StopTimeUpdates in the past and remove them. | ||
""" | ||
use GenServer | ||
alias Concentrate.{VehiclePosition, StopTimeUpdate} | ||
|
||
@table __MODULE__ | ||
# 5 minutes | ||
@stale_timeout_seconds 300 | ||
|
||
@spec stop_time_updates_for_vehicle(VehiclePosition.t(), [StopTimeUpdate.t()]) :: [ | ||
StopTimeUpdate.t() | ||
] | ||
def stop_time_updates_for_vehicle(vehicle_position, stop_time_updates) do | ||
if is_integer(VehiclePosition.stop_sequence(vehicle_position)) do | ||
insert_new_updates!(stop_time_updates) | ||
delete_old_updates(vehicle_position) | ||
fetch_updates_with_stop_sequence_ge_than_vehicle(vehicle_position) | ||
else | ||
stop_time_updates | ||
end | ||
rescue | ||
ArgumentError -> | ||
stop_time_updates | ||
end | ||
|
||
defp insert_new_updates!(stop_time_updates) do | ||
inserts = | ||
for stu <- stop_time_updates, | ||
stop_sequence <- List.wrap(StopTimeUpdate.stop_sequence(stu)) do | ||
trip_id = StopTimeUpdate.trip_id(stu) | ||
time = StopTimeUpdate.time(stu) | ||
:ets.match_delete(@table, {trip_id, stop_sequence, :_, :_}) | ||
{trip_id, stop_sequence, time, stu} | ||
end | ||
|
||
:ets.insert(@table, inserts) | ||
end | ||
|
||
defp fetch_updates_with_stop_sequence_ge_than_vehicle(vp) do | ||
unsorted = | ||
:ets.select(@table, [ | ||
{ | ||
{VehiclePosition.trip_id(vp), :"$1", :_, :"$2"}, | ||
[{:>=, :"$1", VehiclePosition.stop_sequence(vp)}], | ||
[:"$2"] | ||
} | ||
]) | ||
|
||
Enum.sort_by(unsorted, &StopTimeUpdate.stop_sequence/1) | ||
end | ||
|
||
defp delete_old_updates(vp) do | ||
:ets.select_delete(@table, [ | ||
{ | ||
{VehiclePosition.trip_id(vp), :"$1", :_, :_}, | ||
[{:<, :"$1", VehiclePosition.stop_sequence(vp)}], | ||
[true] | ||
} | ||
]) | ||
end | ||
|
||
def start_link([]) do | ||
GenServer.start_link(__MODULE__, []) | ||
end | ||
|
||
@impl GenServer | ||
def init([]) do | ||
@table = :ets.new(@table, [:bag, :named_table, :public]) | ||
schedule_clear!() | ||
{:ok, []} | ||
end | ||
|
||
@impl GenServer | ||
def handle_info(:clear, state) do | ||
now = System.system_time(:seconds) | ||
minimum_time = now - @stale_timeout_seconds | ||
|
||
:ets.select_delete(@table, [ | ||
{ | ||
{:_, :_, :"$1", :_}, | ||
[{:<, :"$1", minimum_time}], | ||
[true] | ||
} | ||
]) | ||
|
||
{:noreply, state} | ||
end | ||
|
||
def handle_info(message, state) do | ||
super(message, state) | ||
end | ||
|
||
defp schedule_clear! do | ||
send(self(), :clear) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
defmodule Concentrate.GroupFilter.VehicleBeforeStop do | ||
@moduledoc """ | ||
Adds a historic StopTimeUpdate for the trip if the vehicle hasn't moved past it yet. | ||
""" | ||
@behaviour Concentrate.GroupFilter | ||
alias Concentrate.TripUpdate | ||
alias Concentrate.GroupFilter.Cache.VehicleBeforeStop, as: Cache | ||
|
||
@impl Concentrate.GroupFilter | ||
def filter({%TripUpdate{} = tu, [vp], stus}) do | ||
stus = Cache.stop_time_updates_for_vehicle(vp, stus) | ||
|
||
{tu, [vp], stus} | ||
end | ||
|
||
def filter(other), do: other | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
test/concentrate/group_filter/cache/vehicle_before_stop_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
defmodule Concentrate.GroupFilter.Cache.VehicleBeforeStopTest do | ||
@moduledoc false | ||
use ExUnit.Case | ||
import Concentrate.GroupFilter.Cache.VehicleBeforeStop | ||
alias Concentrate.{VehiclePosition, StopTimeUpdate} | ||
|
||
defp supervised(_) do | ||
{:ok, _} = start_supervised(Concentrate.GroupFilter.Cache.VehicleBeforeStop) | ||
:ok | ||
end | ||
|
||
describe "stop_time_updates_for_vehicle/2" do | ||
setup :supervised | ||
|
||
test "restores older StopTimeUpdate values if the vehicle hasn't reached them" do | ||
trip_id = "before_stop_test" | ||
|
||
vp = | ||
VehiclePosition.new( | ||
id: "vehicle", | ||
trip_id: trip_id, | ||
stop_sequence: 1, | ||
latitude: 1.0, | ||
longitude: 1.0 | ||
) | ||
|
||
stus = | ||
for stop_sequence <- 1..4 do | ||
StopTimeUpdate.new(trip_id: trip_id, stop_sequence: stop_sequence) | ||
end | ||
|
||
assert stop_time_updates_for_vehicle(vp, stus) == stus | ||
# restores the first two StopTimeUpdates since the vehicle hasn't | ||
# reached them | ||
assert stop_time_updates_for_vehicle(vp, Enum.drop(stus, 2)) == stus | ||
|
||
# restores the second StopTimeUpdate since the vehicle is past the | ||
# first one | ||
vp = VehiclePosition.update_stop_sequence(vp, 2) | ||
assert stop_time_updates_for_vehicle(vp, Enum.drop(stus, 2)) == Enum.drop(stus, 1) | ||
end | ||
|
||
test "uses updated stop time updates for future changes" do | ||
trip_id = "before_stop_test" | ||
|
||
vp = | ||
VehiclePosition.new( | ||
id: "vehicle", | ||
trip_id: trip_id, | ||
stop_sequence: 1, | ||
latitude: 1.0, | ||
longitude: 1.0 | ||
) | ||
|
||
stus = | ||
for stop_sequence <- 1..4 do | ||
StopTimeUpdate.new(trip_id: trip_id, stop_sequence: stop_sequence) | ||
end | ||
|
||
assert stop_time_updates_for_vehicle(vp, stus) == stus | ||
vp = VehiclePosition.update_stop_sequence(vp, 2) | ||
|
||
new_stus = | ||
for stop_sequence <- 3..4 do | ||
StopTimeUpdate.new(trip_id: trip_id, stop_sequence: stop_sequence, arrival_time: 5) | ||
end | ||
|
||
# we expect one old update, plus the two new ones | ||
expected = Enum.slice(stus, 1..1) ++ new_stus | ||
assert stop_time_updates_for_vehicle(vp, new_stus) == expected | ||
end | ||
end | ||
|
||
describe "missing ETS table" do | ||
test "stop_time_updates_for_vehicle returns same updates" do | ||
vp = VehiclePosition.new(latitude: 1, longitude: 1) | ||
stu = StopTimeUpdate.new([]) | ||
assert stop_time_updates_for_vehicle(vp, [stu]) == [stu] | ||
end | ||
end | ||
end |
47 changes: 47 additions & 0 deletions
47
test/concentrate/group_filter/vehicle_before_stop_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
defmodule Concentrate.GroupFilter.VehicleBeforeStopTest do | ||
@moduledoc false | ||
use ExUnit.Case | ||
import Concentrate.GroupFilter.VehicleBeforeStop | ||
alias Concentrate.{TripUpdate, VehiclePosition, StopTimeUpdate} | ||
|
||
describe "filter/1" do | ||
setup do | ||
{:ok, _} = start_supervised(Concentrate.GroupFilter.Cache.VehicleBeforeStop) | ||
:ok | ||
end | ||
|
||
test "restores older StopTimeUpdate values if the vehicle hasn't reached them yet" do | ||
trip_id = "before_stop_test" | ||
tu = TripUpdate.new(trip_id: trip_id) | ||
|
||
vp = | ||
VehiclePosition.new( | ||
id: "vehicle", | ||
trip_id: trip_id, | ||
stop_sequence: 1, | ||
latitude: 1.0, | ||
longitude: 1.0 | ||
) | ||
|
||
stus = | ||
for stop_sequence <- 1..4 do | ||
StopTimeUpdate.new(trip_id: trip_id, stop_sequence: stop_sequence) | ||
end | ||
|
||
group = {tu, [vp], stus} | ||
assert filter(group) == group | ||
# restores the first two StopTimeUpdates since the vehicle hasn't | ||
# reached them | ||
assert filter({tu, [vp], Enum.drop(stus, 2)}) == group | ||
|
||
# restores the second StopTimeUpdate since the vehicle is past the | ||
# first one | ||
vp = VehiclePosition.update_stop_sequence(vp, 2) | ||
assert filter({tu, [vp], Enum.drop(stus, 2)}) == {tu, [vp], Enum.drop(stus, 1)} | ||
end | ||
|
||
test "ignores unknown values" do | ||
assert filter(:value) == :value | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters