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

Flight data explorer

api-exploration.livemd

Flight data explorer

Mix.install([
  {:tesla, "~> 1.7"},
  {:sweet_xml, "~> 0.7.3"},
  {:castore, "~> 1.0"},
  {:plug_cowboy, "~> 2.6"}
])

Requests

import SweetXml

ba =
  "https://gist.githubusercontent.com/kanmaniselvan/bb11edf031e254977b210c480a0bd89a/raw/ea9bcb65ba4bb2304580d6202ece88aee38540f8/ba_response_sample.xml"

klm =
  "https://gist.githubusercontent.com/kanmaniselvan/bb11edf031e254977b210c480a0bd89a/raw/ea9bcb65ba4bb2304580d6202ece88aee38540f8/afklm_response_sample.xml"

Simple transformer helper

defmodule FlightsHelper do
  def add_provider(
        %{currency: currency, price: price, offer_id: offer_id, segment_ids: segment_ids},
        provider
      ) do
    Task.async(fn ->
      %{
        provider: provider |> to_string(),
        currency: currency |> to_string(),
        price: price |> to_string() |> String.to_float(),
        offer_id: offer_id |> to_string(),
        segment_ids: segment_ids |> Enum.uniq()
      }
    end)
  end
end

Save responses

ba_response =
  ba
  |> Tesla.get!()
  |> Map.get(:body)

# Air France
klm_response =
  klm
  |> Tesla.get!()
  |> Map.get(:body)

Extract offers and flight segments

# British Airways
ba_prices =
  ba_response
  |> xpath(
    ~x"//AirlineOffers/AirlineOffer"l,
    currency: ~x"./TotalPrice/SimpleCurrencyPrice/@Code",
    price: ~x"./TotalPrice/SimpleCurrencyPrice/text()",
    offer_id: ~x"./OfferID/text()",
    segment_ids: ~x"./PricedOffer/Associations/ApplicableFlight/FlightSegmentReference/@ref"l
  )
  |> Enum.map(&FlightsHelper.add_provider(&1, "ba"))
  |> Task.await_many()
# Air France
klm_prices =
  klm_response
  |> xpath(
    ~x"//ns2:OffersGroup/ns2:CarrierOffers/ns2:Offer"l,
    currency: ~x"./ns2:OfferItem/ns2:Price/ns2:TotalAmount/@CurCode",
    price: ~x"./ns2:OfferItem/ns2:Price/ns2:TotalAmount/text()",
    offer_id: ~x"./ns2:OfferID/text()",
    segment_ids: ~x"./ns2:OfferItem/ns2:FareDetail/ns2:FareComponent/ns2:PaxSegmentRefID/text()"l
  )
  |> Enum.map(&FlightsHelper.add_provider(&1, "klm"))
  |> Task.await_many()

Extract flight segments

ba_segments =
  ba_response
  |> xpath(
    ~x"//DataLists/FlightSegmentList/FlightSegment"l,
    segment_id: ~x"./@SegmentKey",
    origin: ~x"./Departure/AirportCode/text()",
    destination: ~x"./Arrival/AirportCode/text()",
    departure_date: ~x"./Departure/Date/text()"
  )
klm_segments =
  klm_response
  |> xpath(
    ~x"//ns2:DataLists/ns2:PaxSegmentList/ns2:PaxSegment"l,
    segment_id: ~x"./ns2:PaxSegmentID/text()",
    origin: ~x"./ns2:Dep/ns2:IATALocationCode/text()",
    destination: ~x"./ns2:Arrival/ns2:IATALocationCode/text()",
    departure_date: ~x"./ns2:Dep/ns2:AircraftScheduledDateTime/text()"
  )
all_segments = ba_segments ++ klm_segments

Next is to create a map using segment id => segment

segment_mapping =
  all_segments
  |> Enum.group_by(fn %{segment_id: segment_id} ->
    segment_id
  end)
all_offers =
  (ba_prices ++ klm_prices)
  |> Enum.sort_by(fn %{price: price} -> price end, :asc)
all_offers
|> Enum.map(fn %{segment_ids: segment_ids} ->
  segment_ids
  |> Enum.map(fn sid ->
    segment_mapping |> Map.get(sid)
  end)
end)

Time to search

# sample query string - origin=BER&destination=LHR&departureDate=2019-07-17
origin = "MUC"
dest = "LCY"
date = "2021-09-28"

filtered_segments =
  all_segments
  |> Enum.filter(fn segment ->
    to_string(segment.origin) == origin && to_string(segment.destination) == dest
  end)

results =
  if not is_nil(date) do
    filtered_segments
    |> Enum.filter(fn segment ->
      segment.departure_date
      |> to_string()
      |> String.starts_with?(date)
    end)
    |> Enum.map(& &1.segment_id)
  else
    filtered_segments
    |> Enum.map(& &1.segment_id)
  end
sorted_results =
  results
  |> Enum.map(fn segment_id ->
    all_offers
    |> Enum.filter(fn offer ->
      offer.segment_ids |> Enum.member?(segment_id |> to_charlist())
    end)
  end)
  |> List.flatten()
  |> Enum.group_by(fn %{price: price} ->
    price
  end)

key =
  sorted_results
  |> Map.keys()
  |> Enum.at(0)

sorted_results |> Map.get(key)
# Enum.at(sorted_results, 0)

Plug.Conn.Query experiments

alias Plug.Conn.Query
Query.decode("origin=BER&destination=LHR&departureDate=2019-07-17")
defmodule DateMatcher do
  def match(
        <>
      ) do
    IO.puts(year)
    IO.puts(month)
    IO.puts(day)
  end

  def match(_), do: :error
end
DateMatcher.match("2022-07-17")
IO.puts("------")
DateMatcher.match("2019-07-17T00:00:00")
IO.puts("------")
DateMatcher.match(nil)