@nativesquare/soma 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.d.ts +7 -128
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +31 -112
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +80 -4
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +135 -261
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/garmin/private.d.ts +475 -0
- package/dist/component/garmin/private.d.ts.map +1 -0
- package/dist/component/garmin/private.js +1614 -0
- package/dist/component/garmin/private.js.map +1 -0
- package/dist/component/garmin/public.d.ts +155 -0
- package/dist/component/garmin/public.d.ts.map +1 -0
- package/dist/component/garmin/public.js +787 -0
- package/dist/component/garmin/public.js.map +1 -0
- package/dist/component/garmin/schemas/activity.d.ts +94 -0
- package/dist/component/garmin/schemas/activity.d.ts.map +1 -0
- package/dist/component/garmin/schemas/activity.js +27 -0
- package/dist/component/garmin/schemas/activity.js.map +1 -0
- package/dist/component/garmin/schemas/activityDetails.d.ts +146 -0
- package/dist/component/garmin/schemas/activityDetails.d.ts.map +1 -0
- package/dist/component/garmin/schemas/activityDetails.js +27 -0
- package/dist/component/garmin/schemas/activityDetails.js.map +1 -0
- package/dist/component/garmin/schemas/bloodPressure.d.ts +38 -0
- package/dist/component/garmin/schemas/bloodPressure.d.ts.map +1 -0
- package/dist/component/garmin/schemas/bloodPressure.js +27 -0
- package/dist/component/garmin/schemas/bloodPressure.js.map +1 -0
- package/dist/component/garmin/schemas/bodyCompositions.d.ts +42 -0
- package/dist/component/garmin/schemas/bodyCompositions.d.ts.map +1 -0
- package/dist/component/garmin/schemas/bodyCompositions.js +27 -0
- package/dist/component/garmin/schemas/bodyCompositions.js.map +1 -0
- package/dist/component/garmin/schemas/dailies.d.ts +98 -0
- package/dist/component/garmin/schemas/dailies.d.ts.map +1 -0
- package/dist/component/garmin/schemas/dailies.js +27 -0
- package/dist/component/garmin/schemas/dailies.js.map +1 -0
- package/dist/component/garmin/schemas/epochs.d.ts +54 -0
- package/dist/component/garmin/schemas/epochs.d.ts.map +1 -0
- package/dist/component/garmin/schemas/epochs.js +27 -0
- package/dist/component/garmin/schemas/epochs.js.map +1 -0
- package/dist/component/garmin/schemas/healthSnapshot.d.ts +48 -0
- package/dist/component/garmin/schemas/healthSnapshot.d.ts.map +1 -0
- package/dist/component/garmin/schemas/healthSnapshot.js +27 -0
- package/dist/component/garmin/schemas/healthSnapshot.js.map +1 -0
- package/dist/component/garmin/schemas/hrvSummary.d.ts +40 -0
- package/dist/component/garmin/schemas/hrvSummary.d.ts.map +1 -0
- package/dist/component/garmin/schemas/hrvSummary.js +27 -0
- package/dist/component/garmin/schemas/hrvSummary.js.map +1 -0
- package/dist/component/garmin/schemas/manuallyUpdatedActivities.d.ts +94 -0
- package/dist/component/garmin/schemas/manuallyUpdatedActivities.d.ts.map +1 -0
- package/dist/component/garmin/schemas/manuallyUpdatedActivities.js +27 -0
- package/dist/component/garmin/schemas/manuallyUpdatedActivities.js.map +1 -0
- package/dist/component/garmin/schemas/menstrualCycleTracking.d.ts +100 -0
- package/dist/component/garmin/schemas/menstrualCycleTracking.d.ts.map +1 -0
- package/dist/component/garmin/schemas/menstrualCycleTracking.js +28 -0
- package/dist/component/garmin/schemas/menstrualCycleTracking.js.map +1 -0
- package/dist/component/garmin/schemas/moveIQ.d.ts +40 -0
- package/dist/component/garmin/schemas/moveIQ.d.ts.map +1 -0
- package/dist/component/garmin/schemas/moveIQ.js +27 -0
- package/dist/component/garmin/schemas/moveIQ.js.map +1 -0
- package/dist/component/garmin/schemas/pulseOx.d.ts +38 -0
- package/dist/component/garmin/schemas/pulseOx.d.ts.map +1 -0
- package/dist/component/garmin/schemas/pulseOx.js +28 -0
- package/dist/component/garmin/schemas/pulseOx.js.map +1 -0
- package/dist/component/garmin/schemas/respiration.d.ts +34 -0
- package/dist/component/garmin/schemas/respiration.d.ts.map +1 -0
- package/dist/component/garmin/schemas/respiration.js +28 -0
- package/dist/component/garmin/schemas/respiration.js.map +1 -0
- package/dist/component/garmin/schemas/skinTemperature.d.ts +36 -0
- package/dist/component/garmin/schemas/skinTemperature.d.ts.map +1 -0
- package/dist/component/garmin/schemas/skinTemperature.js +28 -0
- package/dist/component/garmin/schemas/skinTemperature.js.map +1 -0
- package/dist/component/garmin/schemas/sleeps.d.ts +88 -0
- package/dist/component/garmin/schemas/sleeps.d.ts.map +1 -0
- package/dist/component/garmin/schemas/sleeps.js +27 -0
- package/dist/component/garmin/schemas/sleeps.js.map +1 -0
- package/dist/component/garmin/schemas/stress.d.ts +70 -0
- package/dist/component/garmin/schemas/stress.d.ts.map +1 -0
- package/dist/component/garmin/schemas/stress.js +28 -0
- package/dist/component/garmin/schemas/stress.js.map +1 -0
- package/dist/component/garmin/schemas/userMetrics.d.ts +36 -0
- package/dist/component/garmin/schemas/userMetrics.d.ts.map +1 -0
- package/dist/component/garmin/schemas/userMetrics.js +28 -0
- package/dist/component/garmin/schemas/userMetrics.js.map +1 -0
- package/dist/component/garmin/transform/activity.d.ts +13 -0
- package/dist/component/garmin/transform/activity.d.ts.map +1 -0
- package/dist/component/garmin/transform/activity.js +111 -0
- package/dist/component/garmin/transform/activity.js.map +1 -0
- package/dist/component/garmin/transform/activityDetails.d.ts +13 -0
- package/dist/component/garmin/transform/activityDetails.d.ts.map +1 -0
- package/dist/component/garmin/transform/activityDetails.js +173 -0
- package/dist/component/garmin/transform/activityDetails.js.map +1 -0
- package/dist/component/garmin/transform/bloodPressure.d.ts +12 -0
- package/dist/component/garmin/transform/bloodPressure.d.ts.map +1 -0
- package/dist/component/garmin/transform/bloodPressure.js +33 -0
- package/dist/component/garmin/transform/bloodPressure.js.map +1 -0
- package/dist/component/garmin/transform/bodyCompositions.d.ts +12 -0
- package/dist/component/garmin/transform/bodyCompositions.d.ts.map +1 -0
- package/dist/component/garmin/transform/bodyCompositions.js +42 -0
- package/dist/component/garmin/transform/bodyCompositions.js.map +1 -0
- package/dist/component/garmin/transform/dailies.d.ts +12 -0
- package/dist/component/garmin/transform/dailies.d.ts.map +1 -0
- package/dist/component/garmin/transform/dailies.js +132 -0
- package/dist/component/garmin/transform/dailies.js.map +1 -0
- package/dist/component/garmin/transform/epochs.d.ts +13 -0
- package/dist/component/garmin/transform/epochs.d.ts.map +1 -0
- package/dist/component/garmin/transform/epochs.js +76 -0
- package/dist/component/garmin/transform/epochs.js.map +1 -0
- package/dist/component/garmin/transform/healthSnapshot.d.ts +12 -0
- package/dist/component/garmin/transform/healthSnapshot.d.ts.map +1 -0
- package/dist/component/garmin/transform/healthSnapshot.js +111 -0
- package/dist/component/garmin/transform/healthSnapshot.js.map +1 -0
- package/dist/component/garmin/transform/hrvSummary.d.ts +12 -0
- package/dist/component/garmin/transform/hrvSummary.d.ts.map +1 -0
- package/dist/component/garmin/transform/hrvSummary.js +45 -0
- package/dist/component/garmin/transform/hrvSummary.js.map +1 -0
- package/dist/component/garmin/transform/manuallyUpdatedActivities.d.ts +11 -0
- package/dist/component/garmin/transform/manuallyUpdatedActivities.d.ts.map +1 -0
- package/dist/component/garmin/transform/manuallyUpdatedActivities.js +20 -0
- package/dist/component/garmin/transform/manuallyUpdatedActivities.js.map +1 -0
- package/dist/component/garmin/transform/menstrualCycleTracking.d.ts +10 -0
- package/dist/component/garmin/transform/menstrualCycleTracking.d.ts.map +1 -0
- package/dist/component/garmin/transform/menstrualCycleTracking.js +43 -0
- package/dist/component/garmin/transform/menstrualCycleTracking.js.map +1 -0
- package/dist/component/garmin/transform/moveIQ.d.ts +17 -0
- package/dist/component/garmin/transform/moveIQ.d.ts.map +1 -0
- package/dist/component/garmin/transform/moveIQ.js +41 -0
- package/dist/component/garmin/transform/moveIQ.js.map +1 -0
- package/dist/component/garmin/transform/pulseOx.d.ts +12 -0
- package/dist/component/garmin/transform/pulseOx.d.ts.map +1 -0
- package/dist/component/garmin/transform/pulseOx.js +46 -0
- package/dist/component/garmin/transform/pulseOx.js.map +1 -0
- package/dist/component/garmin/transform/respiration.d.ts +12 -0
- package/dist/component/garmin/transform/respiration.d.ts.map +1 -0
- package/dist/component/garmin/transform/respiration.js +54 -0
- package/dist/component/garmin/transform/respiration.js.map +1 -0
- package/dist/component/garmin/transform/skinTemperature.d.ts +12 -0
- package/dist/component/garmin/transform/skinTemperature.d.ts.map +1 -0
- package/dist/component/garmin/transform/skinTemperature.js +38 -0
- package/dist/component/garmin/transform/skinTemperature.js.map +1 -0
- package/dist/component/garmin/transform/sleeps.d.ts +55 -0
- package/dist/component/garmin/transform/sleeps.d.ts.map +1 -0
- package/dist/component/garmin/transform/sleeps.js +120 -0
- package/dist/component/garmin/transform/sleeps.js.map +1 -0
- package/dist/component/garmin/transform/stress.d.ts +12 -0
- package/dist/component/garmin/transform/stress.d.ts.map +1 -0
- package/dist/component/garmin/transform/stress.js +56 -0
- package/dist/component/garmin/transform/stress.js.map +1 -0
- package/dist/component/garmin/transform/userMetrics.d.ts +12 -0
- package/dist/component/garmin/transform/userMetrics.d.ts.map +1 -0
- package/dist/component/garmin/transform/userMetrics.js +48 -0
- package/dist/component/garmin/transform/userMetrics.js.map +1 -0
- package/dist/component/garmin/types/garmin.d.ts +21 -0
- package/dist/component/garmin/types/garmin.d.ts.map +1 -0
- package/dist/component/garmin/types/garmin.js +6 -0
- package/dist/component/garmin/types/garmin.js.map +1 -0
- package/dist/component/garmin/types/zod/zod.gen.d.ts +1319 -0
- package/dist/component/garmin/types/zod/zod.gen.d.ts.map +1 -0
- package/dist/component/garmin/types/zod/zod.gen.js +784 -0
- package/dist/component/garmin/types/zod/zod.gen.js.map +1 -0
- package/dist/component/garmin/webhooks.d.ts +141 -0
- package/dist/component/garmin/webhooks.d.ts.map +1 -0
- package/dist/component/garmin/webhooks.js +766 -0
- package/dist/component/garmin/webhooks.js.map +1 -0
- package/dist/component/private.d.ts +4 -4
- package/dist/component/public.d.ts +333 -333
- package/dist/component/schema.d.ts +133 -133
- package/dist/component/strava/private.d.ts +30 -0
- package/dist/component/strava/private.d.ts.map +1 -0
- package/dist/component/strava/private.js +71 -0
- package/dist/component/strava/private.js.map +1 -0
- package/dist/component/{strava.d.ts → strava/public.d.ts} +3 -31
- package/dist/component/strava/public.d.ts.map +1 -0
- package/dist/component/{strava.js → strava/public.js} +22 -101
- package/dist/component/strava/public.js.map +1 -0
- package/dist/component/validators/activity.d.ts +6 -0
- package/dist/component/validators/activity.d.ts.map +1 -1
- package/dist/component/validators/activity.js.map +1 -1
- package/dist/component/validators/body.d.ts +20 -14
- package/dist/component/validators/body.d.ts.map +1 -1
- package/dist/component/validators/body.js.map +1 -1
- package/dist/component/validators/daily.d.ts +6 -0
- package/dist/component/validators/daily.d.ts.map +1 -1
- package/dist/component/validators/daily.js.map +1 -1
- package/dist/component/validators/enums.d.ts +1 -1
- package/dist/component/validators/menstruation.d.ts +5 -0
- package/dist/component/validators/menstruation.d.ts.map +1 -1
- package/dist/component/validators/menstruation.js.map +1 -1
- package/dist/garmin/client.js.map +1 -1
- package/dist/garmin/index.d.ts +0 -2
- package/dist/garmin/index.d.ts.map +1 -1
- package/dist/garmin/index.js +0 -1
- package/dist/garmin/index.js.map +1 -1
- package/dist/garmin/sync.d.ts.map +1 -1
- package/dist/garmin/sync.js +3 -2
- package/dist/garmin/sync.js.map +1 -1
- package/dist/garmin/types.d.ts +1 -1
- package/dist/garmin/types.d.ts.map +1 -1
- package/dist/validators.d.ts +31 -28
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +2 -2
- package/dist/validators.js.map +1 -1
- package/package.json +4 -7
- package/src/client/index.ts +41 -172
- package/src/component/_generated/api.ts +96 -4
- package/src/component/_generated/component.ts +252 -284
- package/src/{garmin → component/garmin}/auth.ts +8 -1
- package/src/component/garmin/client.ts +39 -0
- package/src/component/garmin/private.ts +1798 -0
- package/src/component/garmin/public.ts +938 -0
- package/src/component/garmin/schemas/activity.ts +40 -0
- package/src/component/garmin/schemas/activityDetails.ts +45 -0
- package/src/component/garmin/schemas/bloodPressure.ts +38 -0
- package/src/component/garmin/schemas/bodyCompositions.ts +38 -0
- package/src/component/garmin/schemas/dailies.ts +38 -0
- package/src/component/garmin/schemas/epochs.ts +38 -0
- package/src/component/garmin/schemas/healthSnapshot.ts +38 -0
- package/src/component/garmin/schemas/hrvSummary.ts +38 -0
- package/src/component/garmin/schemas/manuallyUpdatedActivities.ts +49 -0
- package/src/component/garmin/schemas/menstrualCycleTracking.ts +39 -0
- package/src/component/garmin/schemas/moveIQ.ts +38 -0
- package/src/component/garmin/schemas/pulseOx.ts +39 -0
- package/src/component/garmin/schemas/respiration.ts +39 -0
- package/src/component/garmin/schemas/skinTemperature.ts +39 -0
- package/src/component/garmin/schemas/sleeps.ts +38 -0
- package/src/component/garmin/schemas/stress.ts +43 -0
- package/src/component/garmin/schemas/userMetrics.ts +39 -0
- package/src/component/garmin/transform/activity.ts +143 -0
- package/src/component/garmin/transform/activityDetails.ts +236 -0
- package/src/{garmin → component/garmin/transform}/bloodPressure.ts +39 -41
- package/src/component/garmin/transform/bodyCompositions.ts +51 -0
- package/src/component/garmin/transform/dailies.ts +179 -0
- package/src/component/garmin/transform/epochs.ts +94 -0
- package/src/component/garmin/transform/healthSnapshot.ts +152 -0
- package/src/component/garmin/transform/hrvSummary.ts +56 -0
- package/src/component/garmin/transform/manuallyUpdatedActivities.ts +27 -0
- package/src/{garmin/maps/activity-type.ts → component/garmin/transform/maps/activityType.ts} +116 -116
- package/src/{garmin/maps/sleep-level.ts → component/garmin/transform/maps/sleepLevel.ts} +22 -22
- package/src/component/garmin/transform/menstrualCycleTracking.ts +48 -0
- package/src/component/garmin/transform/moveIQ.ts +48 -0
- package/src/{garmin → component/garmin/transform}/plannedWorkout.ts +328 -333
- package/src/component/garmin/transform/pulseOx.ts +64 -0
- package/src/component/garmin/transform/respiration.ts +73 -0
- package/src/component/garmin/transform/skinTemperature.ts +44 -0
- package/src/component/garmin/transform/sleeps.ts +159 -0
- package/src/component/garmin/transform/stress.ts +78 -0
- package/src/component/garmin/transform/userMetrics.ts +56 -0
- package/src/component/garmin/types/specs/training-api-workouts.json +699 -0
- package/src/component/garmin/types/trainingApiWorkouts/client/client.gen.ts +290 -0
- package/src/component/garmin/types/trainingApiWorkouts/client/index.ts +25 -0
- package/src/component/garmin/types/trainingApiWorkouts/client/types.gen.ts +214 -0
- package/src/component/garmin/types/trainingApiWorkouts/client/utils.gen.ts +316 -0
- package/src/component/garmin/types/trainingApiWorkouts/client.gen.ts +16 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/auth.gen.ts +41 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/bodySerializer.gen.ts +82 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/params.gen.ts +169 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/pathSerializer.gen.ts +171 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/queryKeySerializer.gen.ts +117 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/serverSentEvents.gen.ts +243 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/types.gen.ts +104 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/utils.gen.ts +140 -0
- package/src/component/garmin/types/trainingApiWorkouts/index.ts +4 -0
- package/src/component/garmin/types/trainingApiWorkouts/sdk.gen.ts +126 -0
- package/src/component/garmin/types/trainingApiWorkouts/types.gen.ts +387 -0
- package/src/component/garmin/types/trainingApiWorkouts/zod.gen.ts +423 -0
- package/src/component/garmin/types/wellnessApi/client/client.gen.ts +290 -0
- package/src/component/garmin/types/wellnessApi/client/index.ts +25 -0
- package/src/component/garmin/types/wellnessApi/client/types.gen.ts +214 -0
- package/src/component/garmin/types/wellnessApi/client/utils.gen.ts +316 -0
- package/src/component/garmin/types/wellnessApi/client.gen.ts +16 -0
- package/src/component/garmin/types/wellnessApi/core/auth.gen.ts +41 -0
- package/src/component/garmin/types/wellnessApi/core/bodySerializer.gen.ts +82 -0
- package/src/component/garmin/types/wellnessApi/core/params.gen.ts +169 -0
- package/src/component/garmin/types/wellnessApi/core/pathSerializer.gen.ts +171 -0
- package/src/component/garmin/types/wellnessApi/core/queryKeySerializer.gen.ts +117 -0
- package/src/component/garmin/types/wellnessApi/core/serverSentEvents.gen.ts +243 -0
- package/src/component/garmin/types/wellnessApi/core/types.gen.ts +104 -0
- package/src/component/garmin/types/wellnessApi/core/utils.gen.ts +140 -0
- package/src/component/garmin/types/wellnessApi/index.ts +4 -0
- package/src/component/garmin/types/wellnessApi/sdk.gen.ts +207 -0
- package/src/component/garmin/types/wellnessApi/types.gen.ts +2942 -0
- package/src/component/garmin/types/wellnessApi/zod.gen.ts +878 -0
- package/src/component/garmin/utils.ts +25 -0
- package/src/component/garmin/webhooks.ts +852 -0
- package/src/component/strava/private.ts +89 -0
- package/src/component/{strava.ts → strava/public.ts} +341 -404
- package/src/component/validators/activity.ts +5 -0
- package/src/component/validators/body.ts +5 -0
- package/src/component/validators/daily.ts +5 -0
- package/src/component/validators/menstruation.ts +5 -1
- package/src/component/validators/plannedWorkout.ts +5 -0
- package/src/validators.ts +12 -2
- package/dist/component/garmin.d.ts +0 -366
- package/dist/component/garmin.d.ts.map +0 -1
- package/dist/component/garmin.js +0 -1481
- package/dist/component/garmin.js.map +0 -1
- package/dist/component/strava.d.ts.map +0 -1
- package/dist/component/strava.js.map +0 -1
- package/dist/garmin/activity.d.ts +0 -92
- package/dist/garmin/activity.d.ts.map +0 -1
- package/dist/garmin/activity.js +0 -201
- package/dist/garmin/activity.js.map +0 -1
- package/src/component/garmin.ts +0 -1722
- package/src/garmin/activity.test.ts +0 -170
- package/src/garmin/activity.ts +0 -265
- package/src/garmin/auth.test.ts +0 -103
- package/src/garmin/body.ts +0 -59
- package/src/garmin/client.ts +0 -886
- package/src/garmin/daily.ts +0 -215
- package/src/garmin/hrv.ts +0 -57
- package/src/garmin/index.ts +0 -145
- package/src/garmin/maps/activity-type.test.ts +0 -78
- package/src/garmin/menstruation.ts +0 -44
- package/src/garmin/pulseOx.ts +0 -45
- package/src/garmin/respiration.ts +0 -55
- package/src/garmin/skinTemp.ts +0 -42
- package/src/garmin/sleep.test.ts +0 -109
- package/src/garmin/sleep.ts +0 -176
- package/src/garmin/stressDetails.ts +0 -71
- package/src/garmin/sync.ts +0 -566
- package/src/garmin/types.ts +0 -268
- package/src/garmin/userMetrics.ts +0 -50
- package/src/garmin/wellness-api.d.ts +0 -5637
- /package/src/{garmin/spec → component/garmin/types/specs}/wellness-api.json +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// ─── Pulse Ox Transformer ────────────────────────────────────────────────────
|
|
2
|
+
// Transforms a Garmin Pulse Ox push item into the Soma Daily schema shape.
|
|
3
|
+
//
|
|
4
|
+
// Pulse Ox summaries contain SpO2 (blood oxygen saturation) readings
|
|
5
|
+
// throughout the day. We map these into the Daily model's oxygen_data field.
|
|
6
|
+
|
|
7
|
+
import type { SomaDaily } from "../../validators/daily.js";
|
|
8
|
+
import type { ClientDailySpo2Acclimation } from "../types/wellnessApi/types.gen.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Transform a Garmin Pulse Ox push item into a Soma Daily document shape.
|
|
12
|
+
*
|
|
13
|
+
* Pulse Ox summaries are ingested into the `daily` table via `ingestDaily`,
|
|
14
|
+
* keyed by `metadata.start_time + metadata.end_time`.
|
|
15
|
+
*
|
|
16
|
+
* Returns `null` when no meaningful data is present.
|
|
17
|
+
*/
|
|
18
|
+
export function transformPulseOx(pulseOx: ClientDailySpo2Acclimation): SomaDaily | null {
|
|
19
|
+
const startMs = (pulseOx.startTimeInSeconds ?? 0) * 1000;
|
|
20
|
+
const endMs =
|
|
21
|
+
startMs + (pulseOx.durationInSeconds ?? 0) * 1000;
|
|
22
|
+
|
|
23
|
+
const oxygenData = buildOxygenData(pulseOx);
|
|
24
|
+
|
|
25
|
+
if (oxygenData == null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
metadata: {
|
|
31
|
+
start_time: new Date(startMs).toISOString(),
|
|
32
|
+
end_time: new Date(endMs).toISOString(),
|
|
33
|
+
upload_type: 1, // Automatic
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
oxygen_data: oxygenData,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function buildOxygenData(pulseOx: ClientDailySpo2Acclimation) {
|
|
43
|
+
const hasSamples =
|
|
44
|
+
pulseOx.timeOffsetSpo2Values != null &&
|
|
45
|
+
Object.keys(pulseOx.timeOffsetSpo2Values).length > 0;
|
|
46
|
+
|
|
47
|
+
if (!hasSamples) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const startTime = pulseOx.startTimeInSeconds ?? 0;
|
|
52
|
+
const samples = Object.entries(pulseOx.timeOffsetSpo2Values ?? {}).map(
|
|
53
|
+
([offset, percentage]) => ({
|
|
54
|
+
timestamp: new Date(
|
|
55
|
+
(startTime + parseInt(offset, 10)) * 1000,
|
|
56
|
+
).toISOString(),
|
|
57
|
+
percentage,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
saturation_samples: samples.length > 0 ? samples : undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// ─── Respiration Transformer ─────────────────────────────────────────────────
|
|
2
|
+
// Transforms a Garmin Respiration push item into the Soma Daily schema shape.
|
|
3
|
+
//
|
|
4
|
+
// Respiration summaries contain breathing rate readings throughout the day.
|
|
5
|
+
// We map these into the Daily model's respiration_data field.
|
|
6
|
+
|
|
7
|
+
import type { SomaDaily } from "../../validators/daily.js";
|
|
8
|
+
import type { ClientRespiration } from "../types/wellnessApi/types.gen.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Transform a Garmin Respiration push item into a Soma Daily document shape.
|
|
12
|
+
*
|
|
13
|
+
* Respiration summaries are ingested into the `daily` table via `ingestDaily`,
|
|
14
|
+
* keyed by `metadata.start_time + metadata.end_time`.
|
|
15
|
+
*
|
|
16
|
+
* Returns `null` when no meaningful data is present.
|
|
17
|
+
*/
|
|
18
|
+
export function transformRespiration(resp: ClientRespiration): SomaDaily | null {
|
|
19
|
+
const startMs = (resp.startTimeInSeconds ?? 0) * 1000;
|
|
20
|
+
const endMs =
|
|
21
|
+
startMs + (resp.durationInSeconds ?? 0) * 1000;
|
|
22
|
+
|
|
23
|
+
const respirationData = buildRespirationData(resp);
|
|
24
|
+
|
|
25
|
+
if (respirationData == null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
metadata: {
|
|
31
|
+
start_time: new Date(startMs).toISOString(),
|
|
32
|
+
end_time: new Date(endMs).toISOString(),
|
|
33
|
+
upload_type: 1, // Automatic
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
respiration_data: respirationData,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function buildRespirationData(resp: ClientRespiration) {
|
|
43
|
+
const hasSamples =
|
|
44
|
+
resp.timeOffsetEpochToBreaths != null &&
|
|
45
|
+
Object.keys(resp.timeOffsetEpochToBreaths).length > 0;
|
|
46
|
+
|
|
47
|
+
if (!hasSamples) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const startTime = resp.startTimeInSeconds ?? 0;
|
|
52
|
+
const samples = Object.entries(resp.timeOffsetEpochToBreaths ?? {}).map(
|
|
53
|
+
([offset, breaths_per_min]) => ({
|
|
54
|
+
timestamp: new Date(
|
|
55
|
+
(startTime + parseInt(offset, 10)) * 1000,
|
|
56
|
+
).toISOString(),
|
|
57
|
+
breaths_per_min,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Compute summary stats from samples
|
|
62
|
+
const values = samples.map((s) => s.breaths_per_min);
|
|
63
|
+
const avg = values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
breaths_data: {
|
|
67
|
+
avg_breaths_per_min: Math.round(avg * 10) / 10,
|
|
68
|
+
min_breaths_per_min: Math.min(...values),
|
|
69
|
+
max_breaths_per_min: Math.max(...values),
|
|
70
|
+
samples: samples.length > 0 ? samples : undefined,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// ─── Skin Temperature Transformer ────────────────────────────────────────────
|
|
2
|
+
// Transforms a Garmin Skin Temperature push item into the Soma Body schema shape.
|
|
3
|
+
//
|
|
4
|
+
// Skin temperature summaries contain the average deviation of the user's
|
|
5
|
+
// skin temperature during their sleep window. We map this into the Body
|
|
6
|
+
// model's temperature_data.skin_temperature_samples field.
|
|
7
|
+
|
|
8
|
+
import type { SomaBody } from "../../validators/body.js";
|
|
9
|
+
import type { ClientSkinTemp } from "../types/wellnessApi/types.gen.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Transform a Garmin Skin Temperature push item into a Soma Body document shape.
|
|
13
|
+
*
|
|
14
|
+
* Skin temperature records are ingested into the `body` table via `ingestBody`,
|
|
15
|
+
* keyed by `metadata.start_time + metadata.end_time`.
|
|
16
|
+
*
|
|
17
|
+
* Returns `null` when no meaningful temperature data is present.
|
|
18
|
+
*/
|
|
19
|
+
export function transformSkinTemperature(skinTemp: ClientSkinTemp): SomaBody | null {
|
|
20
|
+
const startMs = (skinTemp.startTimeInSeconds ?? 0) * 1000;
|
|
21
|
+
const endMs = startMs + (skinTemp.durationInSeconds ?? 0) * 1000;
|
|
22
|
+
const startTime = new Date(startMs).toISOString();
|
|
23
|
+
const endTime = new Date(endMs).toISOString();
|
|
24
|
+
|
|
25
|
+
if (skinTemp.avgDeviationCelsius == null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
metadata: {
|
|
31
|
+
start_time: startTime,
|
|
32
|
+
end_time: endTime,
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
temperature_data: {
|
|
36
|
+
skin_temperature_samples: [
|
|
37
|
+
{
|
|
38
|
+
timestamp: startTime,
|
|
39
|
+
temperature_celsius: skinTemp.avgDeviationCelsius,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// ─── Sleeps Transformer ──────────────────────────────────────────────────────
|
|
2
|
+
// Transforms a Garmin sleep summary push item into the Soma Sleep schema shape.
|
|
3
|
+
|
|
4
|
+
import { mapSleepLevel } from "./maps/sleepLevel.js";
|
|
5
|
+
import type { ClientSleep } from "../types/wellnessApi/types.gen.js";
|
|
6
|
+
|
|
7
|
+
type TimeRange = {
|
|
8
|
+
startTimeInSeconds?: number;
|
|
9
|
+
endTimeInSeconds?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Transform a Garmin sleep summary push item into a Soma Sleep document shape.
|
|
14
|
+
*
|
|
15
|
+
* Sleep summaries are ingested into the `sleep` table via `ingestSleep`,
|
|
16
|
+
* keyed by `metadata.summary_id`.
|
|
17
|
+
*
|
|
18
|
+
* The returned object is ready to be spread into an `ingestSleep` call
|
|
19
|
+
* alongside `connectionId` and `userId`.
|
|
20
|
+
*/
|
|
21
|
+
export function transformSleeps(sleep: ClientSleep) {
|
|
22
|
+
const startMs = (sleep.startTimeInSeconds ?? 0) * 1000;
|
|
23
|
+
const endMs =
|
|
24
|
+
startMs +
|
|
25
|
+
((sleep.durationInSeconds ?? 0) + (sleep.awakeDurationInSeconds ?? 0)) *
|
|
26
|
+
1000;
|
|
27
|
+
|
|
28
|
+
const uploadTypeMap: Record<string, number> = {
|
|
29
|
+
ENHANCED_FINAL: 2,
|
|
30
|
+
ENHANCED_TENTATIVE: 4,
|
|
31
|
+
AUTO_FINAL: 2,
|
|
32
|
+
AUTO_TENTATIVE: 4,
|
|
33
|
+
AUTO_MANUAL: 2,
|
|
34
|
+
MANUAL: 1,
|
|
35
|
+
DEVICE: 2,
|
|
36
|
+
OFF_WRIST: 0,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
metadata: {
|
|
41
|
+
summary_id: sleep.summaryId,
|
|
42
|
+
start_time: new Date(startMs).toISOString(),
|
|
43
|
+
end_time: new Date(endMs).toISOString(),
|
|
44
|
+
upload_type: uploadTypeMap[sleep.validation ?? ""] ?? 0,
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
sleep_durations_data: buildSleepDurationsData(sleep),
|
|
48
|
+
|
|
49
|
+
respiration_data: buildRespirationData(sleep),
|
|
50
|
+
|
|
51
|
+
scores: buildScoresData(sleep),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
function buildSleepDurationsData(sleep: ClientSleep) {
|
|
58
|
+
const totalAsleep =
|
|
59
|
+
(sleep.deepSleepDurationInSeconds ?? 0) +
|
|
60
|
+
(sleep.lightSleepDurationInSeconds ?? 0) +
|
|
61
|
+
(sleep.remSleepInSeconds ?? 0);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
asleep: {
|
|
65
|
+
duration_asleep_state_seconds: totalAsleep || undefined,
|
|
66
|
+
duration_deep_sleep_state_seconds: sleep.deepSleepDurationInSeconds,
|
|
67
|
+
duration_light_sleep_state_seconds: sleep.lightSleepDurationInSeconds,
|
|
68
|
+
duration_REM_sleep_state_seconds: sleep.remSleepInSeconds,
|
|
69
|
+
},
|
|
70
|
+
awake: {
|
|
71
|
+
duration_awake_state_seconds: sleep.awakeDurationInSeconds,
|
|
72
|
+
},
|
|
73
|
+
other: {
|
|
74
|
+
duration_in_bed_seconds: sleep.durationInSeconds,
|
|
75
|
+
duration_unmeasurable_sleep_seconds: sleep.unmeasurableSleepInSeconds,
|
|
76
|
+
},
|
|
77
|
+
hypnogram_samples: buildHypnogramSamples(sleep),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildHypnogramSamples(sleep: ClientSleep) {
|
|
82
|
+
if (!sleep.sleepLevelsMap) return undefined;
|
|
83
|
+
|
|
84
|
+
const samples: Array<{ timestamp: string; level: number }> = [];
|
|
85
|
+
|
|
86
|
+
for (const [stage, levels] of Object.entries(sleep.sleepLevelsMap)) {
|
|
87
|
+
if (!levels) continue;
|
|
88
|
+
const terraLevel = mapSleepLevel(stage);
|
|
89
|
+
|
|
90
|
+
for (const level of levels as TimeRange[]) {
|
|
91
|
+
if (level.startTimeInSeconds == null) continue;
|
|
92
|
+
samples.push({
|
|
93
|
+
timestamp: new Date(level.startTimeInSeconds * 1000).toISOString(),
|
|
94
|
+
level: terraLevel,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
samples.sort(
|
|
100
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return samples.length > 0 ? samples : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildRespirationData(sleep: ClientSleep) {
|
|
107
|
+
const hasBreathSamples =
|
|
108
|
+
sleep.timeOffsetSleepRespiration != null &&
|
|
109
|
+
Object.keys(sleep.timeOffsetSleepRespiration).length > 0;
|
|
110
|
+
const hasSpO2Samples =
|
|
111
|
+
sleep.timeOffsetSleepSpo2 != null &&
|
|
112
|
+
Object.keys(sleep.timeOffsetSleepSpo2).length > 0;
|
|
113
|
+
|
|
114
|
+
if (!hasBreathSamples && !hasSpO2Samples) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const startTime = sleep.startTimeInSeconds ?? 0;
|
|
119
|
+
|
|
120
|
+
const breathSamples = hasBreathSamples
|
|
121
|
+
? Object.entries(sleep.timeOffsetSleepRespiration ?? {}).map(
|
|
122
|
+
([offset, rate]) => ({
|
|
123
|
+
timestamp: new Date(
|
|
124
|
+
(startTime + parseInt(offset, 10)) * 1000,
|
|
125
|
+
).toISOString(),
|
|
126
|
+
breaths_per_min: rate,
|
|
127
|
+
}),
|
|
128
|
+
)
|
|
129
|
+
: undefined;
|
|
130
|
+
|
|
131
|
+
const spo2Samples = hasSpO2Samples
|
|
132
|
+
? Object.entries(sleep.timeOffsetSleepSpo2 ?? {}).map(([offset, pct]) => ({
|
|
133
|
+
timestamp: new Date(
|
|
134
|
+
(startTime + parseInt(offset, 10)) * 1000,
|
|
135
|
+
).toISOString(),
|
|
136
|
+
percentage: pct,
|
|
137
|
+
}))
|
|
138
|
+
: undefined;
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
breaths_data:
|
|
142
|
+
breathSamples && breathSamples.length > 0
|
|
143
|
+
? { samples: breathSamples }
|
|
144
|
+
: undefined,
|
|
145
|
+
oxygen_saturation_data:
|
|
146
|
+
spo2Samples && spo2Samples.length > 0
|
|
147
|
+
? { samples: spo2Samples }
|
|
148
|
+
: undefined,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function buildScoresData(sleep: ClientSleep) {
|
|
153
|
+
const overallScore = sleep.overallSleepScore?.value;
|
|
154
|
+
if (overallScore == null) return undefined;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
sleep: overallScore,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// ─── Stress Details Transformer ──────────────────────────────────────────────
|
|
2
|
+
// Transforms a Garmin Stress Details push item into the Soma Daily schema shape.
|
|
3
|
+
//
|
|
4
|
+
// Stress details summaries contain 3-minute stress level averages and body
|
|
5
|
+
// battery values throughout the day. We map these into the Daily model's
|
|
6
|
+
// stress_data field.
|
|
7
|
+
|
|
8
|
+
import type { SomaDaily } from "../../validators/daily.js";
|
|
9
|
+
import type { ClientStress } from "../types/wellnessApi/types.gen.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Transform a Garmin Stress Details push item into a Soma Daily document shape.
|
|
13
|
+
*
|
|
14
|
+
* Stress records are ingested into the `daily` table via `ingestDaily`,
|
|
15
|
+
* keyed by `metadata.start_time + metadata.end_time`.
|
|
16
|
+
*
|
|
17
|
+
* Returns `null` when no meaningful stress data is present.
|
|
18
|
+
*/
|
|
19
|
+
export function transformStress(stress: ClientStress): SomaDaily | null {
|
|
20
|
+
const hasSummary =
|
|
21
|
+
stress.averageStressLevel != null || stress.maxStressLevel != null;
|
|
22
|
+
const hasStressSamples =
|
|
23
|
+
stress.timeOffsetStressLevelValues != null &&
|
|
24
|
+
Object.keys(stress.timeOffsetStressLevelValues).length > 0;
|
|
25
|
+
const hasBodyBatterySamples =
|
|
26
|
+
stress.timeOffsetBodyBatteryValues != null &&
|
|
27
|
+
Object.keys(stress.timeOffsetBodyBatteryValues).length > 0;
|
|
28
|
+
|
|
29
|
+
if (!hasSummary && !hasStressSamples && !hasBodyBatterySamples) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const startSeconds = stress.startTimeInSeconds ?? 0;
|
|
34
|
+
const durationSeconds = stress.durationInSeconds ?? 86400;
|
|
35
|
+
const startTime = new Date(startSeconds * 1000).toISOString();
|
|
36
|
+
const endTime = new Date((startSeconds + durationSeconds) * 1000).toISOString();
|
|
37
|
+
|
|
38
|
+
const stressSamples = hasStressSamples
|
|
39
|
+
? Object.entries(stress.timeOffsetStressLevelValues ?? {}).map(
|
|
40
|
+
([offset, level]) => ({
|
|
41
|
+
timestamp: new Date(
|
|
42
|
+
(startSeconds + parseInt(offset, 10)) * 1000,
|
|
43
|
+
).toISOString(),
|
|
44
|
+
level,
|
|
45
|
+
}),
|
|
46
|
+
)
|
|
47
|
+
: undefined;
|
|
48
|
+
|
|
49
|
+
const bodyBatterySamples = hasBodyBatterySamples
|
|
50
|
+
? Object.entries(stress.timeOffsetBodyBatteryValues ?? {}).map(
|
|
51
|
+
([offset, level]) => ({
|
|
52
|
+
timestamp: new Date(
|
|
53
|
+
(startSeconds + parseInt(offset, 10)) * 1000,
|
|
54
|
+
).toISOString(),
|
|
55
|
+
level,
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
58
|
+
: undefined;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
metadata: {
|
|
62
|
+
start_time: startTime,
|
|
63
|
+
end_time: endTime,
|
|
64
|
+
upload_type: 1,
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
stress_data: {
|
|
68
|
+
avg_stress_level: stress.averageStressLevel,
|
|
69
|
+
max_stress_level: stress.maxStressLevel,
|
|
70
|
+
samples:
|
|
71
|
+
stressSamples && stressSamples.length > 0 ? stressSamples : undefined,
|
|
72
|
+
body_battery_samples:
|
|
73
|
+
bodyBatterySamples && bodyBatterySamples.length > 0
|
|
74
|
+
? bodyBatterySamples
|
|
75
|
+
: undefined,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// ─── User Metrics Transformer ────────────────────────────────────────────────
|
|
2
|
+
// Transforms a Garmin User Metrics push item into the Soma Body schema shape.
|
|
3
|
+
//
|
|
4
|
+
// User Metrics summaries contain VO2 Max, VO2 Max Cycling, fitness age, and
|
|
5
|
+
// an enhanced flag. We map these into the Body model's oxygen_data and
|
|
6
|
+
// measurements_data fields.
|
|
7
|
+
|
|
8
|
+
import type { SomaBody } from "../../validators/body.js";
|
|
9
|
+
import type { ClientUserMetrics } from "../types/wellnessApi/types.gen.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Transform a Garmin User Metrics push item into a Soma Body document shape.
|
|
13
|
+
*
|
|
14
|
+
* User metrics records are ingested into the `body` table via `ingestBody`,
|
|
15
|
+
* keyed by `metadata.start_time + metadata.end_time`.
|
|
16
|
+
*
|
|
17
|
+
* Returns `null` when no meaningful metrics data is present.
|
|
18
|
+
*/
|
|
19
|
+
export function transformUserMetrics(metrics: ClientUserMetrics): SomaBody | null {
|
|
20
|
+
const dateStr = metrics.calendarDate;
|
|
21
|
+
const startTime = dateStr
|
|
22
|
+
? `${dateStr}T00:00:00.000Z`
|
|
23
|
+
: new Date(0).toISOString();
|
|
24
|
+
const endTime = dateStr
|
|
25
|
+
? `${dateStr}T23:59:59.999Z`
|
|
26
|
+
: new Date(0).toISOString();
|
|
27
|
+
|
|
28
|
+
const hasVo2 = metrics.vo2Max != null || metrics.vo2MaxCycling != null;
|
|
29
|
+
const hasFitnessAge = metrics.fitnessAge != null;
|
|
30
|
+
|
|
31
|
+
if (!hasVo2 && !hasFitnessAge) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
metadata: {
|
|
37
|
+
start_time: startTime,
|
|
38
|
+
end_time: endTime,
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
oxygen_data: hasVo2
|
|
42
|
+
? { vo2max_ml_per_min_per_kg: metrics.vo2Max ?? metrics.vo2MaxCycling }
|
|
43
|
+
: undefined,
|
|
44
|
+
|
|
45
|
+
measurements_data: hasFitnessAge
|
|
46
|
+
? {
|
|
47
|
+
measurements: [
|
|
48
|
+
{
|
|
49
|
+
measurement_time: startTime,
|
|
50
|
+
estimated_fitness_age: String(metrics.fitnessAge),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
: undefined,
|
|
55
|
+
};
|
|
56
|
+
}
|