Conversation
…t at its wake Resting HR, HRV, SpO2 and respiratory rate were each written as one instant at the day's latest wake, which sits exactly on the sleep window's boundary. A reader that selects them by that window cannot count on a boundary sample: Bevel, for one, shows no Recovery when HRV or resting HR is not captured during the sleep window. The latest wake could also belong to a nap after the main sleep. They are now stamped at the midpoint of the longest bridged night that woke on that day, from the same night plan the sleep write exports (factored out of `writeSleep` so the two cannot disagree). Keys are unchanged, so the next write-back replaces the wake-stamped samples. `vitalsInstantByDay` is pure and covered in `HealthWritebackTests`.
…its midpoint falls in a wake gap
Iskrata
force-pushed
the
feat/health-heartbeat-series
branch
from
September 17, 2026 09:24
a005895 to
9c36572
Compare
5 tasks
…I type-checker accepts
…th as heartbeat series NOOP exported HRV to Apple Health only as one nightly SDNN value. A reader that computes its own HRV has nothing to compute from: Bevel's Recovery, for one, reads HRV from beat-to-beat measurements inside the sleep window, and shows no Recovery without it. Each finished night's R-R intervals are now written as `HKHeartbeatSeriesSample`s in 5-minute chunks. Beats are laid out one interval after the previous rather than on their whole-second row stamps, and a row that disagrees with that by more than 2 s starts a gap (`HealthWriteback.heartbeatSeriesPlan`). Only nights whose beats clear both gates NOOP applies before trusting the same statistic itself are written — no over-counted beats, and individually accurate values rather than a record period decomposed across one timestamp (`HRVAnalyzer.beatSeriesIsExportable`) — so no reader is handed intervals NOOP would refuse. A night is rewritten only when its fingerprint moves, and cleared if it stops being exportable; series are immutable, so a rewrite deletes the night's series by key under our own `HKSource` first. The fingerprint is recorded only once the whole night is written, so a failure mid-night retries. Heartbeat series joins the share types, so an existing install is asked once, in the foreground, through the existing re-ask for newly added write types. Health Connect has no beat-to-beat record type, so the new declarations carry `platform_specific` parity dispositions.
…so the heartbeat series stays in order
Iskrata
force-pushed
the
feat/health-heartbeat-series
branch
from
September 17, 2026 12:13
9c36572 to
f616b27
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
NOOP's only HRV export to Apple Health was a single nightly SDNN value. A reader that computes its own HRV gets nothing to work from. Bevel's Recovery, for example, computes HRV from beat-to-beat measurements inside the sleep window, and shows no Recovery when there are none.
This PR writes each finished night's R-R intervals as
HKHeartbeatSeriesSamples, in 5-minute chunks:HealthWriteback.heartbeatSeriesPlan): each beat lands one interval after the previous one, not on its whole-second row stamp. A row more than 2 s off from that prediction starts a gap (precededByGap).HRVAnalyzer.beatSeriesIsExportable): a night is exported only if its beats pass both checks NOOP already applies before trusting this statistic itself.successiveDiffIsTrustworthyrules out over-counted beats;beatValuesAreTrustworthyrules out a record period decomposed across one timestamp. No reader gets intervals NOOP itself would refuse.heartbeatSyncPlan): a night is rewritten only when its fingerprint changes, and cleared if it stops being exportable. Series samples are immutable, so a rewrite first deletes that night's series byHKMetadataKeyExternalUUID(noop:heartbeat:<startTs>), scoped to our ownHKSource. The fingerprint is saved only after the whole night is written, so a failure partway retries.HKSeriesType.heartbeat()is added to the share types. Existing installs get asked once, in the foreground, through the existing re-ask for newly added write types.Health Connect has no beat-to-beat record type, so the new declarations carry
platform_specificparity dispositions.Type of change
How it was tested
swift testinPackages/StrandImport(329, including beat layout, gaps, 5-minute split, sync plan) andPackages/StrandAnalytics(2024, including newHRVBeatSeriesExportTests: clean train accepted, over-counted and decomposed nights refused).doc_comment_lint.pyclean.NOOPiOSbuilds for device and is installed on an iPhone 16 with a WHOOP 5.0. Draft until an overnight run shows the night's heartbeat series in Apple Health (Heart → Heart Rate Variability → beat-to-beat) and Bevel computing Recovery from it.Checklist
StrandImport,StrandAnalytics)docs/CONTRIBUTING.mdStrand.xcodeproj/) or any secrets/keystoresRelated issues
None open for this; related HRV-trust context in #1118.