Compare commits
No commits in common. "f22772d12df22710c1c1443128670ea3c5023ae6" and "8a0df48a73c0749f9f4d2beb55123d90488b2bcf" have entirely different histories.
f22772d12d
...
8a0df48a73
8 changed files with 29 additions and 47 deletions
|
|
@ -32,4 +32,4 @@ This is a personal agenda web application built with Flask that tracks various e
|
|||
- Personal data directory (`personal-data/`) is excluded from git
|
||||
|
||||
## Notes
|
||||
- Trip stats new-country badges come from `agenda.stats.calculate_yearly_stats` via `year_stats.new_countries` (first-visit year, excluding `PREVIOUSLY_VISITED`).
|
||||
- Trip stats new-country badges come from `agenda.stats.calculate_yearly_stats` via `year_stats.new_countries` (first-visit year); `templates/trip/stats.html` filters against `PREVIOUSLY_VISITED`.
|
||||
|
|
|
|||
|
|
@ -81,16 +81,15 @@ def get_busy_events(
|
|||
return busy_events
|
||||
|
||||
|
||||
def _parse_datetime_field(datetime_obj: datetime | date | str) -> tuple[datetime, date]:
|
||||
def _parse_datetime_field(datetime_obj: datetime | date) -> tuple[datetime, date]:
|
||||
"""Parse a datetime field that could be datetime object or string."""
|
||||
if isinstance(datetime_obj, datetime):
|
||||
if hasattr(datetime_obj, "date"):
|
||||
return datetime_obj, datetime_obj.date()
|
||||
if isinstance(datetime_obj, date):
|
||||
return datetime.combine(datetime_obj, datetime.min.time()), datetime_obj
|
||||
if isinstance(datetime_obj, str):
|
||||
elif isinstance(datetime_obj, str):
|
||||
dt = datetime.fromisoformat(datetime_obj.replace("Z", "+00:00"))
|
||||
return dt, dt.date()
|
||||
raise ValueError(f"Invalid datetime format: {datetime_obj}")
|
||||
else:
|
||||
raise ValueError(f"Invalid datetime format: {datetime_obj}")
|
||||
|
||||
|
||||
def _get_accommodation_location(
|
||||
|
|
@ -106,15 +105,13 @@ def _get_accommodation_location(
|
|||
def _find_most_recent_travel_within_trip(
|
||||
trip: Trip,
|
||||
target_date: date,
|
||||
) -> 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"}
|
||||
|
||||
trip_most_recent_date: date | None = None
|
||||
trip_most_recent_location: tuple[str | None, pycountry.db.Country | None] | None = (
|
||||
None
|
||||
)
|
||||
trip_most_recent_datetime: datetime | None = None
|
||||
trip_most_recent_date = None
|
||||
trip_most_recent_location = None
|
||||
trip_most_recent_datetime = None
|
||||
|
||||
# Check flights within trip period
|
||||
for travel_item in trip.travel:
|
||||
|
|
@ -289,9 +286,6 @@ def _get_trip_location_by_progression(
|
|||
return (city, country)
|
||||
|
||||
# Multiple locations: use progression through the trip
|
||||
if not trip.end:
|
||||
city, country = locations[-1]
|
||||
return (city, country)
|
||||
trip_duration = (trip.end - trip.start).days + 1
|
||||
days_into_trip = (target_date - trip.start).days
|
||||
|
||||
|
|
@ -311,9 +305,9 @@ def _find_most_recent_travel_before_date(
|
|||
"""Find the most recent travel location before a given date."""
|
||||
uk_airports = {"LHR", "LGW", "STN", "LTN", "BRS", "BHX", "MAN", "EDI", "GLA"}
|
||||
|
||||
most_recent_location: tuple[str | None, pycountry.db.Country | None] | None = None
|
||||
most_recent_date: date | None = None
|
||||
most_recent_datetime: datetime | None = None
|
||||
most_recent_location = None
|
||||
most_recent_date = None
|
||||
most_recent_datetime = None
|
||||
|
||||
# Check all travel across all trips
|
||||
for trip in trips:
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ def build_toastui_events(events: list[Event]) -> list[dict[str, typing.Any]]:
|
|||
end_date = e.as_date
|
||||
end_iso = end_date.isoformat()
|
||||
|
||||
item: dict[str, typing.Any] = {
|
||||
item = {
|
||||
"id": str(uuid.uuid4()),
|
||||
"calendarId": calendar_id,
|
||||
"title": e.title_with_emoji,
|
||||
|
|
|
|||
|
|
@ -33,13 +33,10 @@ def conferences(trip: Trip, yearly_stats: Mapping[int, StrDict]) -> None:
|
|||
yearly_stats[c["start"].year]["conferences"] += 1
|
||||
|
||||
|
||||
def calculate_yearly_stats(
|
||||
trips: list[Trip], previously_visited: set[str] | None = None
|
||||
) -> dict[int, StrDict]:
|
||||
def calculate_yearly_stats(trips: list[Trip]) -> dict[int, StrDict]:
|
||||
"""Calculate total distance and distance by transport type grouped by year."""
|
||||
yearly_stats: defaultdict[int, StrDict] = defaultdict(dict)
|
||||
first_visit_year: dict[str, int] = {}
|
||||
excluded_new: set[str] = previously_visited or set()
|
||||
|
||||
for trip in trips:
|
||||
year = trip.start.year
|
||||
|
|
@ -75,10 +72,7 @@ def calculate_yearly_stats(
|
|||
continue
|
||||
yearly_stats[year].setdefault("countries", set())
|
||||
yearly_stats[year]["countries"].add(country)
|
||||
if (
|
||||
first_visit_year.get(country.alpha_2) == year
|
||||
and country.alpha_2 not in excluded_new
|
||||
):
|
||||
if first_visit_year.get(country.alpha_2) == year:
|
||||
yearly_stats[year].setdefault("new_countries", set())
|
||||
yearly_stats[year]["new_countries"].add(country)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class Airline(typing.TypedDict, total=False):
|
|||
|
||||
def load_travel(travel_type: str, plural: str, data_dir: str) -> list[StrDict]:
|
||||
"""Read flight and train journeys."""
|
||||
items: list[StrDict] = travel.parse_yaml(plural, data_dir)
|
||||
items = typing.cast(list[StrDict], travel.parse_yaml(plural, data_dir))
|
||||
for item in items:
|
||||
item["type"] = travel_type
|
||||
return items
|
||||
|
|
|
|||
|
|
@ -27,13 +27,19 @@
|
|||
<div>Trips in {{ year }}: {{ year_stats.count }}</div>
|
||||
<div>Conferences in {{ year }}: {{ year_stats.conferences }}</div>
|
||||
<div>{{ countries | count }} countries visited in {{ year }}:
|
||||
{% if new_countries %}
|
||||
({{ new_countries | count }} new)
|
||||
{% set display_new_countries = [] %}
|
||||
{% for c in new_countries %}
|
||||
{% if c.alpha_2 not in previously_visited %}
|
||||
{% set _ = display_new_countries.append(c) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if display_new_countries %}
|
||||
({{ display_new_countries | count }} new)
|
||||
{% endif %}
|
||||
{% for c in countries %}
|
||||
<span class="d-inline-block border border-2 p-1 m-1">
|
||||
{{ c.flag }} {{ c.name }} ({{ c.alpha_2 }})
|
||||
{% if c in new_countries %}
|
||||
{% if c in display_new_countries %}
|
||||
<span class="badge text-bg-info">new</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -27,11 +27,3 @@ def test_new_country_only_first_year() -> None:
|
|||
assert czechia in yearly_stats[2024]["new_countries"]
|
||||
assert czechia in yearly_stats[2026]["countries"]
|
||||
assert "new_countries" not in yearly_stats[2026]
|
||||
|
||||
|
||||
def test_new_country_respects_previously_visited() -> None:
|
||||
"""Ensure previously visited countries are excluded from new-country stats."""
|
||||
trips = [make_trip(date(2024, 5, 1), "CZ")]
|
||||
|
||||
yearly_stats = calculate_yearly_stats(trips, {"CZ"})
|
||||
assert "new_countries" not in yearly_stats[2024]
|
||||
|
|
|
|||
10
web_view.py
10
web_view.py
|
|
@ -461,9 +461,7 @@ def accommodation_list() -> str:
|
|||
items = travel.parse_yaml("accommodation", data_dir)
|
||||
|
||||
# Create a dictionary to hold stats for each year
|
||||
year_stats: defaultdict[int, dict[str, int]] = defaultdict(
|
||||
lambda: {"total_nights": 0, "nights_abroad": 0}
|
||||
)
|
||||
year_stats = defaultdict(lambda: {"total_nights": 0, "nights_abroad": 0})
|
||||
|
||||
# Calculate stats for each year
|
||||
for stay in items:
|
||||
|
|
@ -538,7 +536,7 @@ def calc_total_distance(trips: list[Trip]) -> float:
|
|||
|
||||
def calc_total_co2_kg(trips: list[Trip]) -> float:
|
||||
"""Total CO₂ for trips."""
|
||||
return sum(item.total_co2_kg() or 0.0 for item in trips)
|
||||
return sum(item.total_co2_kg() for item in trips)
|
||||
|
||||
|
||||
def sum_distances_by_transport_type(trips: list[Trip]) -> list[tuple[str, float]]:
|
||||
|
|
@ -808,9 +806,7 @@ def trip_stats() -> str:
|
|||
|
||||
conferences = sum(len(item.conferences) for item in trip_list)
|
||||
|
||||
yearly_stats = agenda.stats.calculate_yearly_stats(
|
||||
trip_list, app.config.get("PREVIOUSLY_VISITED")
|
||||
)
|
||||
yearly_stats = agenda.stats.calculate_yearly_stats(trip_list)
|
||||
|
||||
return flask.render_template(
|
||||
"trip/stats.html",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue