From fcd0e9c1cdd2f46c6bbc329ed8b90e7aa1bfc4bc Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Wed, 16 Jul 2025 06:38:37 +0200 Subject: [PATCH 1/3] Fix European trip return heuristic for weekend location tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adjust European short trip heuristic from >3 days to >1 day to correctly detect when user has returned home from European trips. This fixes the April 29-30, 2023 case where the location incorrectly showed "Sankt Georg, Hamburg" instead of "Bristol" when the user was free (no events scheduled) after the foss-north trip ended on April 27. The previous logic required more than 3 days to pass before assuming return home from European countries, but for short European trips by rail/ferry, users typically return within 1-2 days. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- agenda/busy.py | 4 +-- tests/test_busy.py | 64 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/agenda/busy.py b/agenda/busy.py index dedeab6..f0751ff 100644 --- a/agenda/busy.py +++ b/agenda/busy.py @@ -294,11 +294,11 @@ def get_location_for_date( ): return ("Bristol", get_country("gb")) - # For short European trips (ended >3 days ago), assume returned home + # For short European trips (ended >1 day ago), assume returned home # if no subsequent travel data shows you're still abroad days_since_trip = (target_date - trip.end).days if ( - days_since_trip > 3 + days_since_trip > 1 and hasattr(final_country, "alpha_2") and final_country.alpha_2 in {"BE", "NL", "FR", "DE", "CH", "AT", "IT", "ES"} diff --git a/tests/test_busy.py b/tests/test_busy.py index c4a6424..1e3a03e 100644 --- a/tests/test_busy.py +++ b/tests/test_busy.py @@ -1,4 +1,4 @@ -from datetime import date, datetime +from datetime import date, datetime, timedelta import agenda.travel as travel import agenda.trip @@ -9,28 +9,57 @@ def test_get_location_for_date() -> None: app.config["SERVER_NAME"] = "test" with app.app_context(): today = datetime.now().date() - start = date(today.year, 1, 1) - trips = [ - t - for t in agenda.trip.build_trip_list() - if t.start - in [ - date(2023, 2, 2), - date(2023, 9, 8), - date(2023, 11, 14), - date(2025, 2, 9), - date(2025, 7, 4), - ] - ] - assert len(trips) == 5 - + start = date(2023, 1, 1) + trips = agenda.trip.build_trip_list() + data_dir = app.config["PERSONAL_DATA"] + busy_events = agenda.busy.get_busy_events(start, app.config, trips) + weekends = agenda.busy.weekends(start, busy_events, trips, data_dir) + + # Debug the specific failing case + for weekend in weekends: + weekend_date = weekend["date"] + if weekend_date == date(2023, 4, 29): + print(f"\nDEBUG April 29-30, 2023:") + print(f"Saturday {weekend_date}: location={weekend['saturday_location']}, events={weekend['saturday']}") + print(f"Sunday {weekend_date + timedelta(days=1)}: location={weekend['sunday_location']}, events={weekend['sunday']}") + + # Find trips around this time + print("\nTrips around this time:") + for trip in trips: + if trip.start and abs((trip.start - weekend_date).days) < 60: + print(f" {trip.start} to {trip.end}: {trip.title}") + + for weekend in weekends: + for day in "saturday", "sunday": + if not (weekend[day + "_location"][0] == "Bristol" or bool(weekend[day])): + weekend_date = weekend["date"] + day_date = weekend_date if day == "saturday" else weekend_date + timedelta(days=1) + print(f"FAILING: {day_date} ({day}): location='{weekend[day + '_location'][0]}', events={len(weekend[day])}") + + assert weekend[day + "_location"][0] == "Bristol" or bool(weekend[day]) + # Parse YAML files once for the test bookings = travel.parse_yaml("flights", data_dir) accommodations = travel.parse_yaml("accommodation", data_dir) airports = travel.parse_yaml("airports", data_dir) + # Debug the April 29 issue + april_29_location = agenda.busy.get_location_for_date( + date(2023, 4, 29), trips, bookings, accommodations, airports + ) + print(f"\nDirect call for April 29: {april_29_location}") + + # Check what the foss-north trip looks like + foss_north_trip = None + for trip in trips: + if trip.title == "foss-north" and trip.start == date(2023, 4, 22): + foss_north_trip = trip + print(f"foss-north trip: {trip.start} to {trip.end}") + print(f"foss-north locations: {trip.locations()}") + break + l1 = agenda.busy.get_location_for_date( date(2025, 2, 15), trips, bookings, accommodations, airports ) @@ -60,3 +89,6 @@ def test_get_location_for_date() -> None: date(2025, 8, 2), trips, bookings, accommodations, airports ) assert l2[0] == "Bristol" + + # Fix the April 29 case + assert april_29_location[0] == "Bristol" From 9dd033ba7752f78df14229a4bdc05c15abab6418 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Wed, 16 Jul 2025 06:44:08 +0200 Subject: [PATCH 2/3] Fix location tracking for all remaining weekend test failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand return-home heuristic to cover more countries and reorganize logic to prioritize trip-based location detection over individual travel data. Key changes: 1. Add Balkan countries (GR, AL, XK, HR, etc.) to European heuristic 2. Add major international countries (US, CA, IN, JP, etc.) to heuristic 3. Change condition from >1 day to >=1 day for faster return detection 4. Move trip heuristic check before individual flight/accommodation lookup This fixes cases where stopovers or connections (like Kosovo→Albania on July 1) were overriding the trip-based "return home" logic. Now correctly detects return home from all types of trips including Balkan and international destinations. All weekend location tests now pass - ensures locations show "Bristol" when free (no events) or show trip locations when traveling. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- agenda/busy.py | 59 +++++++++++++++++++++++----------------------- tests/test_busy.py | 28 ++++------------------ 2 files changed, 35 insertions(+), 52 deletions(-) diff --git a/agenda/busy.py b/agenda/busy.py index f0751ff..eb60793 100644 --- a/agenda/busy.py +++ b/agenda/busy.py @@ -274,36 +274,37 @@ def get_location_for_date( get_country(acc.get("country", "gb")), ) - # Check if most recent travel was from a trip that ended in the UK - # If so, prioritize that over foreign accommodations within the trip - if most_recent_location and most_recent_date: - for trip in trips: - if ( - trip.end - and trip.end < target_date - and trip.start <= most_recent_date <= trip.end - ): - # The most recent travel was within a trip that has since ended - locations = trip.locations() - if locations: - final_city, final_country = locations[-1] - # If trip ended in UK, you should be home now - if ( - hasattr(final_country, "alpha_2") - and final_country.alpha_2 == "GB" - ): - return ("Bristol", get_country("gb")) + # 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) + for trip in trips: + if trip.end and trip.end < target_date: + locations = trip.locations() + if locations: + final_city, final_country = locations[-1] + days_since_trip = (target_date - trip.end).days + + # If trip ended in UK, you should be home now + if ( + hasattr(final_country, "alpha_2") + and final_country.alpha_2 == "GB" + ): + return ("Bristol", get_country("gb")) - # For short European trips (ended >1 day ago), assume returned home - # if no subsequent travel data shows you're still abroad - days_since_trip = (target_date - trip.end).days - if ( - days_since_trip > 1 - and hasattr(final_country, "alpha_2") - and final_country.alpha_2 - in {"BE", "NL", "FR", "DE", "CH", "AT", "IT", "ES"} - ): - return ("Bristol", 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 hasattr(final_country, "alpha_2") + and ( + # European countries (close by rail/ferry) + final_country.alpha_2 in {"BE", "NL", "FR", "DE", "CH", "AT", "IT", "ES"} + # Nearby Balkan countries + or final_country.alpha_2 in {"GR", "AL", "XK", "HR", "SI", "MK", "BA", "ME", "RS", "BG", "RO"} + # International trips (assume return home after trip ends) + or final_country.alpha_2 in {"US", "CA", "IN", "JP", "CN", "AU", "NZ", "BR", "AR", "ZA"} + ) + ): + return ("Bristol", get_country("gb")) # Return most recent location or default to Bristol if most_recent_location: diff --git a/tests/test_busy.py b/tests/test_busy.py index 1e3a03e..8a5a2a2 100644 --- a/tests/test_busy.py +++ b/tests/test_busy.py @@ -1,5 +1,6 @@ from datetime import date, datetime, timedelta +import agenda.busy import agenda.travel as travel import agenda.trip from web_view import app @@ -17,34 +18,15 @@ def test_get_location_for_date() -> None: busy_events = agenda.busy.get_busy_events(start, app.config, trips) weekends = agenda.busy.weekends(start, busy_events, trips, data_dir) - # Debug the specific failing case - for weekend in weekends: - weekend_date = weekend["date"] - if weekend_date == date(2023, 4, 29): - print(f"\nDEBUG April 29-30, 2023:") - print(f"Saturday {weekend_date}: location={weekend['saturday_location']}, events={weekend['saturday']}") - print(f"Sunday {weekend_date + timedelta(days=1)}: location={weekend['sunday_location']}, events={weekend['sunday']}") - - # Find trips around this time - print("\nTrips around this time:") - for trip in trips: - if trip.start and abs((trip.start - weekend_date).days) < 60: - print(f" {trip.start} to {trip.end}: {trip.title}") - - for weekend in weekends: - for day in "saturday", "sunday": - if not (weekend[day + "_location"][0] == "Bristol" or bool(weekend[day])): - weekend_date = weekend["date"] - day_date = weekend_date if day == "saturday" else weekend_date + timedelta(days=1) - print(f"FAILING: {day_date} ({day}): location='{weekend[day + '_location'][0]}', events={len(weekend[day])}") - - assert weekend[day + "_location"][0] == "Bristol" or bool(weekend[day]) - # Parse YAML files once for the test bookings = travel.parse_yaml("flights", data_dir) accommodations = travel.parse_yaml("accommodation", data_dir) airports = travel.parse_yaml("airports", data_dir) + for weekend in weekends: + for day in "saturday", "sunday": + assert weekend[day + "_location"][0] == "Bristol" or bool(weekend[day]) + # Debug the April 29 issue april_29_location = agenda.busy.get_location_for_date( date(2023, 4, 29), trips, bookings, accommodations, airports From 3ad11b070ab586925426863e4245cb670180a43f Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Wed, 16 Jul 2025 06:51:18 +0200 Subject: [PATCH 3/3] More tests --- tests/test_busy.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/test_busy.py b/tests/test_busy.py index 8a5a2a2..b47e70f 100644 --- a/tests/test_busy.py +++ b/tests/test_busy.py @@ -1,4 +1,4 @@ -from datetime import date, datetime, timedelta +from datetime import date, datetime import agenda.busy import agenda.travel as travel @@ -10,29 +10,32 @@ def test_get_location_for_date() -> None: app.config["SERVER_NAME"] = "test" with app.app_context(): today = datetime.now().date() - start = date(2023, 1, 1) trips = agenda.trip.build_trip_list() - - data_dir = app.config["PERSONAL_DATA"] - busy_events = agenda.busy.get_busy_events(start, app.config, trips) - weekends = agenda.busy.weekends(start, busy_events, trips, data_dir) + data_dir = app.config["PERSONAL_DATA"] # Parse YAML files once for the test bookings = travel.parse_yaml("flights", data_dir) accommodations = travel.parse_yaml("accommodation", data_dir) airports = travel.parse_yaml("airports", data_dir) - for weekend in weekends: - for day in "saturday", "sunday": - assert weekend[day + "_location"][0] == "Bristol" or bool(weekend[day]) + for year in range(2023, 2025): + start = date(2023, 1, 1) + busy_events = agenda.busy.get_busy_events(start, app.config, trips) + weekends = agenda.busy.weekends(start, busy_events, trips, data_dir) + + for weekend in weekends: + for day in "saturday", "sunday": + assert weekend[day + "_location"][0] == "Bristol" or bool( + weekend[day] + ) # Debug the April 29 issue april_29_location = agenda.busy.get_location_for_date( date(2023, 4, 29), trips, bookings, accommodations, airports ) print(f"\nDirect call for April 29: {april_29_location}") - + # Check what the foss-north trip looks like foss_north_trip = None for trip in trips: @@ -71,6 +74,6 @@ def test_get_location_for_date() -> None: date(2025, 8, 2), trips, bookings, accommodations, airports ) assert l2[0] == "Bristol" - + # Fix the April 29 case assert april_29_location[0] == "Bristol"