Persist SDNN alongside RMSSD (5-min index); fix HealthKit SDNN export mislabel (rebase of #475) - #1334
Merged
Conversation
… mislabel Rebased onto current main: migration renumbered v32→v37-daily-avg-sdnn (upstream advanced to v36 while this branch was open); grdbMigrations lists updated in both schema_oracle.json copies. Logic unchanged. avgHrv keeps RMSSD; new nullable dailyMetric.avgSdnn carries the Task Force 5-min SDNN index. HealthKit write-back now exports avgSdnn under .heartRateVariabilitySDNN (falling back to avgHrv only when SDNN absent). Android twin pending; column registered as a documented one-directional divergence (daily-avg-sdnn-ios-only).
DX23876
added a commit
to DX23876/noop
that referenced
this pull request
Aug 15, 2026
51 commits since the 10.0.0 release point. The android/ tree they still carry is dropped wholesale as usual (74 delete/modify conflicts), leaving ~90 files that actually apply here. Both trees grew personalized HR zones in the same week (ryanbr#531 upstream), down to naming the same `hrZoneSet` / `hasCustomHRZones` properties. This fork's version stays: it has percent AND bpm modes, its own editor screen, coach integration and the boundary-rounding fix, where upstream's is bpm-only with the editor inline in Settings. Upstream's `profile.hrZoneThresholds` backup key is kept whitelisted and imported into the `.bpm` mode when this fork's own keys are absent, so a `.noopbak` written by their build — or a wearer migrating from it — does not silently lose its bands. Read-only and idempotent; an edit made here always wins. Taken from upstream where their answer was better or theirs to make: - ryanbr#1334 SDNN export: HealthKit's HRV field IS SDNN, so the Apple Health export now writes the stored 5-min SDNN index instead of recomputing a whole-night SD at export time, which reads 2-3x high against Apple's short-window samples. The `avgHrv` fallback is NOT taken — RMSSD under an SDNN label is the mislabel this is fixing. - ryanbr#837 charge/sync indicator in the Liquid Today header, with the fork's coach button re-seated at the head of the cluster. `LiquidHeaderMetrics.control` is now an alias for the design system's `compactControlSize`: the indicator sizes its ring off that token, and one 30pt control beside 36pt siblings reads as a mistake. - ryanbr#518 HRV tuning moved out of the always-visible Strap card into Advanced, with a new `.hrv` search section so the in-app field still finds it. - ryanbr#1331 respiratory carry, staleness-bounded, alongside the existing per-field SpO2 / skin-temperature carries. - ryanbr#1284 / ryanbr#1325 sleep dedup + edit-owner resolution, where both trees had independently fixed the same report. - Polish localization, 10.1.0, and the v37+ migrations. Kept here: the fork's Today hero palette and typography, the search-aware Advanced disclosure, and the strict R-R trust gate on what may be exported. Upstream's `defaultLowerBounds(maxHR:)` rounding insight survives their API as `HRZones.defaultBpmLowerBounds(maxHR:)`, pinned by a test that walks every integer bpm to prove seeding the editor cannot reclassify a sample. Verified: 1450 StrandAnalytics + 386 WhoopStore + 2074 app tests, macOS and iOS both build, i18n audit exit 0. Polish for this fork's own ~1300 strings is the next commit; the gate does not cover pl yet.
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
… mislabel (ryanbr#1334) Rebased onto current main: migration renumbered v32→v37-daily-avg-sdnn (upstream advanced to v36 while this branch was open); grdbMigrations lists updated in both schema_oracle.json copies. Logic unchanged. avgHrv keeps RMSSD; new nullable dailyMetric.avgSdnn carries the Task Force 5-min SDNN index. HealthKit write-back now exports avgSdnn under .heartRateVariabilitySDNN (falling back to avgHrv only when SDNN absent). Android twin pending; column registered as a documented one-directional divergence (daily-avg-sdnn-ios-only). Co-authored-by: vishk23 <119831996+vishk23@users.noreply.github.com>
ryanbr
pushed a commit
that referenced
this pull request
Aug 22, 2026
Android stored nightly RMSSD but had no five-minute SDNN index, the separate metric iOS has computed and persisted since #1334. This closes the data and schema gap so native Android nights and cross-platform backups carry the same distinct value. `HrvAnalyzer.sdnnIndex` is the Kotlin twin of `HRVAnalyzer.sdnnIndex`: the mean of per-segment sample-SDNN across consecutive 300-second segments, each cleaned with the same range + Malik rejection the nightly path uses, segments under the clean-beat floor skipped, null when none qualifies. It buckets samples in one pass rather than re-filtering the night per segment. That is equivalent because the Swift twin filters to the window BEFORE cleaning, so a segment receives the same intervals in the same order either way -- and it is O(n) instead of O(n x segments). Computed from R-R inside the matched sleep sessions using the same half-open bounds as every other in-bed aggregate. Storage: `DailyMetric.avgSdnn` (nullable), Room 31 -> 32 with one additive column, appended last so the entity order matches ALTER TABLE. Existing rows stay null; there is deliberately no RMSSD-to-SDNN backfill, because avgHrv is RMSSD for native and Health Connect rows and relabelling it would fabricate the metric. Apple Health's source statistic IS SDNN, so it fills both avgHrv (existing recovery behaviour) and avgSdnn, matching what AppleHealthImport.swift already does on iOS. Health Connect exposes RMSSD and deliberately leaves avgSdnn null. The value is preserved through the merge, coalescing and spine-union paths, where a Kotlin data class would otherwise default it to null in silence. Removes the `daily-avg-sdnn-ios-only` entry from both copies of the shared schema oracle. That entry named this exact work as its closing condition. Thanks to @bhelm. Verified on the merged tree: 4208 Android tests, 0 failures, 6 intentional skips (--no-build-cache --rerun-tasks, so a stale generated DAO cannot mask the schema change); SchemaOracleTest and DailySdnnMigrationTest green; test (WhoopStore) covers the Swift half of the oracle. Migration verification is static plus Room KSP schema verification: the repository has no Room runtime migration harness, so an on-device 31-to-32 upgrade is NOT claimed as exercised.
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.
Maintainer rebase of #475 (@vishk23) onto current
main— the fork branch could not be pushed to directly (maintainer-edit push returned 403), so the author's commit is carried here unchanged in authorship.Only change vs #475: the migration is renumbered
v32-daily-avg-sdnn→v37-daily-avg-sdnn(upstream advanced tov36while the branch was open), and thegrdbMigrationslist in bothschema_oracle.jsoncopies is updated to match (v37 at position 37, both files byte-identical). All logic —HRVAnalyzer.sdnnIndex, theavgSdnncolumn/upsert/read, the HealthKit export fix, and the documenteddaily-avg-sdnn-ios-onlydivergence — is unchanged from #475.Closes #475.
Original #475 summary:
DailyMetric.avgHrvholds RMSSD for strap rows but SDNN for Apple-Health rows, and the HealthKit write-back exported the strap's RMSSD under.heartRateVariabilitySDNN, mislabeling it in the user's permanent Apple Health record. This persists a real 5-min SDNN index (avgSdnn) and exports that instead, falling back toavgHrvonly when SDNN is absent.