aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md103
1 files changed, 54 insertions, 49 deletions
diff --git a/README.md b/README.md
index 1ae3a8b..9c042d4 100644
--- a/README.md
+++ b/README.md
@@ -4,52 +4,29 @@ A Go toolkit for analyzing cycling rides from GPX exports: parse rides, detect c
## Features
-- **GPX parsing** — reads elevation-tracked rides from Garmin/Strava GPX exports into a `Ride` model
-- **Climb detection** — automatically finds climbs within a ride, each scored and categorized (Cat 4 … HC), requiring at least a 3.5% average slope (gentler stretches are re-segmented into their qualifying parts)
-- **Difficulty score (Cotacol)** — scores any ride or climb by splitting it into 100 m segments and summing `0.1 km × slope²` per segment. This fixed step is a deliberate compromise: smaller steps increase sensitivity to elevation noise, while unconstrained variable steps maximize noise rather than meaningful difficulty.
-- **Similar-climb matching** — finds the same climb across rides by matching start/end coordinates, so times can be compared
-- **Mountain pass extraction** — extracts French mountain passes from an OpenStreetMap PBF export (nodes tagged `mountain_pass=yes` / `natural=mountain_pass`) into a SQLite database
-- **Pass crossing detection** — flags which passes a ride crosses and names each climb after the pass it tops (e.g. "Col de Castellaras")
-- **Interactive ride profiles** — explores elevation, climbs, and mountain-pass crossings in the web ride detail view
-- **Official climb cards** — separates matched climbs from pending matches and expands each matched climb with its own elevation profile
-- **Repeated official climbs** — matches the same official climb once per lap on looping rides, respecting direction
-- **Web ride library** — starts a local web server by default, imports Strava rides over a date range, stores metadata in SQLite, and keeps their GPX files on disk
-- **Materialized ride values** — computes Cotacol on import, stores its algorithm version in SQLite, and refreshes all computed values with `-backfill`
-- **Strava stream metrics** — preserves heart rate, cadence, and power in Garmin-compatible GPX files and in-memory ride columns
-- **Metadata-only ride imports** — stores Strava activity statistics for rides without GPS tracks, without Cotacol or route data
-- **Ride table sorting** — sorts every ride-library column through clickable server-side headers and query parameters
-- **Ride library filters** — filters rides by distance, moving duration, start date, Cotacol score, and route availability through shareable query URLs
-- **Computed moving time** — computes moving time from the GPX track with a speed-threshold algorithm, persists it as the canonical value (keeping Strava's as `external_moving_time_s`), and benchmarks against that reference via `-backtest`
-- **CLI progress bars** — shows a live progress bar for the long-running CLI processes (`-fetch-osm`, `-extract-osm`, `-backfill`, `-backtest`)
-- **Verified OSM download** — verifies the France PBF against geofabrik's published MD5 after downloading (and on already-complete files), keeping a corrupt file for inspection instead of silently trusting its size
-- **Ride detail maps** — opens a stored ride with its recorded route on an interactive OpenStreetMap map
-- **Strava import progress** — streams live import progress with an aggregate progress bar and prevents overlapping syncs
-- **Analytics second chart** — plots normalized difficulty (Cotacol per 100 km) against moving duration alongside speed vs. Cotacol
-- **Ride categorisation** — mandatory bike type (road/mtb) and ride type (training/indoor/race/transport) per ride, bulk-categorised via a data-quality todo, filtered on library and analytics, and benchmarked excluding indoor/transport
-- **Ride type multiselect filter** — pick multiple ride types at once via a checkbox group on the library and analytics, defaulting to all except transport
+**Data import** - Sync rides from Strava, geo, time, heart rate, power, cadence.
+Rate limit aware and visible progress.
-## Getting started
+**Climb detection** - Auto detect climbs with difficulty. Save official climbs for rematching.
+Climb profile by 100m step.
-```bash
-mise run build
-```
+**Mountain pass** - Match climbs with mountain pass from OSM, and display on map.
-## Usage
+**Rides** - Display rides forever, with filtering and sorting. Categorise all rides.
-```bash
-# Start the web server on http://localhost:8080
-mise run dev-watch
-```
+**Analytics** - Plot speed vs difficulty, difficulty vs duration. Color points by type or season.
-## Options / Configuration
+## Getting started
-| Flag | Description | Default |
-| --- | --- | --- |
-| `-fetch-osm` | Download the France OSM PBF (resumable) | `false` |
-| `-extract-osm` | Extract mountain passes from a France OSM PBF | `""` |
-| `-backfill` | Recompute and persist all ride computed values | `false` |
-| `-backtest` | Benchmark computed vs stored ride values | `false` |
-| `-pprof` | Expose pprof endpoints at `/debug/pprof` | `false` |
+```bash
+mise run build
+mise run dev-watch # Start the web server on http://localhost:8080
+go run . -fetch-osm # Download the France OSM PBF (resumable)
+go run . -extract-osm france-latest.osm.pbf # Extract mountain passes from a France OSM PBF
+go run . -backfill # Recompute and persist all ride computed values
+go run . -backtest # Benchmark computed vs stored ride values
+go run . -pprof # Expose pprof endpoints at `/debug/pprof`
+```
Configuration is stored in `config.yaml`. Start from `config.example.yaml` and set the Strava credentials before launching the server.
@@ -68,34 +45,62 @@ Standard apps are rate-limited to 100 calls per 15 minutes and 1,000 per day.
## Architecture
- Go 1.27; SQLite via `mattn/go-sqlite3`
-- `ride` — GPX parsing, climb detection, difficulty scores (KOM + Cotacol), and SQLite persistence for imported ride metadata and versioned computed values
-- `mountain_pass` — OSM PBF extraction (`mountain_pass=yes` / `natural=mountain_pass` nodes), pass storage and querying, and crossings/climb matching
+- `geo` — coordinate distance, nearest-point, and shared `Interval` primitives; dependency-free
+- `climb` — climb detection and scoring. A `Route` is the geometry-only trace (distances, elevations, coords), and `Detect` returns `[]DetectedClimb` (a climb plus its ride-relative start index). Provides slope and Cotacol difficulty scoring. Deliberately decoupled from `ride`
+- `ride` — GPX parsing and the ride value (embeds `climb.Route` for geometry, adding timestamps and telemetry), moving-time metrics, and SQLite persistence for imported ride metadata and versioned computed values
+- `official_climb` — matches official climbs against a route by endpoint coordinates
+- `mountain_pass` — OSM PBF extraction (`mountain_pass=yes` / `natural=mountain_pass` nodes), pass storage and querying, and crossings/climb matching on `climb.Climb`
+- `rideanalysis` — orchestrates a ride into analyzed climbs and mountain-pass crossings
- `strava` — OAuth2 client returning activity metadata and GPX data
- `config` — typed YAML configuration and atomic persistence
- `web` — HTTP server, OAuth callback, sync orchestration, and templ pages
- `cli` — command handlers for data import, backfill, and benchmarking
+- test builders `internal/routetest`, `internal/ridetest`, `internal/dbtest`
```mermaid
flowchart TB
%% Arrow X --> Y means: X depends on Y
main["main (bootstrap: main.go)"]
-
- ride["ride (GPX parsing, climbs, Cotacol, persistence)"]
- mpass["mountain_pass (OSM PBF, crossings)"]
+ cli["cli (import, backfill, benchmark)"]
+ web["web (HTTP + templ, sync)"]
+ rideanalysis["rideanalysis (climbs + crossings)"]
+ ride["ride (GPX parsing, Ride value, metrics, persistence)"]
+ climb["climb (Route, detection, Cotacol)"]
+ official_climb["official_climb (official climb matching)"]
+ mpass["mountain_pass (OSM PBF, crossings, matching)"]
+ geo["geo (distance, nearest, Interval)"]
strava["strava (OAuth, List, Get)"]
config["config (YAML)"]
- web["web (HTTP + templ)"]
- cli["cli (import, backfill, benchmark)"]
main --> cli
+ main --> config
+ cli --> web
cli --> ride
cli --> mpass
- cli --> web
- mpass --> ride
+ cli --> config
+
+ web --> rideanalysis
web --> strava
web --> ride
+ web --> mpass
+ web --> official_climb
web --> config
+ web --> climb
+
+ rideanalysis --> ride
+ rideanalysis --> climb
+ rideanalysis --> mpass
+ rideanalysis --> official_climb
+
+ ride --> climb
+
+ mpass --> climb
+ mpass --> geo
+
+ official_climb --> geo
+
+ config --> official_climb
```
## Research