Conversation
|
Reviewed the draft. The load-bearing claim checks out, and there are two connections worth making. The replacement claim holds"Keys don't change, so the next write-back replaces the wake-stamped samples" is the part that decides whether existing users get a clean migration or two samples per night, so I checked it rather than took it. The delete predicate is let byKey = HKQuery.predicateForObjects(withMetadataKey: HKMetadataKeyExternalUUID, allowedValues: keys)
let pred = NSCompoundPredicate(andPredicateWithSubpredicates: [bySource, byKey])So moving the instant cannot orphan the old sample: it is found by key wherever it sits, deleted, and rewritten at the midpoint. Confirmed. Factoring This is half of #2264@andremiliano filed #2264 on the same write-back, and its first complaint is exactly this: "one sample per night, stamped at the wake instant ... it sits exactly at the sleep session's end boundary rather than inside the night". You have fixed the placement. The other half is DENSITY: still one zero-length sample per night, where an Apple Watch writes many short-window samples through the night. Worth cross-referencing so the two are not solved twice or, worse, solved in ways that disagree about which instant a night's value belongs to. No conflict with what just landed
The disposition is the right mechanismThe Staying a draft is the right callHolding until Apple Health on the device actually shows the vitals inside the night is exactly right. The reason this bug existed is that a plausible-looking timestamp was never checked against what a reader does with it, and the fix has the same failure mode until someone looks at real Health data. One question while you are there: does a reader that keys off the sleep window accept a sample at the exact midpoint of a night that was BRIDGED across brief wakes, where the midpoint could land inside a wake gap rather than inside a sleep segment? The midpoint of the span is not necessarily inside any of its segments. |
…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
2dd04ab to
e343538
Compare
|
Thanks for checking the key-based replacement rather than taking it on trust. On the bridged-night question: yes, it could. The plan entry is the bridged night, and its On #2264: agreed, this is the placement half. Density isn't attempted here. Beat-to-beat data reaches Health through #2282 (heartbeat series), which a reader can window itself. If denser scalar vitals are still wanted, I'd do it as a follow-up that picks the night through the same Rebased onto current main (the branch point was 17 commits behind). The Staying a draft until Health on the device shows the vitals inside the night. I'll post that evidence here. |
…I type-checker accepts
What this PR does
Resting HR, HRV, SpO₂ and respiratory rate were each written to Apple Health as a single instant at the day's latest wake. That timestamp sits exactly on the sleep window's edge, so a reader that picks vitals by that window can't count on seeing it. Bevel, for example, shows no Recovery when HRV or resting HR isn't "captured during your sleep window". The latest wake could also come from a nap after the main sleep.
They're now stamped at the midpoint of the longest bridged night that ended on that day. That night comes from the same plan the sleep write exports:
writeSleep's grouping is factored intosleepPlan(sessions:)so the two can't disagree. Keys don't change, so the next write-back replaces the wake-stamped samples.HealthWriteback.vitalsInstantByDayis a pure function, covered inHealthWritebackTests. It carries aplatform_specificparity disposition, since only the HealthKit write-back calls it.Type of change
How it was tested
swift testinPackages/StrandImport: 329 tests pass, including 4 new ones (midpoint, longest night beats a later nap, per-day attribution, empty span).parity_ratchet.py --offlineshows 0 errors.NOOPiOSbuilds for device and is installed on an iPhone 16 with a WHOOP 5.0. Draft until Apple Health on that phone shows the vitals inside the night.Checklist
swift testinPackages/StrandImport)docs/CONTRIBUTING.mdStrand.xcodeproj/) or any secrets/keystoresRelated issues
Refs #1021