Compare commits
No commits in common. "22036771463c65d21dac680494ce29eb3e2edb9f" and "f89d984623fc9377a5032bb81f30a98685041489" have entirely different histories.
2203677146
...
f89d984623
399
agenda/busy.py
399
agenda/busy.py
|
@ -11,6 +11,20 @@ from . import events_yaml, get_country, travel
|
||||||
from .event import Event
|
from .event import Event
|
||||||
from .types import StrDict, Trip
|
from .types import StrDict, Trip
|
||||||
|
|
||||||
|
NEARBY_BALKAN_COUNTRIES = {
|
||||||
|
"GR",
|
||||||
|
"AL",
|
||||||
|
"XK",
|
||||||
|
"HR",
|
||||||
|
"SI",
|
||||||
|
"MK",
|
||||||
|
"BA",
|
||||||
|
"ME",
|
||||||
|
"RS",
|
||||||
|
"BG",
|
||||||
|
"RO",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def busy_event(e: Event) -> bool:
|
def busy_event(e: Event) -> bool:
|
||||||
"""Busy."""
|
"""Busy."""
|
||||||
|
@ -92,20 +106,58 @@ def _parse_datetime_field(datetime_obj: datetime | date) -> tuple[datetime, date
|
||||||
raise ValueError(f"Invalid datetime format: {datetime_obj}")
|
raise ValueError(f"Invalid datetime format: {datetime_obj}")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_airport_location(
|
||||||
|
airport_code: str, airports: StrDict, uk_airports: set[str], on_trip: bool = False
|
||||||
|
) -> tuple[str | None, pycountry.db.Country | None]:
|
||||||
|
"""Get location from airport code."""
|
||||||
|
if airport_code in uk_airports:
|
||||||
|
if on_trip:
|
||||||
|
# When on a trip, show the actual location even for UK airports
|
||||||
|
airport_info = airports.get(airport_code)
|
||||||
|
if airport_info:
|
||||||
|
location_name = airport_info.get(
|
||||||
|
"city", airport_info.get("name", "London")
|
||||||
|
)
|
||||||
|
return (location_name, get_country("gb"))
|
||||||
|
else:
|
||||||
|
return ("London", get_country("gb"))
|
||||||
|
else:
|
||||||
|
# When not on a trip, UK airports mean home
|
||||||
|
return (None, get_country("gb"))
|
||||||
|
else:
|
||||||
|
# Non-UK airports
|
||||||
|
airport_info = airports.get(airport_code)
|
||||||
|
if airport_info:
|
||||||
|
location_name = airport_info.get(
|
||||||
|
"city", airport_info.get("name", airport_code)
|
||||||
|
)
|
||||||
|
return (location_name, get_country(airport_info.get("country", "gb")))
|
||||||
|
else:
|
||||||
|
return (airport_code, get_country("gb"))
|
||||||
|
|
||||||
|
|
||||||
def _get_accommodation_location(
|
def _get_accommodation_location(
|
||||||
acc: StrDict, on_trip: bool = False
|
acc: StrDict, on_trip: bool = False
|
||||||
) -> tuple[str | None, pycountry.db.Country]:
|
) -> tuple[str | None, pycountry.db.Country | None]:
|
||||||
"""Get location from accommodation data."""
|
"""Get location from accommodation data."""
|
||||||
c = get_country(acc["country"])
|
if acc.get("country") == "gb":
|
||||||
assert c
|
if on_trip:
|
||||||
assert isinstance(acc["location"], str)
|
# When on a trip, show the actual location even for UK accommodations
|
||||||
return (acc["location"] if on_trip else None, c)
|
return (acc.get("location", "London"), get_country("gb"))
|
||||||
|
else:
|
||||||
|
# When not on a trip, UK accommodation means home
|
||||||
|
return (None, get_country("gb"))
|
||||||
|
else:
|
||||||
|
return (acc.get("location", "Unknown"), get_country(acc.get("country", "gb")))
|
||||||
|
|
||||||
|
|
||||||
def _find_most_recent_travel_within_trip(
|
def _find_most_recent_travel_within_trip(
|
||||||
trip: Trip,
|
trip: Trip,
|
||||||
target_date: date,
|
target_date: date,
|
||||||
) -> tuple[str | None, pycountry.db.Country] | None:
|
bookings: list[StrDict],
|
||||||
|
accommodations: list[StrDict],
|
||||||
|
airports: StrDict,
|
||||||
|
) -> tuple[str | None, pycountry.db.Country | None] | None:
|
||||||
"""Find the most recent travel location within a trip."""
|
"""Find the most recent travel location within a trip."""
|
||||||
uk_airports = {"LHR", "LGW", "STN", "LTN", "BRS", "BHX", "MAN", "EDI", "GLA"}
|
uk_airports = {"LHR", "LGW", "STN", "LTN", "BRS", "BHX", "MAN", "EDI", "GLA"}
|
||||||
|
|
||||||
|
@ -114,13 +166,18 @@ def _find_most_recent_travel_within_trip(
|
||||||
trip_most_recent_datetime = None
|
trip_most_recent_datetime = None
|
||||||
|
|
||||||
# Check flights within trip period
|
# Check flights within trip period
|
||||||
for travel_item in trip.travel:
|
for booking in bookings:
|
||||||
if travel_item["type"] == "flight" and "arrive" in travel_item:
|
for flight in booking.get("flights", []):
|
||||||
arrive_datetime, arrive_date = _parse_datetime_field(travel_item["arrive"])
|
if "arrive" in flight:
|
||||||
|
try:
|
||||||
|
arrive_datetime, arrive_date = _parse_datetime_field(
|
||||||
|
flight["arrive"]
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
# Only consider flights within this trip and before target date
|
# Only consider flights within this trip and before target date
|
||||||
if not (trip.start <= arrive_date <= target_date):
|
if trip.start <= arrive_date <= target_date:
|
||||||
continue
|
|
||||||
# Compare both date and time to handle same-day flights correctly
|
# Compare both date and time to handle same-day flights correctly
|
||||||
if (
|
if (
|
||||||
trip_most_recent_date is None
|
trip_most_recent_date is None
|
||||||
|
@ -135,33 +192,13 @@ def _find_most_recent_travel_within_trip(
|
||||||
):
|
):
|
||||||
trip_most_recent_date = arrive_date
|
trip_most_recent_date = arrive_date
|
||||||
trip_most_recent_datetime = arrive_datetime
|
trip_most_recent_datetime = arrive_datetime
|
||||||
destination_airport = travel_item["to"]
|
destination_airport = flight["to"]
|
||||||
assert "to_airport" in travel_item
|
trip_most_recent_location = _get_airport_location(
|
||||||
airport_info = travel_item["to_airport"]
|
destination_airport, airports, uk_airports, on_trip=True
|
||||||
airport_country = airport_info["country"]
|
|
||||||
if airport_country == "gb":
|
|
||||||
if destination_airport in uk_airports:
|
|
||||||
# UK airport while on trip - show actual location
|
|
||||||
location_name = airport_info.get(
|
|
||||||
"city", airport_info.get("name", "London")
|
|
||||||
)
|
|
||||||
trip_most_recent_location = (
|
|
||||||
location_name,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
trip_most_recent_location = (None, get_country("gb"))
|
|
||||||
else:
|
|
||||||
location_name = airport_info.get(
|
|
||||||
"city", airport_info.get("name", destination_airport)
|
|
||||||
)
|
|
||||||
trip_most_recent_location = (
|
|
||||||
location_name,
|
|
||||||
get_country(airport_country),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check accommodations within trip period
|
# Check accommodations within trip period
|
||||||
for acc in trip.accommodation:
|
for acc in accommodations:
|
||||||
if "from" in acc:
|
if "from" in acc:
|
||||||
try:
|
try:
|
||||||
_, acc_date = _parse_datetime_field(acc["from"])
|
_, acc_date = _parse_datetime_field(acc["from"])
|
||||||
|
@ -182,93 +219,6 @@ def _find_most_recent_travel_within_trip(
|
||||||
acc, on_trip=True
|
acc, on_trip=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check trains within trip period
|
|
||||||
for travel_item in trip.travel:
|
|
||||||
if travel_item["type"] == "train":
|
|
||||||
for leg in travel_item.get("legs", []):
|
|
||||||
if "arrive" in leg:
|
|
||||||
try:
|
|
||||||
arrive_datetime, arrive_date = _parse_datetime_field(
|
|
||||||
leg["arrive"]
|
|
||||||
)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Only consider trains within this trip and before target date
|
|
||||||
if trip.start <= arrive_date <= target_date:
|
|
||||||
# Compare both date and time to handle same-day arrivals correctly
|
|
||||||
if (
|
|
||||||
trip_most_recent_date is None
|
|
||||||
or arrive_date > trip_most_recent_date
|
|
||||||
or (
|
|
||||||
arrive_date == trip_most_recent_date
|
|
||||||
and (
|
|
||||||
trip_most_recent_datetime is None
|
|
||||||
or arrive_datetime > trip_most_recent_datetime
|
|
||||||
)
|
|
||||||
)
|
|
||||||
):
|
|
||||||
trip_most_recent_date = arrive_date
|
|
||||||
trip_most_recent_datetime = arrive_datetime
|
|
||||||
# For trains, we can get station info from to_station if available
|
|
||||||
destination = leg.get("to")
|
|
||||||
assert "to_station" in leg
|
|
||||||
station_info = leg["to_station"]
|
|
||||||
station_country = station_info["country"]
|
|
||||||
if station_country == "gb":
|
|
||||||
trip_most_recent_location = (
|
|
||||||
destination,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
trip_most_recent_location = (
|
|
||||||
destination,
|
|
||||||
get_country(station_country),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check ferries within trip period
|
|
||||||
for travel_item in trip.travel:
|
|
||||||
if travel_item["type"] == "ferry" and "arrive" in travel_item:
|
|
||||||
try:
|
|
||||||
arrive_datetime, arrive_date = _parse_datetime_field(
|
|
||||||
travel_item["arrive"]
|
|
||||||
)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Only consider ferries within this trip and before target date
|
|
||||||
if trip.start <= arrive_date <= target_date:
|
|
||||||
# Compare both date and time to handle same-day arrivals correctly
|
|
||||||
if (
|
|
||||||
trip_most_recent_date is None
|
|
||||||
or arrive_date > trip_most_recent_date
|
|
||||||
or (
|
|
||||||
arrive_date == trip_most_recent_date
|
|
||||||
and (
|
|
||||||
trip_most_recent_datetime is None
|
|
||||||
or arrive_datetime > trip_most_recent_datetime
|
|
||||||
)
|
|
||||||
)
|
|
||||||
):
|
|
||||||
trip_most_recent_date = arrive_date
|
|
||||||
trip_most_recent_datetime = arrive_datetime
|
|
||||||
# For ferries, we can get terminal info from to_terminal if available
|
|
||||||
destination = travel_item.get("to")
|
|
||||||
assert "to_terminal" in travel_item
|
|
||||||
terminal_info = travel_item["to_terminal"]
|
|
||||||
terminal_country = terminal_info.get("country", "gb")
|
|
||||||
terminal_city = terminal_info.get("city", destination)
|
|
||||||
if terminal_country == "gb":
|
|
||||||
trip_most_recent_location = (
|
|
||||||
terminal_city,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
trip_most_recent_location = (
|
|
||||||
terminal_city,
|
|
||||||
get_country(terminal_country),
|
|
||||||
)
|
|
||||||
|
|
||||||
return trip_most_recent_location
|
return trip_most_recent_location
|
||||||
|
|
||||||
|
|
||||||
|
@ -300,7 +250,9 @@ def _get_trip_location_by_progression(
|
||||||
|
|
||||||
def _find_most_recent_travel_before_date(
|
def _find_most_recent_travel_before_date(
|
||||||
target_date: date,
|
target_date: date,
|
||||||
trips: list[Trip],
|
bookings: list[StrDict],
|
||||||
|
accommodations: list[StrDict],
|
||||||
|
airports: StrDict,
|
||||||
) -> tuple[str | None, pycountry.db.Country | None] | None:
|
) -> tuple[str | None, pycountry.db.Country | None] | None:
|
||||||
"""Find the most recent travel location before a given date."""
|
"""Find the most recent travel location before a given date."""
|
||||||
uk_airports = {"LHR", "LGW", "STN", "LTN", "BRS", "BHX", "MAN", "EDI", "GLA"}
|
uk_airports = {"LHR", "LGW", "STN", "LTN", "BRS", "BHX", "MAN", "EDI", "GLA"}
|
||||||
|
@ -309,14 +261,13 @@ def _find_most_recent_travel_before_date(
|
||||||
most_recent_date = None
|
most_recent_date = None
|
||||||
most_recent_datetime = None
|
most_recent_datetime = None
|
||||||
|
|
||||||
# Check all travel across all trips
|
|
||||||
for trip in trips:
|
|
||||||
# Check flights
|
# Check flights
|
||||||
for travel_item in trip.travel:
|
for booking in bookings:
|
||||||
if travel_item["type"] == "flight" and "arrive" in travel_item:
|
for flight in booking.get("flights", []):
|
||||||
|
if "arrive" in flight:
|
||||||
try:
|
try:
|
||||||
arrive_datetime, arrive_date = _parse_datetime_field(
|
arrive_datetime, arrive_date = _parse_datetime_field(
|
||||||
travel_item["arrive"]
|
flight["arrive"]
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
@ -336,151 +287,13 @@ def _find_most_recent_travel_before_date(
|
||||||
):
|
):
|
||||||
most_recent_date = arrive_date
|
most_recent_date = arrive_date
|
||||||
most_recent_datetime = arrive_datetime
|
most_recent_datetime = arrive_datetime
|
||||||
destination_airport = travel_item["to"]
|
destination_airport = flight["to"]
|
||||||
# For flights, determine if we're "on trip" based on whether this is within any trip period
|
most_recent_location = _get_airport_location(
|
||||||
on_trip = any(
|
destination_airport, airports, uk_airports, on_trip=False
|
||||||
t.start <= arrive_date <= (t.end or t.start) for t in trips
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if "to_airport" in travel_item:
|
|
||||||
airport_info = travel_item["to_airport"]
|
|
||||||
airport_country = airport_info.get("country", "gb")
|
|
||||||
if airport_country == "gb":
|
|
||||||
if not on_trip:
|
|
||||||
# When not on a trip, UK airports mean home
|
|
||||||
most_recent_location = (None, get_country("gb"))
|
|
||||||
else:
|
|
||||||
# When on a trip, show the actual location even for UK airports
|
|
||||||
location_name = airport_info.get(
|
|
||||||
"city", airport_info.get("name", "London")
|
|
||||||
)
|
|
||||||
most_recent_location = (
|
|
||||||
location_name,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
location_name = airport_info.get(
|
|
||||||
"city",
|
|
||||||
airport_info.get("name", destination_airport),
|
|
||||||
)
|
|
||||||
most_recent_location = (
|
|
||||||
location_name,
|
|
||||||
get_country(airport_country),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
most_recent_location = (
|
|
||||||
destination_airport,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check trains
|
|
||||||
elif travel_item["type"] == "train":
|
|
||||||
for leg in travel_item.get("legs", []):
|
|
||||||
if "arrive" in leg:
|
|
||||||
try:
|
|
||||||
arrive_datetime, arrive_date = _parse_datetime_field(
|
|
||||||
leg["arrive"]
|
|
||||||
)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if arrive_date <= target_date:
|
|
||||||
# Compare both date and time to handle same-day arrivals correctly
|
|
||||||
if (
|
|
||||||
most_recent_date is None
|
|
||||||
or arrive_date > most_recent_date
|
|
||||||
or (
|
|
||||||
arrive_date == most_recent_date
|
|
||||||
and (
|
|
||||||
most_recent_datetime is None
|
|
||||||
or arrive_datetime > most_recent_datetime
|
|
||||||
)
|
|
||||||
)
|
|
||||||
):
|
|
||||||
most_recent_date = arrive_date
|
|
||||||
most_recent_datetime = arrive_datetime
|
|
||||||
destination = leg.get("to")
|
|
||||||
on_trip = any(
|
|
||||||
t.start <= arrive_date <= (t.end or t.start)
|
|
||||||
for t in trips
|
|
||||||
)
|
|
||||||
|
|
||||||
if "to_station" in leg:
|
|
||||||
station_info = leg["to_station"]
|
|
||||||
station_country = station_info.get("country", "gb")
|
|
||||||
if station_country == "gb":
|
|
||||||
if not on_trip:
|
|
||||||
most_recent_location = (
|
|
||||||
None,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
most_recent_location = (
|
|
||||||
destination,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
most_recent_location = (
|
|
||||||
destination,
|
|
||||||
get_country(station_country),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
most_recent_location = (
|
|
||||||
destination,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check ferries
|
|
||||||
elif travel_item["type"] == "ferry" and "arrive" in travel_item:
|
|
||||||
try:
|
|
||||||
arrive_datetime, arrive_date = _parse_datetime_field(
|
|
||||||
travel_item["arrive"]
|
|
||||||
)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if arrive_date <= target_date:
|
|
||||||
# Compare both date and time to handle same-day arrivals correctly
|
|
||||||
if (
|
|
||||||
most_recent_date is None
|
|
||||||
or arrive_date > most_recent_date
|
|
||||||
or (
|
|
||||||
arrive_date == most_recent_date
|
|
||||||
and (
|
|
||||||
most_recent_datetime is None
|
|
||||||
or arrive_datetime > most_recent_datetime
|
|
||||||
)
|
|
||||||
)
|
|
||||||
):
|
|
||||||
most_recent_date = arrive_date
|
|
||||||
most_recent_datetime = arrive_datetime
|
|
||||||
destination = travel_item.get("to")
|
|
||||||
on_trip = any(
|
|
||||||
t.start <= arrive_date <= (t.end or t.start) for t in trips
|
|
||||||
)
|
|
||||||
|
|
||||||
if "to_terminal" in travel_item:
|
|
||||||
terminal_info = travel_item["to_terminal"]
|
|
||||||
terminal_country = terminal_info.get("country", "gb")
|
|
||||||
terminal_city = terminal_info.get("city", destination)
|
|
||||||
if terminal_country == "gb":
|
|
||||||
if not on_trip:
|
|
||||||
most_recent_location = (None, get_country("gb"))
|
|
||||||
else:
|
|
||||||
most_recent_location = (
|
|
||||||
terminal_city,
|
|
||||||
get_country("gb"),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
most_recent_location = (
|
|
||||||
terminal_city,
|
|
||||||
get_country(terminal_country),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
most_recent_location = (destination, get_country("gb"))
|
|
||||||
|
|
||||||
# Check accommodation - only override if accommodation is more recent
|
# Check accommodation - only override if accommodation is more recent
|
||||||
for acc in trip.accommodation:
|
for acc in accommodations:
|
||||||
if "from" in acc:
|
if "from" in acc:
|
||||||
try:
|
try:
|
||||||
_, acc_date = _parse_datetime_field(acc["from"])
|
_, acc_date = _parse_datetime_field(acc["from"])
|
||||||
|
@ -491,11 +304,8 @@ def _find_most_recent_travel_before_date(
|
||||||
# Only update if this accommodation is more recent than existing result
|
# Only update if this accommodation is more recent than existing result
|
||||||
if most_recent_date is None or acc_date > most_recent_date:
|
if most_recent_date is None or acc_date > most_recent_date:
|
||||||
most_recent_date = acc_date
|
most_recent_date = acc_date
|
||||||
on_trip = any(
|
|
||||||
t.start <= acc_date <= (t.end or t.start) for t in trips
|
|
||||||
)
|
|
||||||
most_recent_location = _get_accommodation_location(
|
most_recent_location = _get_accommodation_location(
|
||||||
acc, on_trip=on_trip
|
acc, on_trip=False
|
||||||
)
|
)
|
||||||
|
|
||||||
return most_recent_location
|
return most_recent_location
|
||||||
|
@ -517,22 +327,36 @@ def _check_return_home_heuristic(
|
||||||
if hasattr(final_country, "alpha_2") and final_country.alpha_2 == "GB":
|
if hasattr(final_country, "alpha_2") and final_country.alpha_2 == "GB":
|
||||||
return (None, get_country("gb"))
|
return (None, get_country("gb"))
|
||||||
|
|
||||||
|
# For short trips to nearby countries or international trips
|
||||||
|
# (ended >=1 day ago), assume returned home if no subsequent travel data
|
||||||
|
if days_since_trip >= 1 and (
|
||||||
|
# European countries (close by rail/ferry)
|
||||||
|
final_alpha_2 in {"BE", "NL", "FR", "DE", "CH", "AT", "IT", "ES"}
|
||||||
|
# Nearby Balkan countries
|
||||||
|
or final_alpha_2 in NEARBY_BALKAN_COUNTRIES
|
||||||
|
# International trips (assume return home after trip ends)
|
||||||
|
or final_alpha_2
|
||||||
|
in {"US", "CA", "IN", "JP", "CN", "AU", "NZ", "BR", "AR", "ZA"}
|
||||||
|
):
|
||||||
|
return (None, get_country("gb"))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_location_for_date(
|
def get_location_for_date(
|
||||||
target_date: date,
|
target_date: date,
|
||||||
trips: list[Trip],
|
trips: list[Trip],
|
||||||
|
bookings: list[StrDict],
|
||||||
|
accommodations: list[StrDict],
|
||||||
|
airports: StrDict,
|
||||||
) -> tuple[str | None, pycountry.db.Country | None]:
|
) -> tuple[str | None, pycountry.db.Country | None]:
|
||||||
"""Get location (city, country) for a specific date using travel history."""
|
"""Get location (city, country) for a specific date using travel history."""
|
||||||
# First check if currently on a trip
|
# First check if currently on a trip
|
||||||
for trip in trips:
|
for trip in trips:
|
||||||
if not (trip.start <= target_date <= (trip.end or trip.start)):
|
if trip.start <= target_date <= (trip.end or trip.start):
|
||||||
continue
|
# For trips, find the most recent flight or accommodation within the trip period
|
||||||
# For trips, find the most recent travel within the trip period
|
|
||||||
trip_location = _find_most_recent_travel_within_trip(
|
trip_location = _find_most_recent_travel_within_trip(
|
||||||
trip,
|
trip, target_date, bookings, accommodations, airports
|
||||||
target_date,
|
|
||||||
)
|
)
|
||||||
if trip_location:
|
if trip_location:
|
||||||
return trip_location
|
return trip_location
|
||||||
|
@ -542,8 +366,10 @@ def get_location_for_date(
|
||||||
if progression_location:
|
if progression_location:
|
||||||
return progression_location
|
return progression_location
|
||||||
|
|
||||||
# Find most recent travel before this date
|
# Find most recent flight or accommodation before this date
|
||||||
recent_travel = _find_most_recent_travel_before_date(target_date, trips)
|
recent_travel = _find_most_recent_travel_before_date(
|
||||||
|
target_date, bookings, accommodations, airports
|
||||||
|
)
|
||||||
|
|
||||||
# Check for recent trips that have ended - prioritize this over individual travel data
|
# Check for recent trips that have ended - prioritize this over individual travel data
|
||||||
# This handles cases where you're traveling home after a trip (e.g. stopovers, connections)
|
# This handles cases where you're traveling home after a trip (e.g. stopovers, connections)
|
||||||
|
@ -570,6 +396,11 @@ def weekends(
|
||||||
else:
|
else:
|
||||||
start_date = start + timedelta(days=(5 - weekday))
|
start_date = start + timedelta(days=(5 - weekday))
|
||||||
|
|
||||||
|
# Parse YAML files once for all location lookups
|
||||||
|
bookings = travel.parse_yaml("flights", data_dir)
|
||||||
|
accommodations = travel.parse_yaml("accommodation", data_dir)
|
||||||
|
airports = travel.parse_yaml("airports", data_dir)
|
||||||
|
|
||||||
weekends_info = []
|
weekends_info = []
|
||||||
for i in range(52):
|
for i in range(52):
|
||||||
saturday = start_date + timedelta(weeks=i)
|
saturday = start_date + timedelta(weeks=i)
|
||||||
|
@ -587,12 +418,10 @@ def weekends(
|
||||||
]
|
]
|
||||||
|
|
||||||
saturday_location = get_location_for_date(
|
saturday_location = get_location_for_date(
|
||||||
saturday,
|
saturday, trips, bookings, accommodations, airports
|
||||||
trips,
|
|
||||||
)
|
)
|
||||||
sunday_location = get_location_for_date(
|
sunday_location = get_location_for_date(
|
||||||
sunday,
|
sunday, trips, bookings, accommodations, airports
|
||||||
trips,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
weekends_info.append(
|
weekends_info.append(
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Debug: {{ trip.title }} ({{ trip.start }}) - Edward Betts{% endblock %}
|
|
||||||
|
|
||||||
{% block style %}
|
|
||||||
<style>
|
|
||||||
.json-display {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
padding: 1rem;
|
|
||||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
overflow-x: auto;
|
|
||||||
max-height: 80vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-header {
|
|
||||||
background-color: #fff3cd;
|
|
||||||
border: 1px solid #ffeaa7;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-header h1 {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-header p {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-header .btn {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Basic JSON syntax highlighting using CSS */
|
|
||||||
.json-display .json-key {
|
|
||||||
color: #d73a49;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-display .json-string {
|
|
||||||
color: #032f62;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-display .json-number {
|
|
||||||
color: #005cc5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-display .json-boolean {
|
|
||||||
color: #e36209;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-display .json-null {
|
|
||||||
color: #6f42c1;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="debug-header">
|
|
||||||
<h1>🐛 Trip Debug Information</h1>
|
|
||||||
<p>Raw trip object data for: <strong>{{ trip.title }}</strong></p>
|
|
||||||
<a href="{{ url_for('trip_page', start=start) }}" class="btn btn-primary">← Back to Trip Page</a>
|
|
||||||
<button onclick="copyToClipboard()" class="btn btn-secondary">📋 Copy JSON</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<h3>Trip Object (JSON)</h3>
|
|
||||||
<div class="json-display" id="jsonDisplay">{{ trip_json }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
<script>
|
|
||||||
function copyToClipboard() {
|
|
||||||
const jsonText = document.getElementById('jsonDisplay').textContent;
|
|
||||||
navigator.clipboard.writeText(jsonText).then(function() {
|
|
||||||
// Show a temporary notification
|
|
||||||
const btn = event.target;
|
|
||||||
const originalText = btn.textContent;
|
|
||||||
btn.textContent = '✅ Copied!';
|
|
||||||
btn.classList.remove('btn-secondary');
|
|
||||||
btn.classList.add('btn-success');
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
btn.textContent = originalText;
|
|
||||||
btn.classList.remove('btn-success');
|
|
||||||
btn.classList.add('btn-secondary');
|
|
||||||
}, 2000);
|
|
||||||
}).catch(function(err) {
|
|
||||||
console.error('Failed to copy: ', err);
|
|
||||||
alert('Failed to copy to clipboard');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple JSON syntax highlighting
|
|
||||||
function highlightJSON() {
|
|
||||||
const display = document.getElementById('jsonDisplay');
|
|
||||||
let content = display.textContent;
|
|
||||||
|
|
||||||
// Highlight different JSON elements
|
|
||||||
content = content.replace(/"([^"]+)":/g, '<span class="json-key">"$1":</span>');
|
|
||||||
content = content.replace(/"([^"]*)"(?=\s*[,\]\}])/g, '<span class="json-string">"$1"</span>');
|
|
||||||
content = content.replace(/\b(\d+\.?\d*)\b/g, '<span class="json-number">$1</span>');
|
|
||||||
content = content.replace(/\b(true|false)\b/g, '<span class="json-boolean">$1</span>');
|
|
||||||
content = content.replace(/\bnull\b/g, '<span class="json-null">null</span>');
|
|
||||||
|
|
||||||
display.innerHTML = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply highlighting when page loads
|
|
||||||
document.addEventListener('DOMContentLoaded', highlightJSON);
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -73,6 +73,9 @@ def test_specific_home_dates(travel_data):
|
||||||
location = agenda.busy.get_location_for_date(
|
location = agenda.busy.get_location_for_date(
|
||||||
test_date,
|
test_date,
|
||||||
trips,
|
trips,
|
||||||
|
travel_data["bookings"],
|
||||||
|
travel_data["accommodations"],
|
||||||
|
travel_data["airports"],
|
||||||
)
|
)
|
||||||
assert not location[
|
assert not location[
|
||||||
0
|
0
|
||||||
|
@ -91,6 +94,9 @@ def test_specific_away_dates(travel_data):
|
||||||
location = agenda.busy.get_location_for_date(
|
location = agenda.busy.get_location_for_date(
|
||||||
test_date,
|
test_date,
|
||||||
trips,
|
trips,
|
||||||
|
travel_data["bookings"],
|
||||||
|
travel_data["accommodations"],
|
||||||
|
travel_data["airports"],
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
location[0] == expected_city
|
location[0] == expected_city
|
||||||
|
@ -105,6 +111,9 @@ def test_get_location_for_date_basic(travel_data):
|
||||||
location = agenda.busy.get_location_for_date(
|
location = agenda.busy.get_location_for_date(
|
||||||
test_date,
|
test_date,
|
||||||
trips,
|
trips,
|
||||||
|
travel_data["bookings"],
|
||||||
|
travel_data["accommodations"],
|
||||||
|
travel_data["airports"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Should return a tuple with (city|None, country)
|
# Should return a tuple with (city|None, country)
|
||||||
|
|
78
web_view.py
78
web_view.py
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
import decimal
|
import decimal
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
|
||||||
import operator
|
import operator
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
|
@ -259,9 +258,7 @@ async def weekends() -> str:
|
||||||
|
|
||||||
trip_list = agenda.trip.build_trip_list()
|
trip_list = agenda.trip.build_trip_list()
|
||||||
busy_events = agenda.busy.get_busy_events(start, app.config, trip_list)
|
busy_events = agenda.busy.get_busy_events(start, app.config, trip_list)
|
||||||
weekends = agenda.busy.weekends(
|
weekends = agenda.busy.weekends(start, busy_events, trip_list, app.config["PERSONAL_DATA"])
|
||||||
start, busy_events, trip_list, app.config["PERSONAL_DATA"]
|
|
||||||
)
|
|
||||||
return flask.render_template(
|
return flask.render_template(
|
||||||
"weekends.html",
|
"weekends.html",
|
||||||
items=weekends,
|
items=weekends,
|
||||||
|
@ -589,79 +586,6 @@ def trip_page(start: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/trip/<start>/debug")
|
|
||||||
def trip_debug_page(start: str) -> str:
|
|
||||||
"""Trip debug page showing raw trip object data."""
|
|
||||||
|
|
||||||
if not flask.g.user.is_authenticated:
|
|
||||||
flask.abort(401)
|
|
||||||
|
|
||||||
route_distances = agenda.travel.load_route_distances(app.config["DATA_DIR"])
|
|
||||||
trip_list = get_trip_list(route_distances)
|
|
||||||
|
|
||||||
prev_trip, trip, next_trip = get_prev_current_and_next_trip(start, trip_list)
|
|
||||||
if not trip:
|
|
||||||
flask.abort(404)
|
|
||||||
|
|
||||||
# Add Schengen compliance information
|
|
||||||
trip = agenda.trip_schengen.add_schengen_compliance_to_trip(trip)
|
|
||||||
|
|
||||||
# Convert trip object to dictionary for display
|
|
||||||
trip_dict = {
|
|
||||||
"start": trip.start.isoformat(),
|
|
||||||
"name": trip.name,
|
|
||||||
"private": trip.private,
|
|
||||||
"travel": trip.travel,
|
|
||||||
"accommodation": trip.accommodation,
|
|
||||||
"conferences": trip.conferences,
|
|
||||||
"events": trip.events,
|
|
||||||
"flight_bookings": trip.flight_bookings,
|
|
||||||
"computed_properties": {
|
|
||||||
"title": trip.title,
|
|
||||||
"end": trip.end.isoformat() if trip.end else None,
|
|
||||||
"countries": [
|
|
||||||
{"name": c.name, "alpha_2": c.alpha_2, "flag": c.flag}
|
|
||||||
for c in trip.countries
|
|
||||||
],
|
|
||||||
"locations": [
|
|
||||||
{
|
|
||||||
"location": loc,
|
|
||||||
"country": {"name": country.name, "alpha_2": country.alpha_2},
|
|
||||||
}
|
|
||||||
for loc, country in trip.locations()
|
|
||||||
],
|
|
||||||
"total_distance": trip.total_distance(),
|
|
||||||
"total_co2_kg": trip.total_co2_kg(),
|
|
||||||
"distances_by_transport_type": trip.distances_by_transport_type(),
|
|
||||||
"co2_by_transport_type": trip.co2_by_transport_type(),
|
|
||||||
},
|
|
||||||
"schengen_compliance": (
|
|
||||||
{
|
|
||||||
"total_days_used": trip.schengen_compliance.total_days_used,
|
|
||||||
"days_remaining": trip.schengen_compliance.days_remaining,
|
|
||||||
"is_compliant": trip.schengen_compliance.is_compliant,
|
|
||||||
"current_180_day_period": [
|
|
||||||
trip.schengen_compliance.current_180_day_period[0].isoformat(),
|
|
||||||
trip.schengen_compliance.current_180_day_period[1].isoformat(),
|
|
||||||
],
|
|
||||||
"days_over_limit": trip.schengen_compliance.days_over_limit,
|
|
||||||
}
|
|
||||||
if trip.schengen_compliance
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Convert to JSON for pretty printing
|
|
||||||
trip_json = json.dumps(trip_dict, indent=2, default=str)
|
|
||||||
|
|
||||||
return flask.render_template(
|
|
||||||
"trip_debug.html",
|
|
||||||
trip=trip,
|
|
||||||
trip_json=trip_json,
|
|
||||||
start=start,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/holidays")
|
@app.route("/holidays")
|
||||||
def holiday_list() -> str:
|
def holiday_list() -> str:
|
||||||
"""List of holidays."""
|
"""List of holidays."""
|
||||||
|
|
Loading…
Reference in a new issue