Skip to content

HeartRateRecovery accepts accumulated disconnected high-intensity segments #55

Closed
ryanbr/noop
#1901

Description

@bhelm

Summary

HeartRateRecovery.calculate requires 120 seconds of sustained Zone-3-or-higher effort near workout end, but both Swift and Kotlin currently add eligible durations across gaps longer than the 10-second continuity cap. Several short disconnected segments can therefore satisfy the gate even though no continuous segment reaches 120 seconds.

Deterministic reproduction

Use three 40-second high-HR segments, each sampled every 10 seconds and separated by an 11-second gap:

  • offsets from workout end: -300,-290,-280,-270,-260
  • -249,-239,-229,-219,-209
  • -198,-188,-178,-168,-158
  • all at 145 bpm with maxHR = 200 (70% threshold = 140)
  • cessation samples at -30,-15,0, 130 bpm
  • 1-minute samples at +59,+60,+61, 110 bpm
  • workout range end-300...end

No continuous high-intensity segment exceeds 40 seconds. The expected result is null.

Observed on exact fork meta base 06e3ee7868d18a0af447b10eef712dbc92f52cfd:

  • Swift focused test: XCTAssertNil failed with Result(endHR: 130, after1Minute: Optional(20), after2Minutes: nil, after5Minutes: nil).
  • Kotlin focused test: assertNull failed with Result(endHr=130, after1Minute=20, after2Minutes=null, after5Minutes=null).

Cause

Both sustainedSeconds helpers skip an interval when gap > maximumContinuousGapSeconds, but retain the accumulated total. Later valid intervals resume adding to that same total instead of starting a new continuous segment.

Expected behavior

Eligibility should require at least one continuous qualifying high-intensity segment of 120 seconds. A disqualifying gap or below-threshold interval should break/reset the current segment; disconnected segments must not accumulate.

This is shared cross-platform behavior, not a Swift/Kotlin parity difference. The parity rollout should characterize it explicitly until a separately authorized production fix lands.

Activity

  1. bhelm commented on Sep 7, 2026

    @bhelm
    OwnerAuthor

    Fixed upstream by ryanbr#1901 (merged), which requires continuous effort for heart-rate
    recovery eligibility: the tracker now resets on a gap over cap or a drop below threshold, so
    disconnected segments no longer add up to an eligible bout. Both platforms, with a differential
    case set. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions