mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(sleep): stamp Withings sleep segments at the END, not the START
Withings sleep sync stamped each segment's measuredAt with its startdate, but every reader treats measuredAt as the segment END and resolves the span as start = end − duration. Each night was therefore rendered one segment-length earlier than reality. Stamp the enddate so the span lands at its true clock time.
This commit is contained in:
1 parent
bd467d0c4d
commit
7ad0b9c4f4
2 files changed
+17
-7
No files matched your search
@@ -194,7 +194,7 @@ describe("syncUserSleep — segment writes + idempotency", () => {
|
||||
expect(firstDuration.data.unit).toBe("minutes");
|
||||
});
|
||||
|
||||
it("converts startdate (unix seconds) to a Date in measuredAt", async () => {
|
||||
it("stamps measuredAt at the segment END (enddate, unix seconds → Date)", async () => {
|
||||
installFetchMock([
|
||||
{ startdate: 1715000000, enddate: 1715003600, state: 2, id: 1 },
|
||||
]);
|
||||
@@ -205,7 +205,10 @@ describe("syncUserSleep — segment writes + idempotency", () => {
|
||||
const arg = vi.mocked(prisma.measurement.create).mock.calls[0][0] as {
|
||||
data: { measuredAt: Date };
|
||||
};
|
||||
expect(arg.data.measuredAt.getTime()).toBe(1715000000 * 1000);
|
||||
// measuredAt is the segment END — every reader treats it as the END and
|
||||
// resolves the span as start = end − duration. Stamping the START shifted
|
||||
// the night one segment-length earlier.
|
||||
expect(arg.data.measuredAt.getTime()).toBe(1715003600 * 1000);
|
||||
});
|
||||
|
||||
it("skips state 4 (synthetic marker) without throwing", async () => {
|
||||
|
||||
@@ -26,10 +26,13 @@
|
||||
* when Withings re-emits the night with adjusted segment boundaries.
|
||||
*
|
||||
* Date semantics: each segment's `measuredAt` is the segment's
|
||||
* `startdate` (unix seconds → `Date`). The duration in minutes lands
|
||||
* in `value`. The HealthLog analytics aggregator already groups
|
||||
* stage rows under their parent night via the per-night `dayKey`
|
||||
* helper, so the segment-level timestamp is the canonical sort key.
|
||||
* `enddate` (unix seconds → `Date`). Every reader treats `measuredAt`
|
||||
* as the segment END and resolves the span as `start = end − duration`
|
||||
* (`sleep-night.ts` `segmentOf`); stamping the START shifted the whole
|
||||
* night one segment-length earlier. The duration in minutes lands in
|
||||
* `value`. The HealthLog analytics aggregator groups stage rows under
|
||||
* their parent night via the per-night `dayKey` helper, so the
|
||||
* segment-level END timestamp is the canonical sort key.
|
||||
*/
|
||||
import type { MeasurementType, SleepStage } from "@/generated/prisma/client";
|
||||
|
||||
@@ -258,7 +261,11 @@ export async function syncUserSleep(
|
||||
segmentIndex++;
|
||||
continue;
|
||||
}
|
||||
const measuredAt = new Date(segment.startdate * 1000);
|
||||
// `measuredAt` is the segment END (`enddate`). Every reader treats
|
||||
// `measuredAt` as the END instant — `segmentOf` resolves the span as
|
||||
// `start = measuredAt − value·60_000` (sleep-night.ts). Stamping the START
|
||||
// here shifted the whole night one segment-length earlier; END is correct.
|
||||
const measuredAt = new Date(segment.enddate * 1000);
|
||||
const durationSec = Math.max(0, segment.enddate - segment.startdate);
|
||||
// Canonical SLEEP_DURATION unit is minutes (Apple Health alignment,
|
||||
// v1.4.23 schema note). Round so re-sync doesn't flip values on
|
||||
|
||||
Reference in new issue
Block a user