Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Itinerary Groups

livebooks/itinerary-groups.livemd

Itinerary Groups

Section

defmodule ItineraryGroups.Helpers do
  @moduledoc false

  def attach_groups_to_plan(%{"from" => from, "to" => to} = trip) do
    plan(from, to)
    |> Kernel.elem(1)
    |> Enum.reject(&Kernel.is_binary/1)
    |> group()
    |> Kernel.then(&Map.put(trip, "groups", &1))
  end

  defp combined_leg_to_tuple(%Leg{mode: %PersonalDetail{}} = leg) do
    unique_leg_to_tuple(leg)
  end

  defp combined_leg_to_tuple(%Leg{mode: %{route: route}} = leg) do
    {route.id, leg.from.name, leg.to.name}
  end

  defp departures(group) do
    group
    |> Enum.take(5)
    |> Enum.map(&itinerary_to_departure/1)
  end

  defp format_datetime(datetime) do
    Timex.format!(datetime, "%H:%M %p", :strftime)
  end

  defp group(itineraries) do
    itineraries
    |> Enum.group_by(&unique_legs_to_hash/1)
    |> groups_to_strings()
  end

  defp groups_to_strings(groups) do
    Enum.map(groups, fn {_, group} ->
      %{
        departures: departures(group),
        legs:
          group
          |> Enum.uniq_by(&itinerary_to_hash/1)
          |> Enum.map(fn itinerary ->
            itinerary |> Map.get(:legs) |> legs_to_string()
          end)
      }
    end)
    |> Enum.reject(fn group -> Kernel.length(group.legs) == 0 end)
  end

  defp itinerary_to_departure(itinerary) do
    format_datetime(itinerary.start)
  end

  defp itinerary_to_hash(itinerary) do
    itinerary
    |> Map.get(:legs)
    |> Enum.reject(&short_walking_leg?/1)
    |> Enum.map(&combined_leg_to_tuple/1)
    |> :erlang.phash2()
  end

  defp leg_to_string(%Leg{mode: %PersonalDetail{}} = leg) do
    "WALK #{leg.distance} MILES FROM #{leg.from.name} TO #{leg.to.name}"
  end

  defp leg_to_string(%Leg{mode: %{mode: "BUS"} = mode} = leg) do
    "TAKE THE #{mode.route.id} BUS FROM #{leg.from.name} TO #{leg.to.name}"
  end

  defp leg_to_string(%Leg{mode: %{mode: _} = mode} = leg) do
    "TAKE THE #{mode.route.long_name} #{mode.mode} FROM #{leg.from.name} TO #{leg.to.name}"
  end

  defp legs_to_string(legs) do
    legs
    |> Enum.reject(&short_walking_leg?/1)
    |> Enum.map(&leg_to_string/1)
  end

  defp named_position(stop) do
    stop = Stops.Repo.get(stop)

    parent_stop =
      if stop.child? do
        Stops.Repo.get(stop.parent_id)
      else
        nil
      end

    %NamedPosition{
      stop: parent_stop || stop
    }
  end

  defp plan(from, to) do
    TripPlanner.OpenTripPlanner.plan(
      named_position(from),
      named_position(to),
      []
      # [depart_at: DateTime.from_naive!(~N[2024-07-31T08:30:00], "America/New_York")]
    )
  end

  def short_walking_leg?(%Leg{mode: %PersonalDetail{}} = leg) do
    leg.distance <= 0.2
  end

  def short_walking_leg?(_), do: false

  defp unique_leg_to_tuple(%Leg{mode: %PersonalDetail{}} = leg) do
    {"WALK", leg.from.name, leg.to.name}
  end

  defp unique_leg_to_tuple(%Leg{mode: %{route: route}} = leg) do
    {Routes.Route.type_atom(route.type), leg.from.name, leg.to.name}
  end

  defp unique_legs_to_hash(legs) do
    legs
    |> Enum.reject(&amp;short_walking_leg?/1)
    |> Enum.map(&amp;unique_leg_to_tuple/1)
    |> :erlang.phash2()
  end
end

alias ItineraryGroups.Helpers
Application.start(:yamerl)

write_path =
  System.tmp_dir!()
  |> Path.join("itinerary-groups.yml")
  |> IO.inspect()

plans =
  File.cwd!()
  |> Path.join("/livebooks/trips.yml")
  |> YamlElixir.read_from_file!()
  |> Enum.map(fn plan ->
    Helpers.attach_groups_to_plan(plan)
  end)
  |> List.flatten()

yaml = Ymlr.document!(plans)

File.write!(write_path, yaml)