Compare commits

...

6 commits

Author SHA1 Message Date
Edward Betts 2203677146 Trip debug page needs user to be authenticated. 2025-07-16 12:12:47 +02:00
Edward Betts 46091779f0 Format code with black, remove unused import. 2025-07-16 12:12:27 +02:00
Edward Betts 29d5145b87 Refactor get_location_for_date to use trip data directly
Simplify the location tracking function by extracting travel data directly
from trip objects instead of requiring separate YAML file parameters.

Changes:
- Remove airport, train, and ferry location helper functions that required
  separate YAML data lookups
- Update get_location_for_date signature to only take target_date and trips
- Extract flight/train/ferry details directly from trip.travel items
- Use embedded airport/station/terminal objects from trip data
- Remove YAML file parsing from weekends function
- Update test calls to use new simplified signature

This eliminates duplicate data loading and simplifies the API while
maintaining all existing functionality.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 12:08:19 +02:00
Edward Betts 0e2c95117c Add debug page for trip objects
Add /trip/<start>/debug endpoint that displays the complete trip object
data in pretty-printed JSON format with syntax highlighting. Includes
both raw data and computed properties for debugging purposes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:25:17 +02:00
Edward Betts f396d8a62f Simplify return home heuristics now that we have comprehensive travel data
Remove complex country-based return home assumptions since we now track actual train and ferry travel back to the UK. The system can rely on real travel data rather than heuristics.

