From f441dd1040a4d0ea9766ee1d4eef105c5e3c9a6f Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Mon, 28 Aug 2023 16:39:46 -0400 Subject: [PATCH] Crowding data confidence determination (#673) * Add locations feed engine * Add unit test for 'deletion' logic * Add a few more tests * remmove io puts * fix typedef * Begin reading in multi_carriage_details field * Calculate crowding data confidence level * Branch arrival and approaching audio logic based on crowding data confidence level * Add a todo for where to incorporate read loop selection logic * Clean up calculation logic * adjust mapping logic to match actual schema * Address PR comments * Use fully qualified name for CarriageDetails type --- lib/content/audio/predictions.ex | 59 +++++++++++++++++++++--------- lib/content/message/predictions.ex | 28 ++++++++++++-- lib/engine/locations.ex | 32 ++++++++++++++-- lib/locations/carriage_details.ex | 18 +++++++++ lib/locations/location.ex | 6 ++- lib/signs/utilities/audio.ex | 2 + 6 files changed, 120 insertions(+), 25 deletions(-) create mode 100644 lib/locations/carriage_details.ex diff --git a/lib/content/audio/predictions.ex b/lib/content/audio/predictions.ex index 0eecc1943..11639cfd3 100644 --- a/lib/content/audio/predictions.ex +++ b/lib/content/audio/predictions.ex @@ -46,26 +46,51 @@ defmodule Content.Audio.Predictions do ] predictions.minutes == :arriving -> - [ - %TrainIsArriving{ - destination: predictions.destination, - trip_id: predictions.trip_id, - platform: predictions.platform, - route_id: predictions.route_id - } - ] + if predictions.crowding_data_confidence == :high do + # TODO: Pass along crowding data classification once available + [ + %TrainIsArriving{ + destination: predictions.destination, + trip_id: predictions.trip_id, + platform: predictions.platform, + route_id: predictions.route_id + } + ] + else + [ + %TrainIsArriving{ + destination: predictions.destination, + trip_id: predictions.trip_id, + platform: predictions.platform, + route_id: predictions.route_id + } + ] + end predictions.minutes == :approaching and (line == :top or multi_source?) and predictions.route_id in @heavy_rail_routes -> - [ - %Approaching{ - destination: predictions.destination, - trip_id: predictions.trip_id, - platform: predictions.platform, - route_id: predictions.route_id, - new_cars?: predictions.new_cars? - } - ] + if predictions.crowding_data_confidence == :high do + # TODO: Pass along crowding data classification once available + [ + %Approaching{ + destination: predictions.destination, + trip_id: predictions.trip_id, + platform: predictions.platform, + route_id: predictions.route_id, + new_cars?: predictions.new_cars? + } + ] + else + [ + %Approaching{ + destination: predictions.destination, + trip_id: predictions.trip_id, + platform: predictions.platform, + route_id: predictions.route_id, + new_cars?: predictions.new_cars? + } + ] + end predictions.minutes == :approaching -> [ diff --git a/lib/content/message/predictions.ex b/lib/content/message/predictions.ex index abc48223d..648d267d5 100644 --- a/lib/content/message/predictions.ex +++ b/lib/content/message/predictions.ex @@ -31,7 +31,8 @@ defmodule Content.Message.Predictions do platform: nil, new_cars?: false, terminal?: false, - certainty: nil + certainty: nil, + crowding_data_confidence: nil ] @type t :: %__MODULE__{ @@ -47,7 +48,8 @@ defmodule Content.Message.Predictions do zone: String.t() | nil, platform: Content.platform() | nil, terminal?: boolean(), - certainty: non_neg_integer() | nil + certainty: non_neg_integer() | nil, + crowding_data_confidence: :high | :low | nil } @spec non_terminal( @@ -77,6 +79,14 @@ defmodule Content.Message.Predictions do true -> predicted_time |> Kernel./(60) |> round() end + crowding_data_confidence = + calculate_crowding_data_confidence( + prediction, + Engine.Locations.for_vehicle(prediction.vehicle_id) + ) + + # TODO: Calculate crowding data classification and pass that along as well + case Content.Utilities.destination_for_prediction( prediction.route_id, prediction.direction_id, @@ -95,7 +105,8 @@ defmodule Content.Message.Predictions do station_code: station_code, zone: zone, platform: platform, - certainty: certainty + certainty: certainty, + crowding_data_confidence: crowding_data_confidence } {:error, _} -> @@ -143,6 +154,17 @@ defmodule Content.Message.Predictions do end end + defp calculate_crowding_data_confidence(_prediction, nil), do: nil + + defp calculate_crowding_data_confidence(prediction, location) + when prediction.route_id in ["Orange"] and location.stop_id == prediction.stop_id do + if location.status in [:incoming_at, :in_transit_to], + do: :high, + else: :low + end + + defp calculate_crowding_data_confidence(_prediction, _location), do: nil + defimpl Content.Message do require Logger diff --git a/lib/engine/locations.ex b/lib/engine/locations.ex index bd230be8e..b394d5eec 100644 --- a/lib/engine/locations.ex +++ b/lib/engine/locations.ex @@ -135,16 +135,42 @@ defmodule Engine.Locations do defp location_from_update(location) do %Locations.Location{ vehicle_id: get_in(location, ["vehicle", "vehicle", "id"]), - status: status_to_atom(location["vehicle"]["current_status"]), + status: vehicle_status_to_atom(location["vehicle"]["current_status"]), stop_id: location["vehicle"]["stop_id"], timestamp: location["vehicle"]["timestamp"], route_id: location["vehicle"]["trip"]["route_id"], trip_id: location["vehicle"]["trip"]["trip_id"], - consist: location["vehicle"]["vehicle"]["consist"] + consist: location["vehicle"]["vehicle"]["consist"], + multi_carriage_details: + location["vehicle"]["multi_carriage_details"] && + Enum.map( + location["vehicle"]["multi_carriage_details"], + &parse_carriage_details/1 + ) } end - defp status_to_atom(status) do + defp parse_carriage_details(multi_carriage_details) do + %Locations.CarriageDetails{ + label: multi_carriage_details["label"], + occupancy_status: occupancy_status_to_atom(multi_carriage_details["occupancy_status"]), + occupancy_percentage: multi_carriage_details["occupancy_percentage"], + carriage_sequence: multi_carriage_details["carriage_sequence"] + } + end + + defp occupancy_status_to_atom(status) do + case status do + "MANY_SEATS_AVAILABLE" -> :many_seats_available + "FEW_SEATS_AVAILABLE" -> :few_seats_available + "STANDING_ROOM_ONLY" -> :standing_room_only + "CRUSHED_STANDING_ROOM_ONLY" -> :crushed_standing_room_only + "FULL" -> :full + _ -> :unknown + end + end + + defp vehicle_status_to_atom(status) do case status do "INCOMING_AT" -> :incoming_at "STOPPED_AT" -> :stopped_at diff --git a/lib/locations/carriage_details.ex b/lib/locations/carriage_details.ex new file mode 100644 index 000000000..767f7a0a7 --- /dev/null +++ b/lib/locations/carriage_details.ex @@ -0,0 +1,18 @@ +defmodule Locations.CarriageDetails do + defstruct label: nil, + occupancy_status: nil, + occupancy_percentage: nil, + carriage_sequence: nil + + @type t :: %__MODULE__{ + label: String.t(), + occupancy_status: + :many_seats_available + | :few_seats_available + | :standing_room_only + | :crushed_standing_room_only + | :full, + occupancy_percentage: non_neg_integer(), + carriage_sequence: non_neg_integer() + } +end diff --git a/lib/locations/location.ex b/lib/locations/location.ex index f4c4f71ac..ca5ed854e 100644 --- a/lib/locations/location.ex +++ b/lib/locations/location.ex @@ -5,7 +5,8 @@ defmodule Locations.Location do timestamp: nil, route_id: nil, trip_id: nil, - consist: [] + consist: [], + multi_carriage_details: [] @type t :: %__MODULE__{ vehicle_id: String.t() | nil, @@ -14,6 +15,7 @@ defmodule Locations.Location do timestamp: DateTime.t() | nil, route_id: String.t() | nil, trip_id: String.t() | nil, - consist: list() + consist: list(), + multi_carriage_details: list(Locations.CarriageDetails.t()) } end diff --git a/lib/signs/utilities/audio.ex b/lib/signs/utilities/audio.ex index f097a9f60..3a8f0b0a9 100644 --- a/lib/signs/utilities/audio.ex +++ b/lib/signs/utilities/audio.ex @@ -146,6 +146,7 @@ defmodule Signs.Utilities.Audio do {[], sign.announced_approachings, sign.announced_arrivals}, fn audio, {new_audios, new_approaching_trips, new_arriving_trips} -> case audio do + # TODO: Add a check for whether high-confidence crowding info is present and whether it has already been announced with an approaching message %Audio.TrainIsArriving{trip_id: trip_id} when not is_nil(trip_id) -> if audio.trip_id in sign.announced_arrivals do {new_audios, new_approaching_trips, new_arriving_trips} @@ -154,6 +155,7 @@ defmodule Signs.Utilities.Audio do [audio.trip_id | new_arriving_trips]} end + # TODO: Start tracking trip_ids where we announce high-confidence crowding info with Approaching %Audio.Approaching{trip_id: trip_id} when not is_nil(trip_id) -> if audio.trip_id in sign.announced_approachings do {new_audios, new_approaching_trips, new_arriving_trips}