<feed xmlns='http://www.w3.org/2005/Atom'>
<title>biking_home/official_climb, branch main</title>
<subtitle>Unnamed repository; edit this file 'description' to name the repository.
</subtitle>
<id>https://git.kagamino.dev/biking_home/atom?h=main</id>
<link rel='self' href='https://git.kagamino.dev/biking_home/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/'/>
<updated>2026-09-25T06:57:10Z</updated>
<entry>
<title>Apply go fix modernizations</title>
<updated>2026-09-25T06:57:10Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-09-25T06:57:10Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=0eadc1fde03153ff4b60655f15ed86367dc6a3d3'/>
<id>urn:sha1:0eadc1fde03153ff4b60655f15ed86367dc6a3d3</id>
<content type='text'>
Embedded composite literals and min/max clamp idioms, as rewritten by the go fix step of mise check.
</content>
</entry>
<entry>
<title>Chart power vs time for official climb attempts</title>
<updated>2026-09-25T06:46:58Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-09-25T06:46:58Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=077aa1700fbcaf3f499c2178e0e58e89ae9dcaf8'/>
<id>urn:sha1:077aa1700fbcaf3f499c2178e0e58e89ae9dcaf8</id>
<content type='text'>
Snapshot average power per attempt in a nullable average_power_w column (backfilled from stored rides), plot each powered attempt on the climb page as duration vs average watts with ride hover and click-through, and show avg power in the attempts table.
</content>
</entry>
<entry>
<title>Match official climbs against their recorded sample</title>
<updated>2026-09-24T06:37:33Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-09-24T06:37:33Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=90614736f2a0c556f31eca5361b3c032f5f309f6'/>
<id>urn:sha1:90614736f2a0c556f31eca5361b3c032f5f309f6</id>
<content type='text'>
An official climb matched on endpoint coordinates alone. On a loop ridden
the other way the route meets the climb's top before its bottom, so pairing
each start with the first end inside its lap window credited a 5.9 km
passage as the 850 m Chemin de la Tour Pey Blanc, and a 49 km one on
another ride.

Climbs now keep the sample of the segment they were created from: the
recorded length and elevation gain of the selected route points, captured
by the ride detail and preview handlers and stored NOT NULL. Matching takes
the whole route and drops any candidate outside ±50% of that sample on
either axis, and attempts are refreshed by upsert and prune so re-analyzing
a ride keeps the recorded-at stamp of the passages that survive.

The backfill leaves the web package: backfill.Run recomputes ride values
then official climb attempts as a single `go run . backfill` command
(climbs-backfill is gone), rideanalysis owns the attempt snapshot, and the
mountain-pass loading margin is shared. Applied to the stored rides it
stamps 78 samples, drops the four bogus attempts and leaves ride 800 with
its three Pont Rout passages.
</content>
</entry>
<entry>
<title>Persist official climb attempts with climbs pages</title>
<updated>2026-09-13T19:55:59Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-09-13T19:55:59Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=e3d0beee5be3dd2854b0f4fc10905a4fcdc08f47'/>
<id>urn:sha1:e3d0beee5be3dd2854b0f4fc10905a4fcdc08f47</id>
<content type='text'>
Record one attempt row per (ride, climb, occurrence) when a ride detail
loads, snapshotting distance, elevation gain, slope, cotacol and attempt
timestamps; a climbs-backfill CLI command seeds all existing rides.

