agenda/AGENTS.md
Edward Betts 49e5a3000e Add attend_start/attend_end fields for partial conference attendance.
Allows recording when you arrive late or leave early at a multi-day
conference. Both fields accept a plain date or datetime with time.
Trip pages display the attendance dates instead of the official
conference dates when these fields are set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 18:14:27 +00:00

67 lines
4 KiB
Markdown

# Development Guidelines
## Project Overview
This is a personal agenda web application built with Flask that tracks various events and important dates:
- Events: birthdays, holidays, travel itineraries, conferences, waste collection schedules
- Space launches, meteor showers, astronomical events
- Financial information (FX rates, stock market)
- UK-specific features (holidays, waste collection, railway schedules)
- Authentication via UniAuth
- Frontend uses Bootstrap 5, Leaflet for maps, FullCalendar for calendar views
## Python Environment
- Always use `python3` directly, never `python`
- All Python code should include type annotations
- Use `typing.Any` instead of `Any` in type hints (import from typing module)
- Run `mypy --strict` (fix any type errors in the file) and `black` on modified code after creating or modifying Python files
- Avoid running `black .`
- Main entry point: `python3 web_view.py` (Flask app on port 5000)
- Tests: Use `pytest` (tests in `/tests/` directory)
## Project Structure
- `agenda/` - Main Python package with modules for different event types
- `web_view.py` - Flask web application entry point
- `templates/` - Jinja2 HTML templates
- `static/` - CSS, JS, and frontend assets
- `config/` - Configuration files
- `personal-data/` - User's personal data (not in git)
## Git Workflow
- Avoid committing unrelated untracked files (e.g., `node_modules/`, build artifacts)
- Only commit relevant project files
- Personal data directory (`personal-data/`) is excluded from git
## Conference attendance fields
Conferences in `conferences.yaml` support optional `attend_start` and `attend_end` fields for when you arrive late or leave early. Both accept a plain date or a datetime with time and timezone (YAML datetime syntax). When present, the trip page shows the attendance dates instead of the official conference dates. The official `start`/`end` fields are always kept for context.
```yaml
- name: FOSDEM
start: 2023-02-04
end: 2023-02-05
attend_end: 2023-02-04 # left after day 1
attend_start: 2023-02-04 14:00:00+00:00 # or with time
```
## 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 are calculated in `agenda/stats.py`:
- `travel_legs()` extracts airlines, airports, and stations from individual trip travel legs
- `calculate_yearly_stats()` aggregates stats per year including flight/train counts, airlines, airports, stations
- `calculate_overall_stats()` aggregates yearly stats into overall totals for the summary section
## Travel type patterns
Transport types: `flight`, `train`, `ferry`, `coach`, `bus`.
**Road transport (bus and coach)** share a common loader `load_road_transport()` in `trip.py`. `load_coaches` and `load_buses` are thin wrappers that pass type name, YAML filenames, and CO2 factor (coach: 0.027 kg/km, bus: 0.1 kg/km). Both use `from_station`/`to_station` fields and support scalar or dict-keyed GeoJSON route filenames in the stop/station data.
**Ferry** is loaded separately: uses `from_terminal`/`to_terminal` fields and GeoJSON routes come from the terminal's `routes` dict (always a dict, not scalar).
**Route rendering** (`get_trip_routes`): bus and coach are handled in a single combined block (they use the same pattern — `from_station`/`to_station`, `{type}_routes/` folder). Ferry always has a geojson file and renders as type `"train"` for the map renderer.
**Trip elements** (`Trip.elements()` in `types.py`): bus and coach are handled in a single combined block using `item["type"] in ("coach", "bus")`. Ferry is separate because it uses `from_terminal`/`to_terminal` and always requires `arrive` (not optional).
**Location collection** (`get_locations`): bus → `bus_stop`, coach → `coach_station`, ferry → `ferry_terminal` (all separate map pin types).
**CO2 factors** (kg CO2e per passenger per km): train 0.037, coach 0.027, ferry 0.02254, bus 0.1.
**Schengen tracking**: ferry journeys are tracked for Schengen compliance; bus and coach are not.