Derived metric producers: max speed, average HR, average cadence, normalised power #65

Closed
opened 2026-09-01 17:35:44 +02:00 by robert · 1 comment
robert commented 2026-09-01 17:35:44 +02:00 (Migrated from git.butzei.de)

Goal

Give every FieldId a producer. #58 defines a 22-entry enum; several of those fields have nothing anywhere in the plan that computes them, so they could only ever render --.

Missing producers: MAX_SPEED, AVG_HR, HR_ZONE, AVG_CADENCE, NORM_POWER.

Acceptance criteria

  • MAX_SPEED tracked over the ride, from the arbitrated speed source, ignoring fixes rejected by the accuracy gate in #17
  • AVG_HR accumulated on the phone from the batched HR_SAMPLES uplink (#66), over moving time, consistent with the "stopped" definition in #69
  • HR_ZONE derived from the configured zones in #44; renders -- when zones are unconfigured, never a guessed default
  • AVG_CADENCE over moving time, excluding coasting - specify and document whether zero-cadence samples count, because both conventions exist
  • NORM_POWER by the standard method: 30 s rolling average, fourth power, mean, fourth root; needs at least 30 s of data before it reports anything other than --
  • All five sent under the keys in docs/PROTOCOL.md section 2.2, following the changed-keys-only policy in section 4
  • Values reset on ride start and on lap where the field is lap-scoped
  • Unit tests on the JVM with fixture data, alongside #22

Files

  • companion/.../ride/DerivedMetrics.kt
  • companion/.../ride/SessionState.kt

Notes

See D28. This is the half of the contract rewrite that is code rather than schema.

## Goal Give every `FieldId` a producer. #58 defines a 22-entry enum; several of those fields have nothing anywhere in the plan that computes them, so they could only ever render `--`. Missing producers: `MAX_SPEED`, `AVG_HR`, `HR_ZONE`, `AVG_CADENCE`, `NORM_POWER`. ## Acceptance criteria - [ ] `MAX_SPEED` tracked over the ride, from the arbitrated speed source, ignoring fixes rejected by the accuracy gate in #17 - [ ] `AVG_HR` accumulated on the phone from the batched `HR_SAMPLES` uplink (#66), over moving time, consistent with the "stopped" definition in #69 - [ ] `HR_ZONE` derived from the configured zones in #44; renders `--` when zones are unconfigured, never a guessed default - [ ] `AVG_CADENCE` over moving time, excluding coasting - specify and document whether zero-cadence samples count, because both conventions exist - [ ] `NORM_POWER` by the standard method: 30 s rolling average, fourth power, mean, fourth root; needs at least 30 s of data before it reports anything other than `--` - [ ] All five sent under the keys in docs/PROTOCOL.md section 2.2, following the changed-keys-only policy in section 4 - [ ] Values reset on ride start and on lap where the field is lap-scoped - [ ] Unit tests on the JVM with fixture data, alongside #22 ## Files - `companion/.../ride/DerivedMetrics.kt` - `companion/.../ride/SessionState.kt` ## Notes See D28. This is the half of the contract rewrite that is code rather than schema.
Owner

Closed by PR #104 (merged): MaxSpeedTracker, AverageAccumulator (shared moving-time-excluding engine), AvgHrAccumulator, AvgCadenceAccumulator, deriveHrZone, NormalizedPowerCalculator \u2014 the real Coggan algorithm (30s rolling avg \u2192 4th power \u2192 mean \u2192 4th root), verified against two independent published sources, hand-checked to an exact closed form (100\u00b7\u221a3.5\u2248187.0829W) plus a constant-power identity test. Correctly documented as not excluding stopped time, unlike the HR/cadence accumulators \u2014 the cited algorithm is defined over the whole workout as a plain series, and deviating from a named/cited method needs its own decision, not a silent copy of this codebase's moving-time convention; left for whichever future issue wires a real power meter (#50) to decide.\n\nReal input today: MAX_SPEED only, fed from SpeedPipeline's actual stream (accuracy-gate exclusion proven with an integration test). Honestly un-fed: AVG_HR/HR_ZONE (no live bpm anywhere in companion/, blocked on #66\u2192#16/#4 hardware), AVG_CADENCE (CSC crank fields decoded by #18 but nothing converts them to RPM yet, blocked on #49), NORM_POWER (no BLE Cycling Power client exists, blocked on #50) \u2014 all confirmed by grep before writing, not assumed.\n\nVerified for real: 27+15 new tests across :companion:core and :companion:pebble, individual XML results checked not just the green top-level task; full ./gradlew test and :companion:assembleDebug both green.

Closed by PR #104 (merged): MaxSpeedTracker, AverageAccumulator (shared moving-time-excluding engine), AvgHrAccumulator, AvgCadenceAccumulator, deriveHrZone, NormalizedPowerCalculator \u2014 the real Coggan algorithm (30s rolling avg \u2192 4th power \u2192 mean \u2192 4th root), verified against two independent published sources, hand-checked to an exact closed form (100\u00b7\u221a3.5\u2248187.0829W) plus a constant-power identity test. Correctly documented as *not* excluding stopped time, unlike the HR/cadence accumulators \u2014 the cited algorithm is defined over the whole workout as a plain series, and deviating from a named/cited method needs its own decision, not a silent copy of this codebase's moving-time convention; left for whichever future issue wires a real power meter (#50) to decide.\n\n**Real input today**: MAX_SPEED only, fed from SpeedPipeline's actual stream (accuracy-gate exclusion proven with an integration test). **Honestly un-fed**: AVG_HR/HR_ZONE (no live bpm anywhere in companion/, blocked on #66\u2192#16/#4 hardware), AVG_CADENCE (CSC crank fields decoded by #18 but nothing converts them to RPM yet, blocked on #49), NORM_POWER (no BLE Cycling Power client exists, blocked on #50) \u2014 all confirmed by grep before writing, not assumed.\n\nVerified for real: 27+15 new tests across :companion:core and :companion:pebble, individual XML results checked not just the green top-level task; full `./gradlew test` and `:companion:assembleDebug` both green.
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
robert/PedalPebble#65
No description provided.