- Remove NEARBY_BALKAN_COUNTRIES constant
- Remove geographic assumptions about returning home after trips
- Keep only UK trip ending logic (if trip ends in UK, you're home)

With complete train/ferry tracking, actual return travel is captured rather than inferred.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:16:33 +02:00
Edward Betts e370049bcb Add train and ferry support to location tracking
- Add helper functions for train and ferry location extraction
- Update get_location_for_date to consider trains and ferries alongside flights
- Parse stations.yaml and ferry_terminals.yaml for location data
- Handle UK vs international locations consistently for all transport modes
- Add comprehensive tests for new train and ferry location helpers
- Format code with black for consistent style

Now tracks complete travel history including flights, trains, ferries, and accommodation for accurate location determination.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:10:58 +02:00
4 changed files with 524 additions and 158 deletions

View file

@ -11,20 +11,6 @@ from . import events_yaml, get_country, travel
from .event import Event
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:
"""Busy."""
@ -106,58 +92,20 @@ def _parse_datetime_field(datetime_obj: datetime | date) -> tuple[datetime, date
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(
acc: StrDict, on_trip: bool = False
) -> tuple[str | None, pycountry.db.Country | None]:
) -> tuple[str | None, pycountry.db.Country]:
"""Get location from accommodation data."""
if acc.get("country") == "gb":
if on_trip:
# When on a trip, show the actual location even for UK accommodations
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")))
c = get_country(acc["country"])
assert c
assert isinstance(acc["location"], str)
return (acc["location"] if on_trip else None, c)
def _find_most_recent_travel_within_trip(
trip: Trip,
target_date: date,
bookings: list[StrDict],
accommodations: list[StrDict],
airports: StrDict,
) -> tuple[str | None, pycountry.db.Country | None] | None:
) -> tuple[str | None, pycountry.db.Country] | None:
"""Find the most recent travel location within a trip."""
uk_airports = {"LHR", "LGW", "STN", "LTN", "BRS", "BHX", "MAN", "EDI", "GLA"}
@ -166,39 +114,54 @@ def _find_most_recent_travel_within_trip(
trip_most_recent_datetime = None
# Check flights within trip period
for booking in bookings:
for flight in booking.get("flights", []):
if "arrive" in flight:
try:
arrive_datetime, arrive_date = _parse_datetime_field(
flight["arrive"]
)
except ValueError:
continue
for travel_item in trip.travel:
if travel_item["type"] == "flight" and "arrive" in travel_item:
arrive_datetime, arrive_date = _parse_datetime_field(travel_item["arrive"])
# Only consider flights within this trip and before target date
if trip.start <= arrive_date <= target_date:
# Compare both date and time to handle same-day flights 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
)
# Only consider flights within this trip and before target date
if not (trip.start <= arrive_date <= target_date):
continue
# Compare both date and time to handle same-day flights 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
destination_airport = travel_item["to"]
assert "to_airport" in travel_item
airport_info = travel_item["to_airport"]
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_date = arrive_date
trip_most_recent_datetime = arrive_datetime
destination_airport = flight["to"]
trip_most_recent_location = _get_airport_location(
destination_airport, airports, uk_airports, on_trip=True
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
for acc in accommodations:
for acc in trip.accommodation:
if "from" in acc:
try:
_, acc_date = _parse_datetime_field(acc["from"])
@ -219,6 +182,93 @@ def _find_most_recent_travel_within_trip(
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
@ -250,9 +300,7 @@ def _get_trip_location_by_progression(
def _find_most_recent_travel_before_date(
target_date: date,
bookings: list[StrDict],
accommodations: list[StrDict],
airports: StrDict,
trips: list[Trip],
) -> tuple[str | None, pycountry.db.Country | None] | None:
"""Find the most recent travel location before a given date."""
uk_airports = {"LHR", "LGW", "STN", "LTN", "BRS", "BHX", "MAN", "EDI", "GLA"}
@ -261,13 +309,14 @@ def _find_most_recent_travel_before_date(
most_recent_date = None
most_recent_datetime = None
# Check flights
for booking in bookings:
for flight in booking.get("flights", []):
if "arrive" in flight:
# Check all travel across all trips
for trip in trips:
# Check flights
for travel_item in trip.travel:
if travel_item["type"] == "flight" and "arrive" in travel_item:
try:
arrive_datetime, arrive_date = _parse_datetime_field(
flight["arrive"]
travel_item["arrive"]
)
except ValueError:
continue
@ -287,26 +336,167 @@ def _find_most_recent_travel_before_date(
):
most_recent_date = arrive_date
most_recent_datetime = arrive_datetime
destination_airport = flight["to"]
most_recent_location = _get_airport_location(
destination_airport, airports, uk_airports, on_trip=False
destination_airport = travel_item["to"]
# For flights, determine if we're "on trip" based on whether this is within any trip period
on_trip = any(
t.start <= arrive_date <= (t.end or t.start) for t in trips
)
# Check accommodation - only override if accommodation is more recent
for acc in accommodations:
if "from" in acc:
try:
_, acc_date = _parse_datetime_field(acc["from"])
except ValueError:
continue
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"),
)
if acc_date <= target_date:
# Only update if this accommodation is more recent than existing result
if most_recent_date is None or acc_date > most_recent_date:
most_recent_date = acc_date
most_recent_location = _get_accommodation_location(
acc, on_trip=False
# 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
for acc in trip.accommodation:
if "from" in acc:
try:
_, acc_date = _parse_datetime_field(acc["from"])
except ValueError:
continue
if acc_date <= target_date:
# Only update if this accommodation is more recent than existing result
if most_recent_date is None or acc_date > most_recent_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(
acc, on_trip=on_trip
)
return most_recent_location
@ -327,49 +517,33 @@ def _check_return_home_heuristic(
if hasattr(final_country, "alpha_2") and final_country.alpha_2 == "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
def get_location_for_date(
target_date: date,
trips: list[Trip],
bookings: list[StrDict],
accommodations: list[StrDict],
airports: StrDict,
) -> tuple[str | None, pycountry.db.Country | None]:
"""Get location (city, country) for a specific date using travel history."""
# First check if currently on a trip
for trip in trips:
if trip.start <= target_date <= (trip.end or trip.start):
# For trips, find the most recent flight or accommodation within the trip period
trip_location = _find_most_recent_travel_within_trip(
trip, target_date, bookings, accommodations, airports
)
if trip_location:
return trip_location
if not (trip.start <= target_date <= (trip.end or trip.start)):
continue
# For trips, find the most recent travel within the trip period
trip_location = _find_most_recent_travel_within_trip(
trip,
target_date,
)
if trip_location:
return trip_location
# Fallback: determine location based on trip progression and date
progression_location = _get_trip_location_by_progression(trip, target_date)
if progression_location:
return progression_location
# Fallback: determine location based on trip progression and date
progression_location = _get_trip_location_by_progression(trip, target_date)
if progression_location:
return progression_location
# Find most recent flight or accommodation before this date
recent_travel = _find_most_recent_travel_before_date(
target_date, bookings, accommodations, airports
)
# Find most recent travel before this date
recent_travel = _find_most_recent_travel_before_date(target_date, trips)
# 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)
@ -396,11 +570,6 @@ def weekends(
else:
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 = []
for i in range(52):
saturday = start_date + timedelta(weeks=i)
@ -418,10 +587,12 @@ def weekends(
]
saturday_location = get_location_for_date(
saturday, trips, bookings, accommodations, airports
saturday,
trips,
)
sunday_location = get_location_for_date(
sunday, trips, bookings, accommodations, airports
sunday,
trips,
)
weekends_info.append(

128
templates/trip_debug.html Normal file
View file

@ -0,0 +1,128 @@
{% 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 %}

View file

@ -73,9 +73,6 @@ def test_specific_home_dates(travel_data):
location = agenda.busy.get_location_for_date(
test_date,
trips,
travel_data["bookings"],
travel_data["accommodations"],
travel_data["airports"],
)
assert not location[
0
@ -94,9 +91,6 @@ def test_specific_away_dates(travel_data):
location = agenda.busy.get_location_for_date(
test_date,
trips,
travel_data["bookings"],
travel_data["accommodations"],
travel_data["airports"],
)
assert (
location[0] == expected_city
@ -111,9 +105,6 @@ def test_get_location_for_date_basic(travel_data):
location = agenda.busy.get_location_for_date(
test_date,
trips,
travel_data["bookings"],
travel_data["accommodations"],
travel_data["airports"],
)
# Should return a tuple with (city|None, country)

View file

@ -4,6 +4,7 @@
import decimal
import inspect
import json
import operator
import os.path
import sys
@ -258,7 +259,9 @@ async def weekends() -> str:
trip_list = agenda.trip.build_trip_list()
busy_events = agenda.busy.get_busy_events(start, app.config, trip_list)
weekends = agenda.busy.weekends(start, busy_events, trip_list, app.config["PERSONAL_DATA"])
weekends = agenda.busy.weekends(
start, busy_events, trip_list, app.config["PERSONAL_DATA"]
)
return flask.render_template(
"weekends.html",
items=weekends,
@ -586,6 +589,79 @@ 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")
def holiday_list() -> str:
"""List of holidays."""