Show exploration days before/after each conference on trip page

For each conference, calculate the number of free days available
before and after, relative to the trip boundary or adjacent
conference. Multi-conference trips show the gap between conferences
as "after" the first and "before" the second.

Closes #189

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2026-04-23 14:29:29 +01:00
parent 3ba18f9019
commit 54e898bc11
3 changed files with 44 additions and 0 deletions

View file

@ -456,6 +456,39 @@ def coordinate_dict(item: StrDict, coord_type: str) -> StrDict:
}
def conference_free_days(trip: Trip) -> dict[str, tuple[int, int]]:
"""Return (days_before, days_after) exploration days for each conference.
Keyed by the conference start date as an ISO string. Days are relative to
the trip boundary or the adjacent conference's end/start for multi-conference
trips.
"""
if not trip.conferences or not trip.end:
return {}
def conf_attend_start(c: StrDict) -> date:
return as_date(c.get("attend_start") or c["start"])
def conf_attend_end(c: StrDict) -> date:
return as_date(c.get("attend_end") or c["end"])
sorted_confs = sorted(trip.conferences, key=conf_attend_start)
result: dict[str, tuple[int, int]] = {}
for i, conf in enumerate(sorted_confs):
before_boundary = conf_attend_end(sorted_confs[i - 1]) if i > 0 else trip.start
after_boundary = (
conf_attend_start(sorted_confs[i + 1])
if i < len(sorted_confs) - 1
else trip.end
)
days_before = (conf_attend_start(conf) - before_boundary).days
days_after = (after_boundary - conf_attend_end(conf)).days
result[str(conf["start"])] = (days_before, days_after)
return result
def collect_trip_coordinates(trip: Trip) -> list[StrDict]:
"""Extract and de-duplicate travel location coordinates from trip."""
coords = []

View file

@ -239,6 +239,16 @@
{% elif item.price and item.currency %}
<span class="badge bg-info text-nowrap">price: {{ item.price }} {{ item.currency }}</span>
{% endif %}
{% set free_days = conference_free_days.get(item.start | string) %}
{% if free_days %}
{% set days_before, days_after = free_days %}
{% if days_before > 0 %}
<span class="badge bg-secondary text-nowrap">{{ days_before }} day{{ 's' if days_before != 1 }} to explore before</span>
{% endif %}
{% if days_after > 0 %}
<span class="badge bg-secondary text-nowrap">{{ days_after }} day{{ 's' if days_after != 1 }} to explore after</span>
{% endif %}
{% endif %}
</p>
</div>
</div>

View file

@ -1144,6 +1144,7 @@ def trip_page(start: str) -> str:
destination_times=get_destination_timezones(trip),
human_readable_delta=agenda.utils.human_readable_delta,
trip_weather=trip_weather,
conference_free_days=agenda.trip.conference_free_days(trip),
)