mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
feat(measurements): add per-bucket heart-rate min/max to the hourly contract
The v1.19.0 hourly heart-rate bucket (`stats:HKQuantityTypeIdentifierHeartRate:<UTC-hour>`) shipped avg-only: one PULSE row carrying the hour's mean bpm. Add the hour's min/max so a client can render the intra-hour range without re-uploading raw samples. - Two nullable columns on `measurements` (`value_min` / `value_max`), migration 0188. Set only on a well-formed hourly HR bucket row; null for per-sample readings, per-day cumulative totals, and manual entries. - Batch ingest threads the spread through the same unit conversion as the average and persists / overwrites it alongside `value` on the bucket row, pinning it null everywhere else. - The series reader returns the spread for `kind=pulse` (null on a per-sample PULSE row); the OpenAPI Zod contract documents both fields on the batch entry and the series point. The avg path is unchanged.
This commit is contained in:
1 parent
02ce177ac4
commit
868dfb72bd
8 files changed
+286
-3
No files matched your search
@@ -30,7 +30,21 @@ const batchEntrySchema = z
|
||||
.number()
|
||||
.finite()
|
||||
.describe(
|
||||
"Raw HealthKit reading in Apple's native unit; the server applies any canonical scaling at ingest. For an hourly heart-rate bucket (see `externalId`) this is the hour's AVERAGE bpm — v1.19.0 ships avg-only; min/max are a documented fast-follow.",
|
||||
"Raw HealthKit reading in Apple's native unit; the server applies any canonical scaling at ingest. For an hourly heart-rate bucket (see `externalId`) this is the hour's AVERAGE bpm; the hour's spread rides `valueMin` / `valueMax`.",
|
||||
),
|
||||
valueMin: z
|
||||
.number()
|
||||
.finite()
|
||||
.optional()
|
||||
.describe(
|
||||
"v1.19.2 (iOS #34 extension) — the hour's MINIMUM bpm for an hourly heart-rate bucket (see `externalId`). Persisted ONLY on a well-formed `stats:HKQuantityTypeIdentifierHeartRate:<hour>` row; ignored (stored null) on every other entry. Omit on a pre-v1.19.2 client — the bucket keeps the avg-only contract.",
|
||||
),
|
||||
valueMax: z
|
||||
.number()
|
||||
.finite()
|
||||
.optional()
|
||||
.describe(
|
||||
"v1.19.2 (iOS #34 extension) — the hour's MAXIMUM bpm for an hourly heart-rate bucket (see `externalId`). Persisted ONLY on a well-formed `stats:HKQuantityTypeIdentifierHeartRate:<hour>` row; ignored (stored null) on every other entry. Omit on a pre-v1.19.2 client — the bucket keeps the avg-only contract.",
|
||||
),
|
||||
unit: z.string().min(1).max(60),
|
||||
startDate: z.iso.datetime({ offset: true }),
|
||||
@@ -407,6 +421,20 @@ const seriesPointSchema = z.object({
|
||||
.describe(
|
||||
"Per-stage hours for a `kind=sleep` night (CORE/DEEP/REM/…); null/absent for non-sleep kinds.",
|
||||
),
|
||||
valueMin: z
|
||||
.number()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
"v1.19.2 (iOS #34 extension) — per-point MINIMUM for `kind=pulse`. On an aggregated hourly heart-rate bucket `value` is the hour's average and this is the hour's low; null on a per-sample PULSE row and absent for every other kind.",
|
||||
),
|
||||
valueMax: z
|
||||
.number()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
"v1.19.2 (iOS #34 extension) — per-point MAXIMUM for `kind=pulse`. On an aggregated hourly heart-rate bucket `value` is the hour's average and this is the hour's high; null on a per-sample PULSE row and absent for every other kind.",
|
||||
),
|
||||
});
|
||||
|
||||
const seriesResponse = z
|
||||
|
||||
Reference in new issue
Block a user