diff --git a/AGENTS.md b/AGENTS.md index 87c46c9..2616f78 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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`. diff --git a/agenda/busy.py b/agenda/busy.py index 582bbce..8452903 100644 --- a/agenda/busy.py +++ b/agenda/busy.py @@ -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: diff --git a/agenda/calendar.py b/agenda/calendar.py index 0a66b6c..f037a3c 100644 --- a/agenda/calendar.py +++ b/agenda/calendar.py @@ -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, diff --git a/agenda/stats.py b/agenda/stats.py index c3c8e4f..c74d94e 100644 --- a/agenda/stats.py +++ b/agenda/stats.py @@ -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) diff --git a/agenda/trip.py b/agenda/trip.py index cb96981..03ebcd1 100644 --- a/agenda/trip.py +++ b/agenda/trip.py @@ -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 diff --git a/templates/trip/stats.html b/templates/trip/stats.html index e07766d..0e65005 100644 --- a/templates/trip/stats.html +++ b/templates/trip/stats.html @@ -27,13 +27,19 @@
Trips in {{ year }}: {{ year_stats.count }}
Conferences in {{ year }}: {{ year_stats.conferences }}
{{ 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 %} {{ c.flag }} {{ c.name }} ({{ c.alpha_2 }}) - {% if c in new_countries %} + {% if c in display_new_countries %} new {% endif %} diff --git a/tests/test_trip_stats.py b/tests/test_trip_stats.py index 37d04b6..20ff7f5 100644 --- a/tests/test_trip_stats.py +++ b/tests/test_trip_stats.py @@ -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] diff --git a/web_view.py b/web_view.py index a61e257..fa3c8ee 100755 --- a/web_view.py +++ b/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",