New /climbs list page aggregates attempts (medians computed in Go, best
time as min duration) with sortable columns, and /climbs/{id} shows the
median scalar stats, a median normalized elevation profile (50 steps
over each attempt's own length), the full-precision recorded route of
the median-distance attempt on an OSM map, and every attempt with time
at second precision.
</content>
</entry>
<entry>
<title>Extract climb detection into a dedicated climb package</title>
<updated>2026-08-27T09:21:17Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-08-27T09:21:17Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=825cf388b55a6c8441e02149749bb6e2968477c0'/>
<id>urn:sha1:825cf388b55a6c8441e02149749bb6e2968477c0</id>
<content type='text'>
Move climb detection, slope, and Cotacol scoring out of ride into a new
climb package that operates on a geometry-only Route (distances, elevations,
coords). Ride embeds climb.Route and keeps its timing and telemetry columns.

- climb.Detect returns []DetectedClimb (Climb + ride-absolute Start).
- Climb owns its sliced geometry; indices are local, with Start reported at
  the detection boundary for absolute position.
- Detected bounds use core.Interval; freeRegions claim suppression fixed.
- ride no longer builds slices: ClimbFromDist/ClimbFromIndexes/climbFrom and
  ride.Climb/Interval/Range are removed; rideanalysis, mountain_pass MatchClimb,
  and web views are rewired to climb types.
- New internal/routetest builds climb.Route from the ride DSL; ridetest reuses
  it and adds timestamps and telemetry.
- Tests moved to climb (synthetic detection and difficulty) and kept in ride
  for GPX-example integration; ride.comparison detection coverage retained.
- mountain_pass.nearestCrossing now reuses geo.Nearest.
</content>
</entry>
<entry>
<title>Migrate cross-package ride test fixtures to the ridetest DSL</title>
<updated>2026-08-25T11:26:22Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-08-25T11:26:22Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=e73b7a9ccb4bc61e54d1fdc61b5954879c637d00'/>
<id>urn:sha1:e73b7a9ccb4bc61e54d1fdc61b5954879c637d00</id>
<content type='text'>
Replace raw FromColumns 4-array blocks in official_climb, mountain_pass
and web test rides with ridetest.MustRide strings. Add a Heading option to
ridetest for routes whose points are spaced differently per kilometer
(e.g. the mountain_pass expanded-bounds route at 0.1 degrees/km).

Lap, direction and cluster tests in official_climb keep FromColumns: the
DSL generates monotonic northeast coords and cannot express per-point
loops or reversals.
</content>
</entry>
<entry>
<title>Remove per-index ride accessors now dead after the component migration</title>
<updated>2026-08-25T09:34:27Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-08-25T09:34:27Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=e54e2b313a37c2d2af4e3e537ad4e64e8c816e86'/>
<id>urn:sha1:e54e2b313a37c2d2af4e3e537ad4e64e8c816e86</id>
<content type='text'>
The DistanceM, ElevationM, Coord, Timestamp and optional metric index
accessors (HeartRateBpm, CadenceRpm, PowerW) are no longer used by any
production code now that algorithms and consumers read ride components.
Tests that asserted metric parsing and coordinates now read the component
slices (Distances, Coords, HeartRates, Cadences, Powers).
</content>
</entry>
<entry>
<title>Expose ride data as components and make algorithms consume only the slices they read</title>
<updated>2026-08-25T09:24:51Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-08-25T09:24:51Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=ff3cd6248f2b78dc76f1ce8cc2337907188fd401'/>
<id>urn:sha1:ff3cd6248f2b78dc76f1ce8cc2337907188fd401</id>
<content type='text'>
Ride now exposes its columns as component getters (Distances, Elevations,
Coords, Timestamps, and the optional Powers, Cadences, HeartRates) with the
coords field unexported. difficultyScore, movingTime, Slope and the climb
detection internals are pure functions over the specific slices they use,
and Climb carries only the component slices it needs instead of a whole
Ride.
</content>
</entry>
<entry>
<title>Match official climbs once per lap on looping rides</title>
<updated>2026-08-23T10:12:59Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-08-23T10:12:59Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=e5e7870eafbfc80f4668d2c0d0995149ddc2f60a'/>
<id>urn:sha1:e5e7870eafbfc80f4668d2c0d0995149ddc2f60a</id>
<content type='text'>
Official climb matching now returns all non-overlapping occurrences
per ride instead of only the globally nearest pair. Each lap is
detected by clustering contiguous points within the endpoint radius
and pairing each start cluster with the first end cluster inside its
window, preserving direction. The ride analysis aggregates all matches
and sorts claimed ranges before excluding them from auto-detection.

Fixes the case where a looping ride only showed one instance of a
saved climb while the user expected one per repetition.
</content>
</entry>
<entry>
<title>feat: bound climb detection by matched official climbs</title>
<updated>2026-08-21T13:30:51Z</updated>
<author>
<name>Martin Kagamino Lehoux</name>
<email>martin@lehoux.net</email>
</author>
<published>2026-08-21T13:30:51Z</published>
<link rel='alternate' type='text/html' href='https://git.kagamino.dev/biking_home/commit/?id=5e3d39cda5250cb55bc8324bcf96b0ab4b36c47f'/>
<id>urn:sha1:5e3d39cda5250cb55bc8324bcf96b0ab4b36c47f</id>
<content type='text'>
Detected climbs no longer absorb an official climb's range: official climbs
matched to the route by coordinates claim their stored start/end range, and
climb detection now runs only in the free regions around them. On ride 263 a
single absorbed Cat 2 ascent is split so creating a first-half official climb
lets the second half be detected on its own.

- ride: add Range, freeRegions, and DetectClimbs(claimed); exclude claimed
  ranges so a climb never overlaps one
- official_climb: MatchClimb now bounds-matches purely by coordinates without
  needing a detected climb; extract geo.Nearest shared helper
- rideanalysis: Analyze builds claimed climbs from matched official climbs and
  detects only in free regions; render official vs unmatched climbs separately
</content>
</entry>
</feed>
