diff --git a/agenda/travel.py b/agenda/travel.py index 51a7810..440e6af 100644 --- a/agenda/travel.py +++ b/agenda/travel.py @@ -60,11 +60,18 @@ def parse_yaml(travel_type: str, data_dir: str) -> TravelList: def get_flights(data_dir: str) -> list[Event]: """Get travel events.""" bookings = parse_yaml("flights", data_dir) + airlines = parse_yaml("airlines", data_dir) + by_iata = {a["iata"]: a for a in airlines} events = [] for booking in bookings: for item in booking["flights"]: if not item["depart"].date(): continue + airline = by_iata[item["airline"]] + item["airline_code"] = airline[ + "iata" if not airline.get("flight_number_prefer_icao") else "icao" + ] + e = Event( date=item["depart"], end_date=item.get("arrive"), @@ -95,7 +102,7 @@ def get_trains(data_dir: str) -> list[Event]: def flight_number(flight: Leg) -> str: """Flight number.""" - airline_code = flight["airline"] + airline_code = flight["airline_code"] # make sure this is the airline code, not the airline name assert " " not in airline_code and not any(c.islower() for c in airline_code) diff --git a/agenda/trip.py b/agenda/trip.py index bf110b4..90e9997 100644 --- a/agenda/trip.py +++ b/agenda/trip.py @@ -13,6 +13,14 @@ from agenda import travel from agenda.types import StrDict, Trip +class Airline(typing.TypedDict, total=False): + """Airline.""" + + iata: str + icao: str + name: str + + def load_travel(travel_type: str, plural: str, data_dir: str) -> list[StrDict]: """Read flight and train journeys.""" items = travel.parse_yaml(plural, data_dir) @@ -85,7 +93,7 @@ def depart_datetime(item: StrDict) -> datetime: def process_flight( - flight: StrDict, iata: dict[str, str], airports: list[StrDict] + flight: StrDict, by_iata: dict[str, Airline], airports: list[StrDict] ) -> None: """Add airport detail, airline name and distance to flight.""" if flight["from"] in airports: @@ -93,7 +101,11 @@ def process_flight( if flight["to"] in airports: flight["to_airport"] = airports[flight["to"]] if "airline" in flight: - flight["airline_name"] = iata.get(flight["airline"], "[unknown]") + airline = by_iata[flight["airline"]] + flight["airline_detail"] = airline + flight["airline_code"] = airline[ + "iata" if not airline.get("flight_number_prefer_icao") else "icao" + ] flight["distance"] = travel.flight_distance(flight) @@ -102,11 +114,11 @@ def load_flight_bookings(data_dir: str) -> list[StrDict]: """Load flight bookings.""" bookings = load_travel("flight", "flights", data_dir) airlines = yaml.safe_load(open(os.path.join(data_dir, "airlines.yaml"))) - iata = {a["iata"]: a["name"] for a in airlines} + by_iata = {a["iata"]: a for a in airlines} airports = travel.parse_yaml("airports", data_dir) for booking in bookings: for flight in booking["flights"]: - process_flight(flight, iata, airports) + process_flight(flight, by_iata, airports) return bookings diff --git a/templates/macros.html b/templates/macros.html index fad26d3..a7b67a7 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -118,7 +118,7 @@ {% endfor %} {% for item in booking.flights %} - {% set full_flight_number = item.airline + item.flight_number %} + {% set full_flight_number = item.airline_code + item.flight_number %} {% set radarbox_url = "https://www.radarbox.com/data/flights/" + full_flight_number %} <div class="grid-item"></div> <div class="grid-item"></div> @@ -147,7 +147,7 @@ {% endmacro %} {% macro flight_row(item) %} - {% set full_flight_number = item.airline + item.flight_number %} + {% set full_flight_number = item.airline_code + item.flight_number %} {% set radarbox_url = "https://www.radarbox.com/data/flights/" + full_flight_number %} <div class="grid-item text-end">{{ item.depart.strftime("%a, %d %b %Y") }}</div> <div class="grid-item">{{ item.from }} → {{ item.to }}</div> @@ -373,10 +373,10 @@ – {{ e.end_loc }} {{ flag(trip, e.end_country.flag) }} {% if e.element_type == "flight" %} - {% set full_flight_number = e.detail.airline + e.detail.flight_number %} + {% set full_flight_number = e.detail.airline_code + e.detail.flight_number %} {% set radarbox_url = "https://www.radarbox.com/data/flights/" + full_flight_number %} <span class="text-nowrap"><strong>airline:</strong> {{ e.detail.airline_name }}</span> - <span class="text-nowrap"><strong>flight number:</strong> {{ e.detail.airline }}{{ e.detail.flight_number }}</span> + <span class="text-nowrap"><strong>flight number:</strong> {{ full_flight_number }}</span> {% if e.detail.duration %} <span class="text-nowrap"><strong>duration:</strong> {{ e.detail.duration }}</span> {% endif %} @@ -387,7 +387,7 @@ {% endif %} {% if e.element_type == "flight" %} <a href="https://www.flightradar24.com/data/flights/{{ full_flight_number | lower }}">flightradar24</a> - | <a href="https://uk.flightaware.com/live/flight/{{ full_flight_number | replace("U2", "EZY") }}">FlightAware</a> + | <a href="https://uk.flightaware.com/live/flight/{{ full_flight_number }}">FlightAware</a> | <a href="{{ radarbox_url }}">radarbox</a> {% endif %} </div> diff --git a/validate_yaml.py b/validate_yaml.py index cfb5009..ccbdd0d 100755 --- a/validate_yaml.py +++ b/validate_yaml.py @@ -162,9 +162,19 @@ def check_airlines() -> list[agenda.types.StrDict]: airlines = agenda.travel.parse_yaml("airlines", data_dir) print(len(airlines), "airlines") for airline in airlines: - assert airline.keys() == {"icao", "iata", "name"} - assert len(airline["icao"]) == 3 - assert len(airline["iata"]) == 2 + try: + keys = set(airline.keys()) + keys.discard("flight_number_prefer_icao") + assert keys == {"icao", "iata", "name"} + iata, icao = airline["iata"], airline["icao"] + assert iata[0].isupper() and iata[1].isupper() or iata[1].isdigit() + assert icao.isupper() + assert len(iata) == 2 and len(icao) == 3 + if "flight_number_prefer_icao" in airline: + assert isinstance(airline["flight_number_prefer_icao"], bool) + except AssertionError: + print(yaml.dump([airline])) + raise return airlines