mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
test(integrations): pin what each provider mapper makes of sanitised wire fixtures
This commit is contained in:
1 parent
b2ebcb198d
commit
df6e182e0b
72 files changed
+12043
No files matched your search
@@ -0,0 +1,94 @@
|
||||
[
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-oxygen-saturation/dataPoints/spo2-0001",
|
||||
"dailyOxygenSaturation": {
|
||||
"date": { "year": 2026, "month": 6, "day": 1 },
|
||||
"averagePercentage": 96.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-oxygen-saturation/dataPoints/spo2-0002",
|
||||
"dailyOxygenSaturation": {
|
||||
"date": "2026-06-02T00:00:00",
|
||||
"averagePercentage": "97"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-heart-rate-variability/dataPoints/hrv-0001",
|
||||
"dailyHeartRateVariability": {
|
||||
"date": { "year": 2026, "month": 6, "day": 1 },
|
||||
"averageHeartRateVariabilityMilliseconds": 42.123456
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-heart-rate-variability/dataPoints/hrv-0002",
|
||||
"dailyHeartRateVariability": {
|
||||
"date": { "year": 2026, "month": 12, "day": 31 },
|
||||
"averageHeartRateVariabilityMilliseconds": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-resting-heart-rate/dataPoints/rhr-0001",
|
||||
"dailyRestingHeartRate": {
|
||||
"date": "2026-06-03",
|
||||
"beatsPerMinute": "55"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-resting-heart-rate/dataPoints/rhr-0002",
|
||||
"dailyRestingHeartRate": {
|
||||
"date": { "year": 2026, "month": "6", "day": 4 },
|
||||
"beatsPerMinute": 57
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-respiratory-rate/dataPoints/resp-0001",
|
||||
"dailyRespiratoryRate": {
|
||||
"date": { "year": 2026, "month": 3, "day": 29 },
|
||||
"breathsPerMinute": 14.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-respiratory-rate/dataPoints/resp-0002",
|
||||
"dailyRespiratoryRate": {
|
||||
"date": { "year": 2026, "month": 3, "day": 30 },
|
||||
"dailyRespiratoryRateBpm": 15.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-sleep-temperature-derivations/dataPoints/temp-0001",
|
||||
"dailySleepTemperatureDerivations": {
|
||||
"date": { "year": 2026, "month": 6, "day": 5 },
|
||||
"nightlyTemperatureCelsius": 34.42,
|
||||
"baselineTemperatureCelsius": 34.1,
|
||||
"relativeNightlyStddev30dCelsius": 0.3
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-sleep-temperature-derivations/dataPoints/temp-0002",
|
||||
"dailySleepTemperatureDerivations": {
|
||||
"date": "garbage",
|
||||
"nightlyTemperatureCelsius": 34.6
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-vo2-max/dataPoints/vo2-0001",
|
||||
"dailyVo2Max": {
|
||||
"date": { "year": 2026, "month": 6, "day": 6 },
|
||||
"vo2Max": 47.25
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-vo2-max/dataPoints/vo2-0002",
|
||||
"dailyVo2Max": {
|
||||
"vo2Max": "45"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/daily-vo2-max/dataPoints/vo2-0003",
|
||||
"dailyVo2Max": {
|
||||
"date": { "year": 2026, "month": 6, "day": 7 },
|
||||
"vo2Max": null
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,102 @@
|
||||
[
|
||||
{
|
||||
"name": "users/me/dataTypes/exercise/dataPoints/exercise-0001",
|
||||
"exercise": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-02T17:00:00Z",
|
||||
"endTime": "2026-06-02T17:42:30Z",
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 2 },
|
||||
"time": { "hours": 19 }
|
||||
}
|
||||
},
|
||||
"exerciseType": "RUNNING",
|
||||
"metricsSummary": {
|
||||
"caloriesKcal": 412.6,
|
||||
"distanceMillimeters": "7012345",
|
||||
"averageHeartRateBeatsPerMinute": "151"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/exercise/dataPoints/exercise-0002",
|
||||
"exercise": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-03T07:00:00",
|
||||
"endTime": "2026-06-03T08:00:00"
|
||||
},
|
||||
"exerciseType": "strength training",
|
||||
"metricsSummary": {
|
||||
"caloriesKcal": "250",
|
||||
"averageHeartRateBeatsPerMinute": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"exercise": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-04T06:30:00+02:00",
|
||||
"endTime": "2026-06-04T07:15:00+02:00"
|
||||
},
|
||||
"exerciseType": " KITESURFING ",
|
||||
"metricsSummary": {
|
||||
"distanceMillimeters": -100,
|
||||
"averageHeartRateBeatsPerMinute": "128.6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/exercise/dataPoints/exercise-0004",
|
||||
"exercise": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-05T12:00:00Z",
|
||||
"endTime": "2026-06-05T12:30:00Z"
|
||||
},
|
||||
"exerciseType": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/exercise/dataPoints/exercise-0005",
|
||||
"exercise": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-06T12:00:00Z",
|
||||
"endTime": "2026-06-06T12:00:00Z"
|
||||
},
|
||||
"exerciseType": "WALKING"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/exercise/dataPoints/exercise-0006",
|
||||
"exercise": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-07T12:00:00Z"
|
||||
},
|
||||
"exerciseType": "WALKING"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/exercise/dataPoints/exercise-0007",
|
||||
"exercise": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-08T18:00:00Z",
|
||||
"endTime": "2026-06-08T19:05:00Z"
|
||||
},
|
||||
"exerciseType": "Mountain-Biking",
|
||||
"metricsSummary": {
|
||||
"caloriesKcal": 0,
|
||||
"distanceMillimeters": "0",
|
||||
"averageHeartRateBeatsPerMinute": "x"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/exercise/dataPoints/exercise-0008",
|
||||
"exercise": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-09T20:00:00Z",
|
||||
"endTime": "2026-06-09T20:20:00Z"
|
||||
},
|
||||
"exerciseType": "STAIR_CLIMBER"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,72 @@
|
||||
[
|
||||
{
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 1 },
|
||||
"time": {}
|
||||
},
|
||||
"civilEndTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 1 },
|
||||
"time": { "hours": 23, "minutes": 59, "seconds": 59 }
|
||||
},
|
||||
"steps": { "countSum": "8432" }
|
||||
},
|
||||
{
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 2 },
|
||||
"time": {}
|
||||
},
|
||||
"steps": { "countSum": "0" }
|
||||
},
|
||||
{
|
||||
"civilStartTime": "2026-06-03T00:00:00",
|
||||
"steps": { "countSum": 10250 }
|
||||
},
|
||||
{
|
||||
"civilEndTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 4 }
|
||||
},
|
||||
"steps": { "countSum": "5000" }
|
||||
},
|
||||
{
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 1 },
|
||||
"time": {}
|
||||
},
|
||||
"distance": { "millimetersSum": "6240512" }
|
||||
},
|
||||
{
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 2 },
|
||||
"time": {}
|
||||
},
|
||||
"distance": { "millimetersSum": "-1" }
|
||||
},
|
||||
{
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 1 },
|
||||
"time": {}
|
||||
},
|
||||
"activeEnergyBurned": { "kcalSum": 512.75 }
|
||||
},
|
||||
{
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 6, "day": 2 },
|
||||
"time": {}
|
||||
},
|
||||
"activeEnergyBurned": {}
|
||||
},
|
||||
{
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 12, "day": 31 },
|
||||
"time": {}
|
||||
},
|
||||
"floors": { "countSum": "12" }
|
||||
},
|
||||
{
|
||||
"civilStartTime": {
|
||||
"date": { "year": 2026, "month": 1, "day": 1 },
|
||||
"time": {}
|
||||
},
|
||||
"floors": { "countSum": "" }
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,105 @@
|
||||
[
|
||||
{
|
||||
"name": "users/me/dataTypes/weight/dataPoints/weight-0001",
|
||||
"weight": {
|
||||
"sampleTime": { "physicalTime": "2026-06-02T06:15:00Z" },
|
||||
"weightGrams": 78400
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/weight/dataPoints/weight-0002",
|
||||
"weight": {
|
||||
"sampleTime": { "physicalTime": "2026-06-03T07:30:00+02:00" },
|
||||
"weightGrams": "78250.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/weight/dataPoints/weight-0003",
|
||||
"weight": {
|
||||
"sampleTime": { "physicalTime": "2026-06-04T23:45:00" },
|
||||
"weightGrams": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/weight/dataPoints/weight-0004",
|
||||
"weight": {
|
||||
"sampleTime": { "physicalTime": "2026-06-04T23:45:00" },
|
||||
"weightGrams": 77900
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/body-fat/dataPoints/body-fat-0001",
|
||||
"bodyFat": {
|
||||
"sampleTime": { "physicalTime": "2026-06-02T06:15:00.000Z" },
|
||||
"percentage": 21.4
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/body-fat/dataPoints/body-fat-0002",
|
||||
"bodyFat": {
|
||||
"percentage": "20.9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/heart-rate/dataPoints/heart-rate-0001",
|
||||
"heartRate": {
|
||||
"sampleTime": { "physicalTime": "2026-06-02T12:00:30-04:00" },
|
||||
"beatsPerMinute": "64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/heart-rate/dataPoints/heart-rate-0002",
|
||||
"heartRate": {
|
||||
"sampleTime": { "physicalTime": "2026-06-02 00:10" },
|
||||
"beatsPerMinute": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/heart-rate/dataPoints/heart-rate-0003",
|
||||
"heartRate": {
|
||||
"sampleTime": { "physicalTime": "not-a-time" },
|
||||
"beatsPerMinute": 58
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/blood-glucose/dataPoints/blood-glucose-0001",
|
||||
"bloodGlucose": {
|
||||
"sampleTime": { "physicalTime": "2026-06-05T08:00:00Z" },
|
||||
"bloodGlucoseMilligramsPerDeciliter": 96.333333
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/blood-glucose/dataPoints/blood-glucose-0002",
|
||||
"bloodGlucose": {
|
||||
"sampleTime": { "physicalTime": "2026-06-05T09:00:00Z" },
|
||||
"bloodGlucoseMilligramsPerDeciliter": -5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/core-body-temperature/dataPoints/core-temp-0001",
|
||||
"coreBodyTemperature": {
|
||||
"sampleTime": { "physicalTime": "2026-06-06T03:00:00" },
|
||||
"temperatureCelsius": 36.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/height/dataPoints/height-0001",
|
||||
"height": {
|
||||
"sampleTime": { "physicalTime": "2026-01-15T10:00:00Z" },
|
||||
"heightMillimeters": "1805"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/height/dataPoints/height-0002",
|
||||
"height": {
|
||||
"heightMillimeters": 1798
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/height/dataPoints/height-0003",
|
||||
"height": {
|
||||
"sampleTime": { "physicalTime": "2026-01-16T10:00:00Z" },
|
||||
"heightMillimeters": "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,177 @@
|
||||
[
|
||||
{
|
||||
"name": "users/me/dataTypes/sleep/dataPoints/sleep-0001",
|
||||
"sleep": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-01T22:30:00Z",
|
||||
"endTime": "2026-06-02T06:10:00Z"
|
||||
},
|
||||
"stages": [
|
||||
{
|
||||
"startTime": "2026-06-01T22:30:00Z",
|
||||
"startUtcOffset": "7200s",
|
||||
"endTime": "2026-06-01T22:45:00Z",
|
||||
"endUtcOffset": "7200s",
|
||||
"type": "AWAKE"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-01T22:45:00Z",
|
||||
"endTime": "2026-06-02T00:15:00Z",
|
||||
"type": "LIGHT"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-02T00:15:00Z",
|
||||
"endTime": "2026-06-02T01:20:30Z",
|
||||
"type": "DEEP"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-02T01:20:30Z",
|
||||
"endTime": "2026-06-02T02:05:00Z",
|
||||
"type": "REM"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-02T02:05:00Z",
|
||||
"endTime": "2026-06-02T06:10:00Z",
|
||||
"type": "LIGHT"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/sleep/dataPoints/sleep-0002",
|
||||
"sleep": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-02T23:50:00",
|
||||
"endTime": "2026-06-03T07:00:00"
|
||||
},
|
||||
"stages": [
|
||||
{
|
||||
"startTime": "2026-06-02T23:50:00",
|
||||
"endTime": "2026-06-03T00:20:00",
|
||||
"type": "ASLEEP"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-03T00:20:00",
|
||||
"endTime": "2026-06-03T00:25:00",
|
||||
"type": "RESTLESS"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-03T00:25:00",
|
||||
"endTime": "2026-06-03T07:00:00",
|
||||
"type": "asleep"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"sleep": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-04T22:00:00+02:00",
|
||||
"endTime": "2026-06-05T05:30:00+02:00"
|
||||
},
|
||||
"stages": [
|
||||
{
|
||||
"startTime": "2026-06-04T22:00:00+02:00",
|
||||
"endTime": "2026-06-05T01:00:00+02:00",
|
||||
"type": "DEEP"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-05T01:00:00+02:00",
|
||||
"endTime": "2026-06-05T05:30:00+02:00",
|
||||
"type": "SLEEP_STAGE_TYPE_UNSPECIFIED"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"sleep": {
|
||||
"interval": {
|
||||
"startTime": "2026-06-06T23:00:00Z"
|
||||
},
|
||||
"stages": [
|
||||
{
|
||||
"startTime": "2026-06-06T23:00:00Z",
|
||||
"endTime": "2026-06-06T23:00:00Z",
|
||||
"type": "LIGHT"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-07T01:00:00Z",
|
||||
"endTime": "2026-06-07T00:30:00Z",
|
||||
"type": "REM"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-07T01:00:00Z",
|
||||
"type": "DEEP"
|
||||
},
|
||||
{
|
||||
"endTime": "2026-06-07T02:00:00Z",
|
||||
"type": "DEEP"
|
||||
},
|
||||
null,
|
||||
"LIGHT",
|
||||
{
|
||||
"startTime": "2026-06-07T02:00:00Z",
|
||||
"endTime": "2026-06-07T02:30:00Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-07T02:00:00Z",
|
||||
"endTime": "2026-06-07T02:30:00Z",
|
||||
"type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"sleep": {
|
||||
"stages": "not-a-list"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users/me/dataTypes/sleep/dataPoints/sleep-0006",
|
||||
"sleep": {
|
||||
"stages": [
|
||||
{
|
||||
"startTime": "2026-06-08T00:00:00Z",
|
||||
"endTime": "2026-06-08T00:10:00Z",
|
||||
"type": "NAP_STAGE_ALPHA"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-08T00:10:00Z",
|
||||
"endTime": "2026-06-08T00:20:00Z",
|
||||
"type": "NAP_STAGE_BETA"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-08T00:20:00Z",
|
||||
"endTime": "2026-06-08T00:30:00Z",
|
||||
"type": "NAP_STAGE_GAMMA"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-08T00:30:00Z",
|
||||
"endTime": "2026-06-08T00:40:00Z",
|
||||
"type": "NAP_STAGE_ALPHA"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-08T00:40:00Z",
|
||||
"endTime": "2026-06-08T00:50:00Z",
|
||||
"type": "NAP_STAGE_DELTA"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-08T00:50:00Z",
|
||||
"endTime": "2026-06-08T01:00:00Z",
|
||||
"type": "NAP_STAGE_EPSILON"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-08T01:00:00Z",
|
||||
"endTime": "2026-06-08T01:10:00Z",
|
||||
"type": "NAP_STAGE_ZETA_WITH_A_LABEL_MUCH_LONGER_THAN_FORTY_CHARACTERS"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-06-08T01:10:00Z",
|
||||
"endTime": "2026-06-08T01:40:00Z",
|
||||
"type": "light"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,604 @@
|
||||
{
|
||||
"resolveGoogleHealthUserId": [
|
||||
{ "profile": { "name": "users/1234567890" }, "userId": "1234567890" },
|
||||
{ "profile": { "name": "users/", "id": "id-0002" }, "userId": "id-0002" },
|
||||
{ "profile": { "name": "plain-0003" }, "userId": "plain-0003" },
|
||||
{ "profile": { "id": "id-0004" }, "userId": "id-0004" },
|
||||
{ "profile": {}, "userId": "me" }
|
||||
],
|
||||
"formatCivilBound": [
|
||||
{
|
||||
"instant": "2026-06-01T21:59:59.000Z",
|
||||
"tz": null,
|
||||
"bound": "2026-06-01T21:59:59"
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T21:59:59.000Z",
|
||||
"tz": "Europe/Berlin",
|
||||
"bound": "2026-06-01T23:59:59"
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T21:59:59.000Z",
|
||||
"tz": "America/New_York",
|
||||
"bound": "2026-06-01T17:59:59"
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T22:00:00.000Z",
|
||||
"tz": null,
|
||||
"bound": "2026-06-01T22:00:00"
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T22:00:00.000Z",
|
||||
"tz": "Europe/Berlin",
|
||||
"bound": "2026-06-02T00:00:00"
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T22:00:00.000Z",
|
||||
"tz": "America/New_York",
|
||||
"bound": "2026-06-01T18:00:00"
|
||||
},
|
||||
{
|
||||
"instant": "2026-03-29T00:30:00.000Z",
|
||||
"tz": null,
|
||||
"bound": "2026-03-29T00:30:00"
|
||||
},
|
||||
{
|
||||
"instant": "2026-03-29T00:30:00.000Z",
|
||||
"tz": "Europe/Berlin",
|
||||
"bound": "2026-03-29T01:30:00"
|
||||
},
|
||||
{
|
||||
"instant": "2026-03-29T00:30:00.000Z",
|
||||
"tz": "America/New_York",
|
||||
"bound": "2026-03-28T20:30:00"
|
||||
},
|
||||
{
|
||||
"instant": "2026-12-31T23:30:15.500Z",
|
||||
"tz": null,
|
||||
"bound": "2026-12-31T23:30:15"
|
||||
},
|
||||
{
|
||||
"instant": "2026-12-31T23:30:15.500Z",
|
||||
"tz": "Europe/Berlin",
|
||||
"bound": "2027-01-01T00:30:15"
|
||||
},
|
||||
{
|
||||
"instant": "2026-12-31T23:30:15.500Z",
|
||||
"tz": "America/New_York",
|
||||
"bound": "2026-12-31T18:30:15"
|
||||
}
|
||||
],
|
||||
"civilDateInTz": [
|
||||
{
|
||||
"instant": "2026-06-01T21:59:59.000Z",
|
||||
"tz": null,
|
||||
"date": { "year": 2026, "month": 6, "day": 1 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T21:59:59.000Z",
|
||||
"tz": "Europe/Berlin",
|
||||
"date": { "year": 2026, "month": 6, "day": 1 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T21:59:59.000Z",
|
||||
"tz": "America/New_York",
|
||||
"date": { "year": 2026, "month": 6, "day": 1 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T22:00:00.000Z",
|
||||
"tz": null,
|
||||
"date": { "year": 2026, "month": 6, "day": 1 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T22:00:00.000Z",
|
||||
"tz": "Europe/Berlin",
|
||||
"date": { "year": 2026, "month": 6, "day": 2 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-06-01T22:00:00.000Z",
|
||||
"tz": "America/New_York",
|
||||
"date": { "year": 2026, "month": 6, "day": 1 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-03-29T00:30:00.000Z",
|
||||
"tz": null,
|
||||
"date": { "year": 2026, "month": 3, "day": 29 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-03-29T00:30:00.000Z",
|
||||
"tz": "Europe/Berlin",
|
||||
"date": { "year": 2026, "month": 3, "day": 29 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-03-29T00:30:00.000Z",
|
||||
"tz": "America/New_York",
|
||||
"date": { "year": 2026, "month": 3, "day": 28 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-12-31T23:30:15.500Z",
|
||||
"tz": null,
|
||||
"date": { "year": 2026, "month": 12, "day": 31 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-12-31T23:30:15.500Z",
|
||||
"tz": "Europe/Berlin",
|
||||
"date": { "year": 2027, "month": 1, "day": 1 }
|
||||
},
|
||||
{
|
||||
"instant": "2026-12-31T23:30:15.500Z",
|
||||
"tz": "America/New_York",
|
||||
"date": { "year": 2026, "month": 12, "day": 31 }
|
||||
}
|
||||
],
|
||||
"incrementalFilter": [
|
||||
{
|
||||
"dataType": "weight",
|
||||
"style": "camel",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"field": "weight.sample_time.physical_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "weight",
|
||||
"style": "camel",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"field": "weight.sample_time.physical_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "weight",
|
||||
"style": "camel",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"field": "weight.sample_time.physical_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "weight",
|
||||
"style": "snake",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"field": "weight.sample_time.physical_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "weight",
|
||||
"style": "snake",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"field": "weight.sample_time.physical_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "weight",
|
||||
"style": "snake",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"field": "weight.sample_time.physical_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "daily-heart-rate-variability",
|
||||
"style": "camel",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"field": "dailyHeartRateVariability.date",
|
||||
"bound": "2026-06-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "daily-heart-rate-variability",
|
||||
"style": "camel",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"field": "dailyHeartRateVariability.date",
|
||||
"bound": "2026-06-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "daily-heart-rate-variability",
|
||||
"style": "camel",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"field": "dailyHeartRateVariability.date",
|
||||
"bound": "2026-06-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "daily-heart-rate-variability",
|
||||
"style": "snake",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"field": "daily_heart_rate_variability.date",
|
||||
"bound": "2026-06-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "daily-heart-rate-variability",
|
||||
"style": "snake",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"field": "daily_heart_rate_variability.date",
|
||||
"bound": "2026-06-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "daily-heart-rate-variability",
|
||||
"style": "snake",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"field": "daily_heart_rate_variability.date",
|
||||
"bound": "2026-06-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "sleep",
|
||||
"style": "camel",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"field": "sleep.interval.end_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "sleep",
|
||||
"style": "camel",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"field": "sleep.interval.end_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "sleep",
|
||||
"style": "camel",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"field": "sleep.interval.end_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "sleep",
|
||||
"style": "snake",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"field": "sleep.interval.end_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "sleep",
|
||||
"style": "snake",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"field": "sleep.interval.end_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "sleep",
|
||||
"style": "snake",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"field": "sleep.interval.end_time",
|
||||
"bound": "2026-06-01T22:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "exercise",
|
||||
"style": "camel",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"field": "exercise.interval.civil_start_time",
|
||||
"bound": "2026-06-01T22:00:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "exercise",
|
||||
"style": "camel",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"field": "exercise.interval.civil_start_time",
|
||||
"bound": "2026-06-02T00:00:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "exercise",
|
||||
"style": "camel",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"field": "exercise.interval.civil_start_time",
|
||||
"bound": "2026-06-01T18:00:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "exercise",
|
||||
"style": "snake",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"field": "exercise.interval.civil_start_time",
|
||||
"bound": "2026-06-01T22:00:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "exercise",
|
||||
"style": "snake",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"field": "exercise.interval.civil_start_time",
|
||||
"bound": "2026-06-02T00:00:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "exercise",
|
||||
"style": "snake",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"field": "exercise.interval.civil_start_time",
|
||||
"bound": "2026-06-01T18:00:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "steps",
|
||||
"style": "camel",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"threw": "Google Health data type steps reads via :dailyRollUp, not dataPoints.list"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "steps",
|
||||
"style": "camel",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"threw": "Google Health data type steps reads via :dailyRollUp, not dataPoints.list"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "steps",
|
||||
"style": "camel",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"threw": "Google Health data type steps reads via :dailyRollUp, not dataPoints.list"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "steps",
|
||||
"style": "snake",
|
||||
"tz": null,
|
||||
"filter": {
|
||||
"threw": "Google Health data type steps reads via :dailyRollUp, not dataPoints.list"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "steps",
|
||||
"style": "snake",
|
||||
"tz": "Europe/Berlin",
|
||||
"filter": {
|
||||
"threw": "Google Health data type steps reads via :dailyRollUp, not dataPoints.list"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataType": "steps",
|
||||
"style": "snake",
|
||||
"tz": "America/New_York",
|
||||
"filter": {
|
||||
"threw": "Google Health data type steps reads via :dailyRollUp, not dataPoints.list"
|
||||
}
|
||||
}
|
||||
],
|
||||
"incrementalFilterDefaultStyle": {
|
||||
"field": "dailyRestingHeartRate.date",
|
||||
"bound": "2026-06-01"
|
||||
},
|
||||
"chunkCivilRange": [
|
||||
{
|
||||
"start": { "year": 2026, "month": 1, "day": 1 },
|
||||
"end": { "year": 2026, "month": 7, "day": 1 },
|
||||
"maxDays": null,
|
||||
"chunks": [
|
||||
{
|
||||
"start": { "year": 2026, "month": 1, "day": 1 },
|
||||
"end": { "year": 2026, "month": 4, "day": 1 }
|
||||
},
|
||||
{
|
||||
"start": { "year": 2026, "month": 4, "day": 1 },
|
||||
"end": { "year": 2026, "month": 6, "day": 30 }
|
||||
},
|
||||
{
|
||||
"start": { "year": 2026, "month": 6, "day": 30 },
|
||||
"end": { "year": 2026, "month": 7, "day": 1 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"start": { "year": 2026, "month": 2, "day": 20 },
|
||||
"end": { "year": 2026, "month": 3, "day": 10 },
|
||||
"maxDays": 14,
|
||||
"chunks": [
|
||||
{
|
||||
"start": { "year": 2026, "month": 2, "day": 20 },
|
||||
"end": { "year": 2026, "month": 3, "day": 6 }
|
||||
},
|
||||
{
|
||||
"start": { "year": 2026, "month": 3, "day": 6 },
|
||||
"end": { "year": 2026, "month": 3, "day": 10 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"start": { "year": 2026, "month": 6, "day": 1 },
|
||||
"end": { "year": 2026, "month": 6, "day": 1 },
|
||||
"maxDays": null,
|
||||
"chunks": []
|
||||
},
|
||||
{
|
||||
"start": { "year": 2026, "month": 6, "day": 2 },
|
||||
"end": { "year": 2026, "month": 6, "day": 1 },
|
||||
"maxDays": null,
|
||||
"chunks": []
|
||||
}
|
||||
],
|
||||
"buildDailyRollUpBody": [
|
||||
{
|
||||
"chunk": {
|
||||
"start": { "year": 2026, "month": 1, "day": 1 },
|
||||
"end": { "year": 2026, "month": 4, "day": 1 }
|
||||
},
|
||||
"pageToken": null,
|
||||
"body": {
|
||||
"range": {
|
||||
"start": {
|
||||
"date": { "year": 2026, "month": 1, "day": 1 },
|
||||
"time": { "hours": 0, "minutes": 0, "seconds": 0, "nanos": 0 }
|
||||
},
|
||||
"end": {
|
||||
"date": { "year": 2026, "month": 3, "day": 31 },
|
||||
"time": { "hours": 23, "minutes": 59, "seconds": 59, "nanos": 0 }
|
||||
}
|
||||
},
|
||||
"windowSizeDays": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"chunk": {
|
||||
"start": { "year": 2026, "month": 2, "day": 20 },
|
||||
"end": { "year": 2026, "month": 3, "day": 1 }
|
||||
},
|
||||
"pageToken": "page-token-0002",
|
||||
"body": {
|
||||
"range": {
|
||||
"start": {
|
||||
"date": { "year": 2026, "month": 2, "day": 20 },
|
||||
"time": { "hours": 0, "minutes": 0, "seconds": 0, "nanos": 0 }
|
||||
},
|
||||
"end": {
|
||||
"date": { "year": 2026, "month": 2, "day": 28 },
|
||||
"time": { "hours": 23, "minutes": 59, "seconds": 59, "nanos": 0 }
|
||||
}
|
||||
},
|
||||
"windowSizeDays": 1,
|
||||
"pageToken": "page-token-0002"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chunk": {
|
||||
"start": { "year": 2026, "month": 12, "day": 31 },
|
||||
"end": { "year": 2027, "month": 1, "day": 1 }
|
||||
},
|
||||
"pageToken": "",
|
||||
"body": {
|
||||
"range": {
|
||||
"start": {
|
||||
"date": { "year": 2026, "month": 12, "day": 31 },
|
||||
"time": { "hours": 0, "minutes": 0, "seconds": 0, "nanos": 0 }
|
||||
},
|
||||
"end": {
|
||||
"date": { "year": 2026, "month": 12, "day": 31 },
|
||||
"time": { "hours": 23, "minutes": 59, "seconds": 59, "nanos": 0 }
|
||||
}
|
||||
},
|
||||
"windowSizeDays": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"extractGoogleApiErrorDetail": [
|
||||
{
|
||||
"body": {
|
||||
"error": {
|
||||
"code": 400,
|
||||
"status": "INVALID_ARGUMENT",
|
||||
"message": "Invalid filter for https://health.googleapis.com/v4/users/me/dataTypes/weight/dataPoints?filter=weight.date",
|
||||
"details": [
|
||||
{
|
||||
"@type": "type.googleapis.com/google.rpc.BadRequest",
|
||||
"fieldViolations": [
|
||||
{
|
||||
"field": "filter",
|
||||
"description": "Unknown field weight.date"
|
||||
},
|
||||
{ "field": "page_size" },
|
||||
{ "description": "Authorization: Bearer abc.def.ghi rejected" },
|
||||
{ "field": "fourth", "description": "dropped past the third" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"detail": "INVALID_ARGUMENT: Invalid filter for https://health.googleapis.com/v4/users/me/dataTypes/weight/dataPoints?[REDACTED] [filter: Unknown field weight.date; page_size; Authorization: Bearer [REDACTED] rejected]"
|
||||
},
|
||||
{
|
||||
"body": { "error": { "status": "RESOURCE_EXHAUSTED" } },
|
||||
"detail": "RESOURCE_EXHAUSTED"
|
||||
},
|
||||
{
|
||||
"body": {
|
||||
"error": {
|
||||
"message": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
}
|
||||
},
|
||||
"detail": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"body": {
|
||||
"error": { "details": [null, { "fieldViolations": [{}, 5] }] }
|
||||
},
|
||||
"detail": null
|
||||
},
|
||||
{ "body": { "error": "invalid_grant" }, "detail": null },
|
||||
{ "body": { "error": {} }, "detail": null },
|
||||
{ "body": null, "detail": null },
|
||||
{ "body": "plain text", "detail": null }
|
||||
],
|
||||
"refusesEdgeFilter": [
|
||||
{ "detail": null, "refuses": true },
|
||||
{ "detail": "INVALID_ARGUMENT", "refuses": true },
|
||||
{ "detail": "INVALID_ARGUMENT: Invalid filter", "refuses": true },
|
||||
{ "detail": "INVALID_ARGUMENT: Invalid page token", "refuses": false },
|
||||
{ "detail": "INVALID_ARGUMENT: page_size out of range", "refuses": false },
|
||||
{ "detail": "INVALID_ARGUMENT: pageSize too large", "refuses": false },
|
||||
{ "detail": "UNAUTHENTICATED: access token expired", "refuses": false },
|
||||
{ "detail": "FAILED_PRECONDITION: something else", "refuses": false },
|
||||
{ "detail": "Bad filter and bad token", "refuses": true }
|
||||
],
|
||||
"googleHealthRetryDelayMs": [
|
||||
{ "headers": { "retry-after": "30" }, "body": null, "delayMs": 30000 },
|
||||
{ "headers": { "retry-after": "1.5" }, "body": null, "delayMs": 1500 },
|
||||
{
|
||||
"headers": { "retry-after": "Wed, 01 Jul 2026 09:00:45 GMT" },
|
||||
"body": null,
|
||||
"delayMs": 45000
|
||||
},
|
||||
{ "headers": { "retry-after": "-3" }, "body": null, "delayMs": null },
|
||||
{
|
||||
"headers": {},
|
||||
"body": {
|
||||
"error": {
|
||||
"details": [
|
||||
{ "@type": "type.googleapis.com/google.rpc.ErrorInfo" },
|
||||
{
|
||||
"@type": "type.googleapis.com/google.rpc.RetryInfo",
|
||||
"retryDelay": "12.5s"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"delayMs": 12500
|
||||
},
|
||||
{
|
||||
"headers": {},
|
||||
"body": { "error": { "details": [{ "retryDelay": "soon" }] } },
|
||||
"delayMs": null
|
||||
},
|
||||
{ "headers": {}, "body": null, "delayMs": null }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"key": "dailyOxygenSaturation",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 96.5,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-01T12:00:00.000Z",
|
||||
"fieldTag": "2026-06-01:spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"key": "dailyOxygenSaturation",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 96.5,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-01T10:00:00.000Z",
|
||||
"fieldTag": "2026-06-01:spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"key": "dailyOxygenSaturation",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 96.5,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-01T16:00:00.000Z",
|
||||
"fieldTag": "2026-06-01:spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "dailyOxygenSaturation",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 97,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-02T12:00:00.000Z",
|
||||
"fieldTag": "2026-06-02:spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "dailyOxygenSaturation",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 97,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-02T10:00:00.000Z",
|
||||
"fieldTag": "2026-06-02:spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "dailyOxygenSaturation",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 97,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-02T16:00:00.000Z",
|
||||
"fieldTag": "2026-06-02:spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"key": "dailyHeartRateVariability",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 42.12,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-06-01T12:00:00.000Z",
|
||||
"fieldTag": "2026-06-01:hrv"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"key": "dailyHeartRateVariability",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 42.12,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-06-01T10:00:00.000Z",
|
||||
"fieldTag": "2026-06-01:hrv"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"key": "dailyHeartRateVariability",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 42.12,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-06-01T16:00:00.000Z",
|
||||
"fieldTag": "2026-06-01:hrv"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 3, "key": "dailyHeartRateVariability", "tz": null, "output": [] },
|
||||
{
|
||||
"index": 3,
|
||||
"key": "dailyHeartRateVariability",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": []
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"key": "dailyHeartRateVariability",
|
||||
"tz": "America/New_York",
|
||||
"output": []
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"key": "dailyRestingHeartRate",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 55,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-06-03T12:00:00.000Z",
|
||||
"fieldTag": "2026-06-03:rhr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"key": "dailyRestingHeartRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 55,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-06-03T10:00:00.000Z",
|
||||
"fieldTag": "2026-06-03:rhr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"key": "dailyRestingHeartRate",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 55,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-06-03T16:00:00.000Z",
|
||||
"fieldTag": "2026-06-03:rhr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"key": "dailyRestingHeartRate",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 57,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:rhr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"key": "dailyRestingHeartRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 57,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:rhr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"key": "dailyRestingHeartRate",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 57,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:rhr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"key": "dailyRespiratoryRate",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "RESPIRATORY_RATE",
|
||||
"value": 14.8,
|
||||
"unit": "breaths/min",
|
||||
"measuredAt": "2026-03-29T12:00:00.000Z",
|
||||
"fieldTag": "2026-03-29:resp_rate"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"key": "dailyRespiratoryRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "RESPIRATORY_RATE",
|
||||
"value": 14.8,
|
||||
"unit": "breaths/min",
|
||||
"measuredAt": "2026-03-29T10:00:00.000Z",
|
||||
"fieldTag": "2026-03-29:resp_rate"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"key": "dailyRespiratoryRate",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "RESPIRATORY_RATE",
|
||||
"value": 14.8,
|
||||
"unit": "breaths/min",
|
||||
"measuredAt": "2026-03-29T16:00:00.000Z",
|
||||
"fieldTag": "2026-03-29:resp_rate"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 7, "key": "dailyRespiratoryRate", "tz": null, "output": [] },
|
||||
{
|
||||
"index": 7,
|
||||
"key": "dailyRespiratoryRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": []
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"key": "dailyRespiratoryRate",
|
||||
"tz": "America/New_York",
|
||||
"output": []
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"key": "dailySleepTemperatureDerivations",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "WRIST_TEMPERATURE",
|
||||
"value": 34.42,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-06-05T12:00:00.000Z",
|
||||
"fieldTag": "2026-06-05:wrist_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"key": "dailySleepTemperatureDerivations",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "WRIST_TEMPERATURE",
|
||||
"value": 34.42,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-06-05T10:00:00.000Z",
|
||||
"fieldTag": "2026-06-05:wrist_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"key": "dailySleepTemperatureDerivations",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "WRIST_TEMPERATURE",
|
||||
"value": 34.42,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-06-05T16:00:00.000Z",
|
||||
"fieldTag": "2026-06-05:wrist_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"key": "dailySleepTemperatureDerivations",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "WRIST_TEMPERATURE",
|
||||
"value": 34.6,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:wrist_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"key": "dailySleepTemperatureDerivations",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "WRIST_TEMPERATURE",
|
||||
"value": 34.6,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:wrist_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"key": "dailySleepTemperatureDerivations",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "WRIST_TEMPERATURE",
|
||||
"value": 34.6,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:wrist_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"key": "dailyVo2Max",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 47.25,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-06-06T12:00:00.000Z",
|
||||
"fieldTag": "vo2_max:2026-06-06",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"key": "dailyVo2Max",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 47.25,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-06-06T10:00:00.000Z",
|
||||
"fieldTag": "vo2_max:2026-06-06",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"key": "dailyVo2Max",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 47.25,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-06-06T16:00:00.000Z",
|
||||
"fieldTag": "vo2_max:2026-06-06",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"key": "dailyVo2Max",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 45,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "vo2_max:1970-01-01T00:00:00.000Z",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"key": "dailyVo2Max",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 45,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "vo2_max:1970-01-01T00:00:00.000Z",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"key": "dailyVo2Max",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 45,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "vo2_max:1970-01-01T00:00:00.000Z",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 12, "key": "dailyVo2Max", "tz": null, "output": [] },
|
||||
{ "index": 12, "key": "dailyVo2Max", "tz": "Europe/Berlin", "output": [] },
|
||||
{ "index": 12, "key": "dailyVo2Max", "tz": "America/New_York", "output": [] }
|
||||
]
|
||||
@@ -0,0 +1,314 @@
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"tz": null,
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0001",
|
||||
"sportType": "running",
|
||||
"sportTypeRaw": "RUNNING",
|
||||
"startedAt": "2026-06-02T17:00:00.000Z",
|
||||
"endedAt": "2026-06-02T17:42:30.000Z",
|
||||
"durationSec": 2550,
|
||||
"totalEnergyKcal": 413,
|
||||
"totalDistanceM": 7012.35,
|
||||
"avgHeartRate": 151,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"tz": "Europe/Berlin",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0001",
|
||||
"sportType": "running",
|
||||
"sportTypeRaw": "RUNNING",
|
||||
"startedAt": "2026-06-02T17:00:00.000Z",
|
||||
"endedAt": "2026-06-02T17:42:30.000Z",
|
||||
"durationSec": 2550,
|
||||
"totalEnergyKcal": 413,
|
||||
"totalDistanceM": 7012.35,
|
||||
"avgHeartRate": 151,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"tz": "America/New_York",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0001",
|
||||
"sportType": "running",
|
||||
"sportTypeRaw": "RUNNING",
|
||||
"startedAt": "2026-06-02T17:00:00.000Z",
|
||||
"endedAt": "2026-06-02T17:42:30.000Z",
|
||||
"durationSec": 2550,
|
||||
"totalEnergyKcal": 413,
|
||||
"totalDistanceM": 7012.35,
|
||||
"avgHeartRate": 151,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"tz": null,
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0002",
|
||||
"sportType": "strength",
|
||||
"sportTypeRaw": "strength training",
|
||||
"startedAt": "2026-06-03T07:00:00.000Z",
|
||||
"endedAt": "2026-06-03T08:00:00.000Z",
|
||||
"durationSec": 3600,
|
||||
"totalEnergyKcal": 250,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"tz": "Europe/Berlin",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0002",
|
||||
"sportType": "strength",
|
||||
"sportTypeRaw": "strength training",
|
||||
"startedAt": "2026-06-03T05:00:00.000Z",
|
||||
"endedAt": "2026-06-03T06:00:00.000Z",
|
||||
"durationSec": 3600,
|
||||
"totalEnergyKcal": 250,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"tz": "America/New_York",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0002",
|
||||
"sportType": "strength",
|
||||
"sportTypeRaw": "strength training",
|
||||
"startedAt": "2026-06-03T11:00:00.000Z",
|
||||
"endedAt": "2026-06-03T12:00:00.000Z",
|
||||
"durationSec": 3600,
|
||||
"totalEnergyKcal": 250,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"tz": null,
|
||||
"workout": {
|
||||
"externalId": "exercise:2026-06-04T04:30:00.000Z",
|
||||
"sportType": "other",
|
||||
"sportTypeRaw": "KITESURFING",
|
||||
"startedAt": "2026-06-04T04:30:00.000Z",
|
||||
"endedAt": "2026-06-04T05:15:00.000Z",
|
||||
"durationSec": 2700,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": 129,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"tz": "Europe/Berlin",
|
||||
"workout": {
|
||||
"externalId": "exercise:2026-06-04T04:30:00.000Z",
|
||||
"sportType": "other",
|
||||
"sportTypeRaw": "KITESURFING",
|
||||
"startedAt": "2026-06-04T04:30:00.000Z",
|
||||
"endedAt": "2026-06-04T05:15:00.000Z",
|
||||
"durationSec": 2700,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": 129,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"tz": "America/New_York",
|
||||
"workout": {
|
||||
"externalId": "exercise:2026-06-04T04:30:00.000Z",
|
||||
"sportType": "other",
|
||||
"sportTypeRaw": "KITESURFING",
|
||||
"startedAt": "2026-06-04T04:30:00.000Z",
|
||||
"endedAt": "2026-06-04T05:15:00.000Z",
|
||||
"durationSec": 2700,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": 129,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"tz": null,
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0004",
|
||||
"sportType": "other",
|
||||
"sportTypeRaw": null,
|
||||
"startedAt": "2026-06-05T12:00:00.000Z",
|
||||
"endedAt": "2026-06-05T12:30:00.000Z",
|
||||
"durationSec": 1800,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"tz": "Europe/Berlin",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0004",
|
||||
"sportType": "other",
|
||||
"sportTypeRaw": null,
|
||||
"startedAt": "2026-06-05T12:00:00.000Z",
|
||||
"endedAt": "2026-06-05T12:30:00.000Z",
|
||||
"durationSec": 1800,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"tz": "America/New_York",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0004",
|
||||
"sportType": "other",
|
||||
"sportTypeRaw": null,
|
||||
"startedAt": "2026-06-05T12:00:00.000Z",
|
||||
"endedAt": "2026-06-05T12:30:00.000Z",
|
||||
"durationSec": 1800,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{ "index": 4, "tz": null, "workout": null },
|
||||
{ "index": 4, "tz": "Europe/Berlin", "workout": null },
|
||||
{ "index": 4, "tz": "America/New_York", "workout": null },
|
||||
{ "index": 5, "tz": null, "workout": null },
|
||||
{ "index": 5, "tz": "Europe/Berlin", "workout": null },
|
||||
{ "index": 5, "tz": "America/New_York", "workout": null },
|
||||
{
|
||||
"index": 6,
|
||||
"tz": null,
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0007",
|
||||
"sportType": "cycling",
|
||||
"sportTypeRaw": "Mountain-Biking",
|
||||
"startedAt": "2026-06-08T18:00:00.000Z",
|
||||
"endedAt": "2026-06-08T19:05:00.000Z",
|
||||
"durationSec": 3900,
|
||||
"totalEnergyKcal": 0,
|
||||
"totalDistanceM": 0,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"tz": "Europe/Berlin",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0007",
|
||||
"sportType": "cycling",
|
||||
"sportTypeRaw": "Mountain-Biking",
|
||||
"startedAt": "2026-06-08T18:00:00.000Z",
|
||||
"endedAt": "2026-06-08T19:05:00.000Z",
|
||||
"durationSec": 3900,
|
||||
"totalEnergyKcal": 0,
|
||||
"totalDistanceM": 0,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"tz": "America/New_York",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0007",
|
||||
"sportType": "cycling",
|
||||
"sportTypeRaw": "Mountain-Biking",
|
||||
"startedAt": "2026-06-08T18:00:00.000Z",
|
||||
"endedAt": "2026-06-08T19:05:00.000Z",
|
||||
"durationSec": 3900,
|
||||
"totalEnergyKcal": 0,
|
||||
"totalDistanceM": 0,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"tz": null,
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0008",
|
||||
"sportType": "stairClimber",
|
||||
"sportTypeRaw": "STAIR_CLIMBER",
|
||||
"startedAt": "2026-06-09T20:00:00.000Z",
|
||||
"endedAt": "2026-06-09T20:20:00.000Z",
|
||||
"durationSec": 1200,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"tz": "Europe/Berlin",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0008",
|
||||
"sportType": "stairClimber",
|
||||
"sportTypeRaw": "STAIR_CLIMBER",
|
||||
"startedAt": "2026-06-09T20:00:00.000Z",
|
||||
"endedAt": "2026-06-09T20:20:00.000Z",
|
||||
"durationSec": 1200,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"tz": "America/New_York",
|
||||
"workout": {
|
||||
"externalId": "users/me/dataTypes/exercise/dataPoints/exercise-0008",
|
||||
"sportType": "stairClimber",
|
||||
"sportTypeRaw": "STAIR_CLIMBER",
|
||||
"startedAt": "2026-06-09T20:00:00.000Z",
|
||||
"endedAt": "2026-06-09T20:20:00.000Z",
|
||||
"durationSec": 1200,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"minHeartRate": null
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"sleepStages": [
|
||||
{ "raw": "AWAKE", "stage": "AWAKE" },
|
||||
{ "raw": "wake", "stage": "AWAKE" },
|
||||
{ "raw": "LIGHT", "stage": "CORE" },
|
||||
{ "raw": " core ", "stage": "CORE" },
|
||||
{ "raw": "Deep", "stage": "DEEP" },
|
||||
{ "raw": "REM", "stage": "REM" },
|
||||
{ "raw": "ASLEEP", "stage": "ASLEEP" },
|
||||
{ "raw": "RESTLESS", "stage": "AWAKE" },
|
||||
{ "raw": "in bed", "stage": "IN_BED" },
|
||||
{ "raw": "IN-BED", "stage": "IN_BED" },
|
||||
{ "raw": "inbed", "stage": "IN_BED" },
|
||||
{ "raw": "SLEEP_STAGE_TYPE_UNSPECIFIED", "stage": null },
|
||||
{ "raw": "", "stage": null },
|
||||
{ "raw": 42, "stage": null },
|
||||
{ "raw": null, "stage": null }
|
||||
],
|
||||
"sportTypes": [
|
||||
{ "raw": "RUNNING", "sportType": "running" },
|
||||
{ "raw": "TREADMILL_RUNNING", "sportType": "running" },
|
||||
{ "raw": "STRENGTH_TRAINING", "sportType": "strength" },
|
||||
{ "raw": "strength training", "sportType": "strength" },
|
||||
{ "raw": "Mountain-Biking", "sportType": "cycling" },
|
||||
{ "raw": "STAIR_CLIMBER", "sportType": "stairClimber" },
|
||||
{ "raw": "STAIRCLIMBER", "sportType": "stairClimber" },
|
||||
{ "raw": "HIGH INTENSITY INTERVAL TRAINING", "sportType": "hiit" },
|
||||
{ "raw": "FOOTBALL", "sportType": "soccer" },
|
||||
{ "raw": "PILATES", "sportType": "mindAndBody" },
|
||||
{ "raw": "OTHER_WORKOUT", "sportType": "other" },
|
||||
{ "raw": "KITESURFING", "sportType": "other" },
|
||||
{ "raw": "", "sportType": "other" },
|
||||
{ "raw": " ", "sportType": "other" },
|
||||
{ "raw": null, "sportType": "other" },
|
||||
{ "raw": 7, "sportType": "other" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"key": "steps",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 8432,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-01T12:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"key": "steps",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 8432,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-01T10:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"key": "steps",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 8432,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-01T16:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "steps",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-02T12:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-02",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "steps",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-02T10:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-02",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "steps",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-02T16:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-02",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"key": "steps",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 10250,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-03T12:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-03",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"key": "steps",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 10250,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-03T10:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-03",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"key": "steps",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 10250,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-06-03T16:00:00.000Z",
|
||||
"fieldTag": "steps:2026-06-03",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 3, "key": "steps", "tz": null, "output": [] },
|
||||
{ "index": 3, "key": "steps", "tz": "Europe/Berlin", "output": [] },
|
||||
{ "index": 3, "key": "steps", "tz": "America/New_York", "output": [] },
|
||||
{
|
||||
"index": 4,
|
||||
"key": "distance",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 6240.51,
|
||||
"unit": "m",
|
||||
"measuredAt": "2026-06-01T12:00:00.000Z",
|
||||
"fieldTag": "distance:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"key": "distance",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 6240.51,
|
||||
"unit": "m",
|
||||
"measuredAt": "2026-06-01T10:00:00.000Z",
|
||||
"fieldTag": "distance:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"key": "distance",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 6240.51,
|
||||
"unit": "m",
|
||||
"measuredAt": "2026-06-01T16:00:00.000Z",
|
||||
"fieldTag": "distance:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 5, "key": "distance", "tz": null, "output": [] },
|
||||
{ "index": 5, "key": "distance", "tz": "Europe/Berlin", "output": [] },
|
||||
{ "index": 5, "key": "distance", "tz": "America/New_York", "output": [] },
|
||||
{
|
||||
"index": 6,
|
||||
"key": "activeEnergyBurned",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 512.75,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-06-01T12:00:00.000Z",
|
||||
"fieldTag": "active_energy:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"key": "activeEnergyBurned",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 512.75,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-06-01T10:00:00.000Z",
|
||||
"fieldTag": "active_energy:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"key": "activeEnergyBurned",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 512.75,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-06-01T16:00:00.000Z",
|
||||
"fieldTag": "active_energy:2026-06-01",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 7, "key": "activeEnergyBurned", "tz": null, "output": [] },
|
||||
{
|
||||
"index": 7,
|
||||
"key": "activeEnergyBurned",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": []
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"key": "activeEnergyBurned",
|
||||
"tz": "America/New_York",
|
||||
"output": []
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"key": "floors",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "FLIGHTS_CLIMBED",
|
||||
"value": 12,
|
||||
"unit": "flights",
|
||||
"measuredAt": "2026-12-31T12:00:00.000Z",
|
||||
"fieldTag": "floors:2026-12-31",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"key": "floors",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "FLIGHTS_CLIMBED",
|
||||
"value": 12,
|
||||
"unit": "flights",
|
||||
"measuredAt": "2026-12-31T11:00:00.000Z",
|
||||
"fieldTag": "floors:2026-12-31",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"key": "floors",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "FLIGHTS_CLIMBED",
|
||||
"value": 12,
|
||||
"unit": "flights",
|
||||
"measuredAt": "2026-12-31T17:00:00.000Z",
|
||||
"fieldTag": "floors:2026-12-31",
|
||||
"cumulativeDaily": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 9, "key": "floors", "tz": null, "output": [] },
|
||||
{ "index": 9, "key": "floors", "tz": "Europe/Berlin", "output": [] },
|
||||
{ "index": 9, "key": "floors", "tz": "America/New_York", "output": [] }
|
||||
]
|
||||
@@ -0,0 +1,693 @@
|
||||
{
|
||||
"measurements": [
|
||||
{
|
||||
"index": 0,
|
||||
"key": "weight",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 78.4,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-02T06:15:00.000Z",
|
||||
"fieldTag": "2026-06-02T06:15:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"key": "weight",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 78.4,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-02T06:15:00.000Z",
|
||||
"fieldTag": "2026-06-02T06:15:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"key": "weight",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 78.4,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-02T06:15:00.000Z",
|
||||
"fieldTag": "2026-06-02T06:15:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "weight",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 78.25,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-03T05:30:00.000Z",
|
||||
"fieldTag": "2026-06-03T05:30:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "weight",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 78.25,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-03T05:30:00.000Z",
|
||||
"fieldTag": "2026-06-03T05:30:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"key": "weight",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 78.25,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-03T05:30:00.000Z",
|
||||
"fieldTag": "2026-06-03T05:30:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 2, "key": "weight", "tz": null, "output": [] },
|
||||
{ "index": 2, "key": "weight", "tz": "Europe/Berlin", "output": [] },
|
||||
{ "index": 2, "key": "weight", "tz": "America/New_York", "output": [] },
|
||||
{
|
||||
"index": 3,
|
||||
"key": "weight",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 77.9,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-04T23:45:00.000Z",
|
||||
"fieldTag": "2026-06-04T23:45:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"key": "weight",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 77.9,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-04T21:45:00.000Z",
|
||||
"fieldTag": "2026-06-04T23:45:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"key": "weight",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "WEIGHT",
|
||||
"value": 77.9,
|
||||
"unit": "kg",
|
||||
"measuredAt": "2026-06-05T03:45:00.000Z",
|
||||
"fieldTag": "2026-06-04T23:45:00.000Z:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"key": "bodyFat",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_FAT",
|
||||
"value": 21.4,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-02T06:15:00.000Z",
|
||||
"fieldTag": "2026-06-02T06:15:00.000Z:body_fat"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"key": "bodyFat",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_FAT",
|
||||
"value": 21.4,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-02T06:15:00.000Z",
|
||||
"fieldTag": "2026-06-02T06:15:00.000Z:body_fat"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"key": "bodyFat",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_FAT",
|
||||
"value": 21.4,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-06-02T06:15:00.000Z",
|
||||
"fieldTag": "2026-06-02T06:15:00.000Z:body_fat"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"key": "bodyFat",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_FAT",
|
||||
"value": 20.9,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:body_fat"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"key": "bodyFat",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_FAT",
|
||||
"value": 20.9,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:body_fat"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"key": "bodyFat",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_FAT",
|
||||
"value": 20.9,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:body_fat"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"key": "heartRate",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "PULSE",
|
||||
"value": 64,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-06-02T16:00:30.000Z",
|
||||
"fieldTag": "2026-06-02T16:00:30.000Z:hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"key": "heartRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "PULSE",
|
||||
"value": 64,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-06-02T16:00:30.000Z",
|
||||
"fieldTag": "2026-06-02T16:00:30.000Z:hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"key": "heartRate",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "PULSE",
|
||||
"value": 64,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-06-02T16:00:30.000Z",
|
||||
"fieldTag": "2026-06-02T16:00:30.000Z:hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 7, "key": "heartRate", "tz": null, "output": [] },
|
||||
{ "index": 7, "key": "heartRate", "tz": "Europe/Berlin", "output": [] },
|
||||
{ "index": 7, "key": "heartRate", "tz": "America/New_York", "output": [] },
|
||||
{
|
||||
"index": 8,
|
||||
"key": "heartRate",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "PULSE",
|
||||
"value": 58,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"key": "heartRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "PULSE",
|
||||
"value": 58,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"key": "heartRate",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "PULSE",
|
||||
"value": 58,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-07-01T09:00:00.000Z",
|
||||
"fieldTag": "1970-01-01T00:00:00.000Z:hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"key": "bloodGlucose",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 96.33,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "2026-06-05T08:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T08:00:00.000Z:glucose"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"key": "bloodGlucose",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 96.33,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "2026-06-05T08:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T08:00:00.000Z:glucose"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"key": "bloodGlucose",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 96.33,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "2026-06-05T08:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T08:00:00.000Z:glucose"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "index": 10, "key": "bloodGlucose", "tz": null, "output": [] },
|
||||
{ "index": 10, "key": "bloodGlucose", "tz": "Europe/Berlin", "output": [] },
|
||||
{
|
||||
"index": 10,
|
||||
"key": "bloodGlucose",
|
||||
"tz": "America/New_York",
|
||||
"output": []
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"key": "coreBodyTemperature",
|
||||
"tz": null,
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE",
|
||||
"value": 36.75,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-06-06T03:00:00.000Z",
|
||||
"fieldTag": "2026-06-06T03:00:00.000Z:core_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"key": "coreBodyTemperature",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE",
|
||||
"value": 36.75,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-06-06T01:00:00.000Z",
|
||||
"fieldTag": "2026-06-06T03:00:00.000Z:core_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"key": "coreBodyTemperature",
|
||||
"tz": "America/New_York",
|
||||
"output": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE",
|
||||
"value": 36.75,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-06-06T07:00:00.000Z",
|
||||
"fieldTag": "2026-06-06T03:00:00.000Z:core_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 12,
|
||||
"key": "height",
|
||||
"tz": null,
|
||||
"output": { "cm": 180.5, "sampledAt": "2026-01-15T10:00:00.000Z" }
|
||||
},
|
||||
{
|
||||
"index": 12,
|
||||
"key": "height",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": { "cm": 180.5, "sampledAt": "2026-01-15T10:00:00.000Z" }
|
||||
},
|
||||
{
|
||||
"index": 12,
|
||||
"key": "height",
|
||||
"tz": "America/New_York",
|
||||
"output": { "cm": 180.5, "sampledAt": "2026-01-15T10:00:00.000Z" }
|
||||
},
|
||||
{
|
||||
"index": 13,
|
||||
"key": "height",
|
||||
"tz": null,
|
||||
"output": { "cm": 179.8, "sampledAt": null }
|
||||
},
|
||||
{
|
||||
"index": 13,
|
||||
"key": "height",
|
||||
"tz": "Europe/Berlin",
|
||||
"output": { "cm": 179.8, "sampledAt": null }
|
||||
},
|
||||
{
|
||||
"index": 13,
|
||||
"key": "height",
|
||||
"tz": "America/New_York",
|
||||
"output": { "cm": 179.8, "sampledAt": null }
|
||||
},
|
||||
{ "index": 14, "key": "height", "tz": null, "output": null },
|
||||
{ "index": 14, "key": "height", "tz": "Europe/Berlin", "output": null },
|
||||
{ "index": 14, "key": "height", "tz": "America/New_York", "output": null }
|
||||
],
|
||||
"sampleInstants": [
|
||||
{
|
||||
"index": 0,
|
||||
"dataType": "weight",
|
||||
"tz": null,
|
||||
"instant": "2026-06-02T06:15:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"dataType": "weight",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-02T06:15:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"dataType": "weight",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-02T06:15:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"dataType": "weight",
|
||||
"tz": null,
|
||||
"instant": "2026-06-03T05:30:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"dataType": "weight",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-03T05:30:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"dataType": "weight",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-03T05:30:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"dataType": "weight",
|
||||
"tz": null,
|
||||
"instant": "2026-06-04T23:45:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"dataType": "weight",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-04T21:45:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"dataType": "weight",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-05T03:45:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"dataType": "weight",
|
||||
"tz": null,
|
||||
"instant": "2026-06-04T23:45:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"dataType": "weight",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-04T21:45:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"dataType": "weight",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-05T03:45:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"dataType": "bodyFat",
|
||||
"tz": null,
|
||||
"instant": "2026-06-02T06:15:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"dataType": "bodyFat",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-02T06:15:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"dataType": "bodyFat",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-02T06:15:00.000Z"
|
||||
},
|
||||
{ "index": 5, "dataType": "bodyFat", "tz": null, "instant": null },
|
||||
{
|
||||
"index": 5,
|
||||
"dataType": "bodyFat",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": null
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"dataType": "bodyFat",
|
||||
"tz": "America/New_York",
|
||||
"instant": null
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"dataType": "heartRate",
|
||||
"tz": null,
|
||||
"instant": "2026-06-02T16:00:30.000Z"
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"dataType": "heartRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-02T16:00:30.000Z"
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"dataType": "heartRate",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-02T16:00:30.000Z"
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"dataType": "heartRate",
|
||||
"tz": null,
|
||||
"instant": "2026-06-02T00:10:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"dataType": "heartRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-01T22:10:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"dataType": "heartRate",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-02T04:10:00.000Z"
|
||||
},
|
||||
{ "index": 8, "dataType": "heartRate", "tz": null, "instant": null },
|
||||
{
|
||||
"index": 8,
|
||||
"dataType": "heartRate",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": null
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"dataType": "heartRate",
|
||||
"tz": "America/New_York",
|
||||
"instant": null
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"dataType": "bloodGlucose",
|
||||
"tz": null,
|
||||
"instant": "2026-06-05T08:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"dataType": "bloodGlucose",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-05T08:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"dataType": "bloodGlucose",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-05T08:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"dataType": "bloodGlucose",
|
||||
"tz": null,
|
||||
"instant": "2026-06-05T09:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"dataType": "bloodGlucose",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-05T09:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"dataType": "bloodGlucose",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-05T09:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"dataType": "coreBodyTemperature",
|
||||
"tz": null,
|
||||
"instant": "2026-06-06T03:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"dataType": "coreBodyTemperature",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-06-06T01:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"dataType": "coreBodyTemperature",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-06-06T07:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 12,
|
||||
"dataType": "height",
|
||||
"tz": null,
|
||||
"instant": "2026-01-15T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 12,
|
||||
"dataType": "height",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-01-15T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 12,
|
||||
"dataType": "height",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-01-15T10:00:00.000Z"
|
||||
},
|
||||
{ "index": 13, "dataType": "height", "tz": null, "instant": null },
|
||||
{
|
||||
"index": 13,
|
||||
"dataType": "height",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": null
|
||||
},
|
||||
{
|
||||
"index": 13,
|
||||
"dataType": "height",
|
||||
"tz": "America/New_York",
|
||||
"instant": null
|
||||
},
|
||||
{
|
||||
"index": 14,
|
||||
"dataType": "height",
|
||||
"tz": null,
|
||||
"instant": "2026-01-16T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 14,
|
||||
"dataType": "height",
|
||||
"tz": "Europe/Berlin",
|
||||
"instant": "2026-01-16T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 14,
|
||||
"dataType": "height",
|
||||
"tz": "America/New_York",
|
||||
"instant": "2026-01-16T10:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sampleInstantOfDailyType": null
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"tz": null,
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0001",
|
||||
"windowStart": "2026-06-01T22:30:00.000Z",
|
||||
"windowEnd": "2026-06-02T06:10:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-01T22:45:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:30:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T00:15:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:45:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 65.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T01:20:30.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T00:15:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 44.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T02:05:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T01:20:30.000Z",
|
||||
"sleepStage": "REM"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 245,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T06:10:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T02:05:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-01T22:45:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:30:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T00:15:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:45:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 65.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T01:20:30.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T00:15:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 44.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T02:05:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T01:20:30.000Z",
|
||||
"sleepStage": "REM"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 245,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T06:10:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T02:05:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"tz": "Europe/Berlin",
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0001",
|
||||
"windowStart": "2026-06-01T22:30:00.000Z",
|
||||
"windowEnd": "2026-06-02T06:10:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-01T22:45:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:30:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T00:15:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:45:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 65.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T01:20:30.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T00:15:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 44.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T02:05:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T01:20:30.000Z",
|
||||
"sleepStage": "REM"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 245,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T06:10:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T02:05:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-01T22:45:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:30:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T00:15:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:45:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 65.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T01:20:30.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T00:15:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 44.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T02:05:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T01:20:30.000Z",
|
||||
"sleepStage": "REM"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 245,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T06:10:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T02:05:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"tz": "America/New_York",
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0001",
|
||||
"windowStart": "2026-06-01T22:30:00.000Z",
|
||||
"windowEnd": "2026-06-02T06:10:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-01T22:45:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:30:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T00:15:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:45:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 65.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T01:20:30.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T00:15:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 44.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T02:05:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T01:20:30.000Z",
|
||||
"sleepStage": "REM"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 245,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T06:10:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T02:05:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-01T22:45:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:30:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T00:15:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-01T22:45:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 65.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T01:20:30.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T00:15:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 44.5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T02:05:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T01:20:30.000Z",
|
||||
"sleepStage": "REM"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 245,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T06:10:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0001:sleep:2026-06-02T02:05:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"tz": null,
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0002",
|
||||
"windowStart": "2026-06-02T23:50:00.000Z",
|
||||
"windowEnd": "2026-06-03T07:00:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T00:20:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-02T23:50:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T00:25:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T00:20:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 395,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T07:00:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T00:25:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T00:20:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-02T23:50:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T00:25:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T00:20:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 395,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T07:00:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T00:25:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"tz": "Europe/Berlin",
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0002",
|
||||
"windowStart": "2026-06-02T21:50:00.000Z",
|
||||
"windowEnd": "2026-06-03T05:00:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T22:20:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-02T21:50:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T22:25:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-02T22:20:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 395,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T05:00:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-02T22:25:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T22:20:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-02T21:50:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-02T22:25:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-02T22:20:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 395,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T05:00:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-02T22:25:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"tz": "America/New_York",
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0002",
|
||||
"windowStart": "2026-06-03T03:50:00.000Z",
|
||||
"windowEnd": "2026-06-03T11:00:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T04:20:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T03:50:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T04:25:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T04:20:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 395,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T11:00:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T04:25:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T04:20:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T03:50:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 5,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T04:25:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T04:20:00.000Z",
|
||||
"sleepStage": "AWAKE"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 395,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-03T11:00:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0002:sleep:2026-06-03T04:25:00.000Z",
|
||||
"sleepStage": "ASLEEP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"tz": null,
|
||||
"detailed": {
|
||||
"anchor": "2026-06-05T03:30:00.000Z",
|
||||
"windowStart": "2026-06-04T20:00:00.000Z",
|
||||
"windowEnd": "2026-06-04T23:00:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 180,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-04T23:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T03:30:00.000Z:sleep:2026-06-04T20:00:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 1,
|
||||
"unknownStageLabels": ["SLEEP_STAGE_TYPE_UNSPECIFIED"]
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 180,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-04T23:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T03:30:00.000Z:sleep:2026-06-04T20:00:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"tz": "Europe/Berlin",
|
||||
"detailed": {
|
||||
"anchor": "2026-06-05T03:30:00.000Z",
|
||||
"windowStart": "2026-06-04T20:00:00.000Z",
|
||||
"windowEnd": "2026-06-04T23:00:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 180,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-04T23:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T03:30:00.000Z:sleep:2026-06-04T20:00:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 1,
|
||||
"unknownStageLabels": ["SLEEP_STAGE_TYPE_UNSPECIFIED"]
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 180,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-04T23:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T03:30:00.000Z:sleep:2026-06-04T20:00:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"tz": "America/New_York",
|
||||
"detailed": {
|
||||
"anchor": "2026-06-05T03:30:00.000Z",
|
||||
"windowStart": "2026-06-04T20:00:00.000Z",
|
||||
"windowEnd": "2026-06-04T23:00:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 180,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-04T23:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T03:30:00.000Z:sleep:2026-06-04T20:00:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 1,
|
||||
"unknownStageLabels": ["SLEEP_STAGE_TYPE_UNSPECIFIED"]
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 180,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-04T23:00:00.000Z",
|
||||
"fieldTag": "2026-06-05T03:30:00.000Z:sleep:2026-06-04T20:00:00.000Z",
|
||||
"sleepStage": "DEEP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"tz": null,
|
||||
"detailed": {
|
||||
"anchor": "2026-06-06T23:00:00.000Z",
|
||||
"windowStart": null,
|
||||
"windowEnd": null,
|
||||
"rows": [],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"tz": "Europe/Berlin",
|
||||
"detailed": {
|
||||
"anchor": "2026-06-06T23:00:00.000Z",
|
||||
"windowStart": null,
|
||||
"windowEnd": null,
|
||||
"rows": [],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"tz": "America/New_York",
|
||||
"detailed": {
|
||||
"anchor": "2026-06-06T23:00:00.000Z",
|
||||
"windowStart": null,
|
||||
"windowEnd": null,
|
||||
"rows": [],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"tz": null,
|
||||
"detailed": {
|
||||
"anchor": "1970-01-01T00:00:00.000Z",
|
||||
"windowStart": null,
|
||||
"windowEnd": null,
|
||||
"rows": [],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"tz": "Europe/Berlin",
|
||||
"detailed": {
|
||||
"anchor": "1970-01-01T00:00:00.000Z",
|
||||
"windowStart": null,
|
||||
"windowEnd": null,
|
||||
"rows": [],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"tz": "America/New_York",
|
||||
"detailed": {
|
||||
"anchor": "1970-01-01T00:00:00.000Z",
|
||||
"windowStart": null,
|
||||
"windowEnd": null,
|
||||
"rows": [],
|
||||
"unknownStageSegments": 0,
|
||||
"unknownStageLabels": []
|
||||
},
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"tz": null,
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0006",
|
||||
"windowStart": "2026-06-08T01:10:00.000Z",
|
||||
"windowEnd": "2026-06-08T01:40:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-08T01:40:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0006:sleep:2026-06-08T01:10:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 7,
|
||||
"unknownStageLabels": [
|
||||
"NAP_STAGE_ALPHA",
|
||||
"NAP_STAGE_BETA",
|
||||
"NAP_STAGE_GAMMA",
|
||||
"NAP_STAGE_DELTA",
|
||||
"NAP_STAGE_EPSILON"
|
||||
]
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-08T01:40:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0006:sleep:2026-06-08T01:10:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"tz": "Europe/Berlin",
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0006",
|
||||
"windowStart": "2026-06-08T01:10:00.000Z",
|
||||
"windowEnd": "2026-06-08T01:40:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-08T01:40:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0006:sleep:2026-06-08T01:10:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 7,
|
||||
"unknownStageLabels": [
|
||||
"NAP_STAGE_ALPHA",
|
||||
"NAP_STAGE_BETA",
|
||||
"NAP_STAGE_GAMMA",
|
||||
"NAP_STAGE_DELTA",
|
||||
"NAP_STAGE_EPSILON"
|
||||
]
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-08T01:40:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0006:sleep:2026-06-08T01:10:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"tz": "America/New_York",
|
||||
"detailed": {
|
||||
"anchor": "users/me/dataTypes/sleep/dataPoints/sleep-0006",
|
||||
"windowStart": "2026-06-08T01:10:00.000Z",
|
||||
"windowEnd": "2026-06-08T01:40:00.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-08T01:40:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0006:sleep:2026-06-08T01:10:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
],
|
||||
"unknownStageSegments": 7,
|
||||
"unknownStageLabels": [
|
||||
"NAP_STAGE_ALPHA",
|
||||
"NAP_STAGE_BETA",
|
||||
"NAP_STAGE_GAMMA",
|
||||
"NAP_STAGE_DELTA",
|
||||
"NAP_STAGE_EPSILON"
|
||||
]
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-06-08T01:40:00.000Z",
|
||||
"fieldTag": "users/me/dataTypes/sleep/dataPoints/sleep-0006:sleep:2026-06-08T01:10:00.000Z",
|
||||
"sleepStage": "CORE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,460 @@
|
||||
/**
|
||||
* What each Google Health mapper makes of a fixed set of payloads, pinned in
|
||||
* `__golden__/map-*.json`.
|
||||
*
|
||||
* The fixtures under `__fixtures__/` are synthetic data points in the
|
||||
* documented v4 wire shape (spot samples, daily summaries, dailyRollUp
|
||||
* windows, sleep sessions, exercise sessions), chosen to walk every branch the
|
||||
* mappers have: int64 values as strings, zeros and negatives, missing or
|
||||
* unparseable anchors, offset-less local times, unknown stage and sport
|
||||
* labels. Every mapper that takes a zone runs once without one and once each
|
||||
* for `Europe/Berlin` and `America/New_York`. The pure request helpers from
|
||||
* `client.ts` run over small input tables in the test itself.
|
||||
*
|
||||
* The goldens were written before the mappers moved out of `client.ts` and
|
||||
* `mappers.ts` into their own modules, so a green run proves the move changed
|
||||
* no output. The clock is pinned because a sample without a parseable anchor
|
||||
* falls back to the current time.
|
||||
*/
|
||||
import { format } from "prettier";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
GOOGLE_HEALTH_DATA_TYPES,
|
||||
googleHealthSampleInstant,
|
||||
mapActiveEnergy,
|
||||
mapBloodGlucose,
|
||||
mapBodyFat,
|
||||
mapCoreBodyTemperature,
|
||||
mapDistance,
|
||||
mapFloors,
|
||||
mapGoogleHealthSleepStage,
|
||||
mapGoogleHealthSportType,
|
||||
mapHeartRate,
|
||||
mapHeartRateVariability,
|
||||
mapHeight,
|
||||
mapOxygenSaturation,
|
||||
mapRespiratoryRate,
|
||||
mapRestingHeartRate,
|
||||
mapSleepSession,
|
||||
mapSleepSessionDetailed,
|
||||
mapSteps,
|
||||
mapVo2Max,
|
||||
mapWeight,
|
||||
mapWorkout,
|
||||
mapWristTemperature,
|
||||
type GoogleHealthDataPoint,
|
||||
type GoogleHealthDataType,
|
||||
type GoogleHealthRollupPoint,
|
||||
} from "../mappers";
|
||||
import {
|
||||
buildDailyRollUpBody,
|
||||
chunkCivilRange,
|
||||
civilDateInTz,
|
||||
extractGoogleApiErrorDetail,
|
||||
formatCivilBound,
|
||||
googleHealthRetryDelayMs,
|
||||
incrementalFilter,
|
||||
refusesEdgeFilter,
|
||||
resolveGoogleHealthUserId,
|
||||
} from "../client";
|
||||
import dailySummariesJson from "./__fixtures__/daily-summaries.json";
|
||||
import exerciseJson from "./__fixtures__/exercise.json";
|
||||
import rollupsJson from "./__fixtures__/rollups.json";
|
||||
import samplesJson from "./__fixtures__/samples.json";
|
||||
import sleepJson from "./__fixtures__/sleep.json";
|
||||
|
||||
/** The golden is written in the repository's JSON format, so the format check passes on it unchanged. */
|
||||
async function golden(value: unknown): Promise<string> {
|
||||
return format(JSON.stringify(value), { parser: "json" });
|
||||
}
|
||||
|
||||
/** Captures a thrown error as data, so a refusal is part of the golden too. */
|
||||
function outcome<T>(fn: () => T): T | { threw: string } {
|
||||
try {
|
||||
return fn();
|
||||
} catch (err) {
|
||||
return { threw: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
const samples: GoogleHealthDataPoint[] = samplesJson;
|
||||
const dailySummaries: GoogleHealthDataPoint[] = dailySummariesJson;
|
||||
const rollups: GoogleHealthRollupPoint[] = rollupsJson;
|
||||
const sleepSessions: GoogleHealthDataPoint[] = sleepJson;
|
||||
const exercises: GoogleHealthDataPoint[] = exerciseJson;
|
||||
|
||||
const ZONES = [undefined, "Europe/Berlin", "America/New_York"] as const;
|
||||
|
||||
type ZonedMapper<P, R> = (point: P, tz?: string) => R;
|
||||
|
||||
/** Each fixture point through the mapper for the union key it carries, in every zone. */
|
||||
function mapByKey<P extends Record<string, unknown>, R>(
|
||||
points: P[],
|
||||
mappers: Record<string, ZonedMapper<P, R>>,
|
||||
): Array<{ index: number; key: string; tz: string | null; output: R }> {
|
||||
const out: Array<{
|
||||
index: number;
|
||||
key: string;
|
||||
tz: string | null;
|
||||
output: R;
|
||||
}> = [];
|
||||
points.forEach((point, index) => {
|
||||
for (const [key, mapper] of Object.entries(mappers)) {
|
||||
if (!(key in point)) continue;
|
||||
for (const tz of ZONES) {
|
||||
out.push({ index, key, tz: tz ?? null, output: mapper(point, tz) });
|
||||
}
|
||||
}
|
||||
});
|
||||
if (out.length !== points.length * ZONES.length) {
|
||||
throw new Error("a fixture point matched no mapper, or more than one");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const FIXED_NOW = new Date("2026-07-01T09:00:00.000Z");
|
||||
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers({ now: FIXED_NOW, toFake: ["Date"] });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("Google Health mapper goldens", () => {
|
||||
it("maps spot samples", async () => {
|
||||
const output = {
|
||||
measurements: mapByKey<GoogleHealthDataPoint, unknown>(samples, {
|
||||
weight: mapWeight,
|
||||
bodyFat: mapBodyFat,
|
||||
heartRate: mapHeartRate,
|
||||
bloodGlucose: mapBloodGlucose,
|
||||
coreBodyTemperature: mapCoreBodyTemperature,
|
||||
height: (point) => mapHeight(point),
|
||||
}),
|
||||
sampleInstants: samples.flatMap((point, index) =>
|
||||
Object.entries(GOOGLE_HEALTH_DATA_TYPES)
|
||||
.filter(([, dt]) => dt.key in point)
|
||||
.flatMap(([name, dt]) =>
|
||||
ZONES.map((tz) => ({
|
||||
index,
|
||||
dataType: name,
|
||||
tz: tz ?? null,
|
||||
instant: googleHealthSampleInstant(point, dt, tz),
|
||||
})),
|
||||
),
|
||||
),
|
||||
sampleInstantOfDailyType: googleHealthSampleInstant(
|
||||
dailySummaries[0]!,
|
||||
GOOGLE_HEALTH_DATA_TYPES.oxygenSaturation,
|
||||
),
|
||||
};
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-samples.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps daily summaries", async () => {
|
||||
const output = mapByKey(dailySummaries, {
|
||||
dailyOxygenSaturation: mapOxygenSaturation,
|
||||
dailyHeartRateVariability: mapHeartRateVariability,
|
||||
dailyRestingHeartRate: mapRestingHeartRate,
|
||||
dailyRespiratoryRate: mapRespiratoryRate,
|
||||
dailySleepTemperatureDerivations: mapWristTemperature,
|
||||
dailyVo2Max: mapVo2Max,
|
||||
});
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-daily-summaries.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps dailyRollUp windows", async () => {
|
||||
const output = mapByKey(rollups, {
|
||||
steps: mapSteps,
|
||||
distance: mapDistance,
|
||||
activeEnergyBurned: mapActiveEnergy,
|
||||
floors: mapFloors,
|
||||
});
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-rollups.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps sleep sessions", async () => {
|
||||
const output = sleepSessions.flatMap((point, index) =>
|
||||
ZONES.map((tz) => ({
|
||||
index,
|
||||
tz: tz ?? null,
|
||||
detailed: mapSleepSessionDetailed(point, tz),
|
||||
rows: mapSleepSession(point, tz),
|
||||
})),
|
||||
);
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-sleep.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps exercise sessions", async () => {
|
||||
const output = exercises.flatMap((point, index) =>
|
||||
ZONES.map((tz) => ({
|
||||
index,
|
||||
tz: tz ?? null,
|
||||
workout: mapWorkout(point, tz),
|
||||
})),
|
||||
);
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-exercise.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves sleep stage and sport labels", async () => {
|
||||
const stageLabels: unknown[] = [
|
||||
"AWAKE",
|
||||
"wake",
|
||||
"LIGHT",
|
||||
" core ",
|
||||
"Deep",
|
||||
"REM",
|
||||
"ASLEEP",
|
||||
"RESTLESS",
|
||||
"in bed",
|
||||
"IN-BED",
|
||||
"inbed",
|
||||
"SLEEP_STAGE_TYPE_UNSPECIFIED",
|
||||
"",
|
||||
42,
|
||||
null,
|
||||
];
|
||||
const sportLabels: unknown[] = [
|
||||
"RUNNING",
|
||||
"TREADMILL_RUNNING",
|
||||
"STRENGTH_TRAINING",
|
||||
"strength training",
|
||||
"Mountain-Biking",
|
||||
"STAIR_CLIMBER",
|
||||
"STAIRCLIMBER",
|
||||
"HIGH INTENSITY INTERVAL TRAINING",
|
||||
"FOOTBALL",
|
||||
"PILATES",
|
||||
"OTHER_WORKOUT",
|
||||
"KITESURFING",
|
||||
"",
|
||||
" ",
|
||||
null,
|
||||
7,
|
||||
];
|
||||
const output = {
|
||||
sleepStages: stageLabels.map((raw) => ({
|
||||
raw,
|
||||
stage: mapGoogleHealthSleepStage(raw),
|
||||
})),
|
||||
sportTypes: sportLabels.map((raw) => ({
|
||||
raw,
|
||||
sportType: mapGoogleHealthSportType(raw),
|
||||
})),
|
||||
};
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-labels.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("builds request values with the pure client helpers", async () => {
|
||||
const instants = [
|
||||
new Date("2026-06-01T21:59:59.000Z"),
|
||||
new Date("2026-06-01T22:00:00.000Z"),
|
||||
new Date("2026-03-29T00:30:00.000Z"),
|
||||
new Date("2026-12-31T23:30:15.500Z"),
|
||||
];
|
||||
const listTypes: GoogleHealthDataType[] = [
|
||||
GOOGLE_HEALTH_DATA_TYPES.weight,
|
||||
GOOGLE_HEALTH_DATA_TYPES.heartRateVariability,
|
||||
GOOGLE_HEALTH_DATA_TYPES.sleep,
|
||||
GOOGLE_HEALTH_DATA_TYPES.exercise,
|
||||
GOOGLE_HEALTH_DATA_TYPES.steps,
|
||||
];
|
||||
const errorBodies: unknown[] = [
|
||||
{
|
||||
error: {
|
||||
code: 400,
|
||||
status: "INVALID_ARGUMENT",
|
||||
message:
|
||||
"Invalid filter for https://health.googleapis.com/v4/users/me/dataTypes/weight/dataPoints?filter=weight.date",
|
||||
details: [
|
||||
{
|
||||
"@type": "type.googleapis.com/google.rpc.BadRequest",
|
||||
fieldViolations: [
|
||||
{ field: "filter", description: "Unknown field weight.date" },
|
||||
{ field: "page_size" },
|
||||
{ description: "Authorization: Bearer abc.def.ghi rejected" },
|
||||
{ field: "fourth", description: "dropped past the third" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ error: { status: "RESOURCE_EXHAUSTED" } },
|
||||
{ error: { message: "x".repeat(260) } },
|
||||
{ error: { details: [null, { fieldViolations: [{}, 5] }] } },
|
||||
{ error: "invalid_grant" },
|
||||
{ error: {} },
|
||||
null,
|
||||
"plain text",
|
||||
];
|
||||
const retryCases: Array<{
|
||||
headers: Record<string, string>;
|
||||
body: unknown;
|
||||
}> = [
|
||||
{ headers: { "retry-after": "30" }, body: null },
|
||||
{ headers: { "retry-after": "1.5" }, body: null },
|
||||
{
|
||||
headers: { "retry-after": "Wed, 01 Jul 2026 09:00:45 GMT" },
|
||||
body: null,
|
||||
},
|
||||
{ headers: { "retry-after": "-3" }, body: null },
|
||||
{
|
||||
headers: {},
|
||||
body: {
|
||||
error: {
|
||||
details: [
|
||||
{ "@type": "type.googleapis.com/google.rpc.ErrorInfo" },
|
||||
{
|
||||
"@type": "type.googleapis.com/google.rpc.RetryInfo",
|
||||
retryDelay: "12.5s",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {},
|
||||
body: { error: { details: [{ retryDelay: "soon" }] } },
|
||||
},
|
||||
{ headers: {}, body: null },
|
||||
];
|
||||
const output = {
|
||||
resolveGoogleHealthUserId: [
|
||||
{ name: "users/1234567890" },
|
||||
{ name: "users/", id: "id-0002" },
|
||||
{ name: "plain-0003" },
|
||||
{ id: "id-0004" },
|
||||
{},
|
||||
].map((profile) => ({
|
||||
profile,
|
||||
userId: resolveGoogleHealthUserId(profile),
|
||||
})),
|
||||
formatCivilBound: instants.flatMap((instant) =>
|
||||
ZONES.map((tz) => ({
|
||||
instant,
|
||||
tz: tz ?? null,
|
||||
bound: formatCivilBound(instant, tz),
|
||||
})),
|
||||
),
|
||||
civilDateInTz: instants.flatMap((instant) =>
|
||||
ZONES.map((tz) => ({
|
||||
instant,
|
||||
tz: tz ?? null,
|
||||
date: civilDateInTz(instant, tz),
|
||||
})),
|
||||
),
|
||||
incrementalFilter: listTypes.flatMap((dataType) =>
|
||||
(["camel", "snake"] as const).flatMap((style) =>
|
||||
ZONES.map((tz) => ({
|
||||
dataType: dataType.path,
|
||||
style,
|
||||
tz: tz ?? null,
|
||||
filter: outcome(() =>
|
||||
incrementalFilter(dataType, instants[1]!, tz, style),
|
||||
),
|
||||
})),
|
||||
),
|
||||
),
|
||||
incrementalFilterDefaultStyle: incrementalFilter(
|
||||
GOOGLE_HEALTH_DATA_TYPES.restingHeartRate,
|
||||
instants[0]!,
|
||||
),
|
||||
chunkCivilRange: [
|
||||
{
|
||||
start: { year: 2026, month: 1, day: 1 },
|
||||
end: { year: 2026, month: 7, day: 1 },
|
||||
},
|
||||
{
|
||||
start: { year: 2026, month: 2, day: 20 },
|
||||
end: { year: 2026, month: 3, day: 10 },
|
||||
maxDays: 14,
|
||||
},
|
||||
{
|
||||
start: { year: 2026, month: 6, day: 1 },
|
||||
end: { year: 2026, month: 6, day: 1 },
|
||||
},
|
||||
{
|
||||
start: { year: 2026, month: 6, day: 2 },
|
||||
end: { year: 2026, month: 6, day: 1 },
|
||||
},
|
||||
].map(({ start, end, maxDays }) => ({
|
||||
start,
|
||||
end,
|
||||
maxDays: maxDays ?? null,
|
||||
chunks: chunkCivilRange(start, end, maxDays),
|
||||
})),
|
||||
buildDailyRollUpBody: [
|
||||
{
|
||||
chunk: {
|
||||
start: { year: 2026, month: 1, day: 1 },
|
||||
end: { year: 2026, month: 4, day: 1 },
|
||||
},
|
||||
},
|
||||
{
|
||||
chunk: {
|
||||
start: { year: 2026, month: 2, day: 20 },
|
||||
end: { year: 2026, month: 3, day: 1 },
|
||||
},
|
||||
pageToken: "page-token-0002",
|
||||
},
|
||||
{
|
||||
chunk: {
|
||||
start: { year: 2026, month: 12, day: 31 },
|
||||
end: { year: 2027, month: 1, day: 1 },
|
||||
},
|
||||
pageToken: "",
|
||||
},
|
||||
].map(({ chunk, pageToken }) => ({
|
||||
chunk,
|
||||
pageToken: pageToken ?? null,
|
||||
body: buildDailyRollUpBody(chunk, pageToken),
|
||||
})),
|
||||
extractGoogleApiErrorDetail: errorBodies.map((body) => ({
|
||||
body,
|
||||
detail: extractGoogleApiErrorDetail(body) ?? null,
|
||||
})),
|
||||
refusesEdgeFilter: [
|
||||
undefined,
|
||||
"INVALID_ARGUMENT",
|
||||
"INVALID_ARGUMENT: Invalid filter",
|
||||
"INVALID_ARGUMENT: Invalid page token",
|
||||
"INVALID_ARGUMENT: page_size out of range",
|
||||
"INVALID_ARGUMENT: pageSize too large",
|
||||
"UNAUTHENTICATED: access token expired",
|
||||
"FAILED_PRECONDITION: something else",
|
||||
"Bad filter and bad token",
|
||||
].map((detail) => ({
|
||||
detail: detail ?? null,
|
||||
refuses: refusesEdgeFilter(detail),
|
||||
})),
|
||||
googleHealthRetryDelayMs: retryCases.map(({ headers, body }) => ({
|
||||
headers,
|
||||
body,
|
||||
delayMs:
|
||||
googleHealthRetryDelayMs(
|
||||
new Response(null, { status: 429, headers }),
|
||||
body,
|
||||
FIXED_NOW.getTime(),
|
||||
) ?? null,
|
||||
})),
|
||||
};
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-client-helpers.json",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000001",
|
||||
"type": "sgv",
|
||||
"sgv": 112,
|
||||
"date": 1772445600000,
|
||||
"dateString": "2026-03-02T10:00:00.000Z",
|
||||
"direction": "Flat",
|
||||
"noise": 1,
|
||||
"device": "example-cgm"
|
||||
},
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000002",
|
||||
"type": "sgv",
|
||||
"sgv": 187.5,
|
||||
"date": 1772445900000,
|
||||
"dateString": "2026-03-02T10:05:00.000Z",
|
||||
"direction": "SingleUp"
|
||||
},
|
||||
{
|
||||
"sgv": 95,
|
||||
"date": 1772446200000,
|
||||
"dateString": "2026-03-02T10:10:00.000Z"
|
||||
},
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000004",
|
||||
"type": "mbg",
|
||||
"mbg": 120,
|
||||
"date": 1772446500000
|
||||
},
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000005",
|
||||
"type": "cal",
|
||||
"slope": 850,
|
||||
"intercept": 32000,
|
||||
"date": 1772446800000
|
||||
},
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000006",
|
||||
"type": "sgv",
|
||||
"sgv": null,
|
||||
"date": 1772447100000
|
||||
},
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000007",
|
||||
"type": "sgv",
|
||||
"sgv": 140
|
||||
},
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000008",
|
||||
"type": "sgv",
|
||||
"sgv": 140,
|
||||
"date": "1772447700000"
|
||||
},
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000009",
|
||||
"type": "sgv",
|
||||
"sgv": "130",
|
||||
"date": 1772448000000
|
||||
},
|
||||
{
|
||||
"_id": 42,
|
||||
"type": "sgv",
|
||||
"sgv": 78,
|
||||
"date": 1772448300000
|
||||
},
|
||||
null,
|
||||
"not-an-entry",
|
||||
{
|
||||
"_id": "6650a1b2c3d4e5f600000013",
|
||||
"type": "sgv",
|
||||
"sgv": 0,
|
||||
"date": 0
|
||||
},
|
||||
{
|
||||
"_id": "",
|
||||
"type": "",
|
||||
"sgv": 401,
|
||||
"date": 1772448600000
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"baseUrl": "https://ns.example.org",
|
||||
"count": 288,
|
||||
"token": null,
|
||||
"url": "https://ns.example.org/api/v1/entries.json?count=288&type=sgv"
|
||||
},
|
||||
{
|
||||
"baseUrl": "https://ns.example.org/",
|
||||
"count": 288,
|
||||
"token": "reader-0123456789abcdef",
|
||||
"url": "https://ns.example.org/api/v1/entries.json?count=288&type=sgv&token=reader-0123456789abcdef"
|
||||
},
|
||||
{
|
||||
"baseUrl": "https://ns.example.org///",
|
||||
"count": 1,
|
||||
"token": "",
|
||||
"url": "https://ns.example.org/api/v1/entries.json?count=1&type=sgv"
|
||||
},
|
||||
{
|
||||
"baseUrl": "https://example.org/nightscout",
|
||||
"count": 50,
|
||||
"token": "token with spaces&more=1",
|
||||
"url": "https://example.org/nightscout/api/v1/entries.json?count=50&type=sgv&token=token+with+spaces%26more%3D1"
|
||||
},
|
||||
{
|
||||
"baseUrl": "http://ns.example.org:1337",
|
||||
"count": 0,
|
||||
"token": "reader-token",
|
||||
"url": "http://ns.example.org:1337/api/v1/entries.json?count=0&type=sgv&token=reader-token"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"parsed": [
|
||||
{ "id": "6650a1b2c3d4e5f600000001", "sgv": 112, "date": 1772445600000 },
|
||||
{ "id": "6650a1b2c3d4e5f600000002", "sgv": 187.5, "date": 1772445900000 },
|
||||
{ "id": null, "sgv": 95, "date": 1772446200000 },
|
||||
{ "id": null, "sgv": 78, "date": 1772448300000 },
|
||||
{ "id": "6650a1b2c3d4e5f600000013", "sgv": 0, "date": 0 },
|
||||
{ "id": "", "sgv": 401, "date": 1772448600000 }
|
||||
],
|
||||
"mapped": [
|
||||
{
|
||||
"externalId": "ns:6650a1b2c3d4e5f600000001",
|
||||
"measurement": {
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 112,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "2026-03-02T10:00:00.000Z",
|
||||
"externalId": "ns:6650a1b2c3d4e5f600000001"
|
||||
}
|
||||
},
|
||||
{
|
||||
"externalId": "ns:6650a1b2c3d4e5f600000002",
|
||||
"measurement": {
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 187.5,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "2026-03-02T10:05:00.000Z",
|
||||
"externalId": "ns:6650a1b2c3d4e5f600000002"
|
||||
}
|
||||
},
|
||||
{
|
||||
"externalId": "ns:date:1772446200000",
|
||||
"measurement": {
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 95,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "2026-03-02T10:10:00.000Z",
|
||||
"externalId": "ns:date:1772446200000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"externalId": "ns:date:1772448300000",
|
||||
"measurement": {
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 78,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "2026-03-02T10:45:00.000Z",
|
||||
"externalId": "ns:date:1772448300000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"externalId": "ns:6650a1b2c3d4e5f600000013",
|
||||
"measurement": {
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 0,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "1970-01-01T00:00:00.000Z",
|
||||
"externalId": "ns:6650a1b2c3d4e5f600000013"
|
||||
}
|
||||
},
|
||||
{
|
||||
"externalId": "ns:date:1772448600000",
|
||||
"measurement": {
|
||||
"type": "BLOOD_GLUCOSE",
|
||||
"value": 401,
|
||||
"unit": "mg/dL",
|
||||
"measuredAt": "2026-03-02T10:50:00.000Z",
|
||||
"externalId": "ns:date:1772448600000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{ "name": "error object", "parsed": [] },
|
||||
{ "name": "login page", "parsed": [] },
|
||||
{ "name": "null", "parsed": [] },
|
||||
{ "name": "number", "parsed": [] },
|
||||
{ "name": "empty array", "parsed": [] }
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{ "value": "", "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709" },
|
||||
{
|
||||
"value": "example-api-secret",
|
||||
"sha1": "ae2d76b7397b1eed862868b41007d75ab043c31c"
|
||||
},
|
||||
{
|
||||
"value": "secret-with-umlaut-äöü",
|
||||
"sha1": "7f13c4ff16a35003f36132c3ba41217605e390f0"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* What the Nightscout parse and mapping make of a fixed set of payloads,
|
||||
* pinned in `__golden__/map-*.json`.
|
||||
*
|
||||
* `__fixtures__/entries.json` is a synthetic `/api/v1/entries.json` answer in
|
||||
* the documented wire shape, mixing what an instance really returns: typed
|
||||
* and untyped SGV rows, meter-BG and calibration rows, a missing value or
|
||||
* timestamp, string-typed numbers, a non-string `_id`, non-object rows and
|
||||
* zero values. The URL builder, the SHA1 helper and the non-array payloads
|
||||
* run from small tables in this file.
|
||||
*
|
||||
* The golden was written before the mappers moved out of `client.ts`, so a
|
||||
* green run is the proof that the move changed nothing.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { format } from "prettier";
|
||||
|
||||
import {
|
||||
buildEntriesUrl,
|
||||
externalIdFor,
|
||||
mapSgvEntryToMeasurement,
|
||||
parseSgvEntries,
|
||||
sha1Hex,
|
||||
} from "../client";
|
||||
|
||||
import entriesFixture from "./__fixtures__/entries.json";
|
||||
|
||||
/** The golden is written in the repository's JSON format, so the format check passes on it unchanged. */
|
||||
async function golden(value: unknown): Promise<string> {
|
||||
return format(JSON.stringify(value), { parser: "json" });
|
||||
}
|
||||
|
||||
describe("Nightscout mappers against the golden fixtures", () => {
|
||||
it("parses SGV entries and maps each to a measurement", async () => {
|
||||
const parsed = parseSgvEntries(entriesFixture);
|
||||
const output = {
|
||||
parsed,
|
||||
mapped: parsed.map((entry) => ({
|
||||
externalId: externalIdFor(entry),
|
||||
measurement: mapSgvEntryToMeasurement(entry),
|
||||
})),
|
||||
};
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-entries.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("answers an empty list for payloads that are not an entry array", async () => {
|
||||
const payloads: Array<[string, unknown]> = [
|
||||
["error object", { status: 401, message: "Unauthorized" }],
|
||||
["login page", "<!doctype html><title>Nightscout</title>"],
|
||||
["null", null],
|
||||
["number", 7],
|
||||
["empty array", []],
|
||||
];
|
||||
const output = payloads.map(([name, payload]) => ({
|
||||
name,
|
||||
parsed: parseSgvEntries(payload),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-non-array-payloads.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("builds the entries URL", async () => {
|
||||
const inputs: Array<[string, number, string | undefined]> = [
|
||||
["https://ns.example.org", 288, undefined],
|
||||
["https://ns.example.org/", 288, "reader-0123456789abcdef"],
|
||||
["https://ns.example.org///", 1, ""],
|
||||
["https://example.org/nightscout", 50, "token with spaces&more=1"],
|
||||
["http://ns.example.org:1337", 0, "reader-token"],
|
||||
];
|
||||
const output = inputs.map(([baseUrl, count, token]) => ({
|
||||
baseUrl,
|
||||
count,
|
||||
token: token ?? null,
|
||||
url: buildEntriesUrl(baseUrl, count, token),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-entries-url.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("hashes the API secret", async () => {
|
||||
const inputs = ["", "example-api-secret", "secret-with-umlaut-äöü"];
|
||||
const output = inputs.map((value) => ({ value, sha1: sha1Hex(value) }));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-sha1.json",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{ "id": "cv-age-0001", "day": "2026-03-02", "vascular_age": 38 },
|
||||
{ "id": "cv-age-0002", "day": "2026-03-03", "vascular_age": null },
|
||||
{ "id": "cv-age-0003", "day": "2026-03-04", "vascular_age": 0 },
|
||||
{ "day": "2026-03-05", "vascular_age": 41.555 },
|
||||
{ "id": "cv-age-0005", "day": "2026-03-06" }
|
||||
]
|
||||
@@ -0,0 +1,65 @@
|
||||
[
|
||||
{
|
||||
"scenario": "luteal then follicular on adjacent days marks the follicular day",
|
||||
"records": [
|
||||
{ "id": "cycle-0001", "day": "2026-03-01", "phase": "luteal" },
|
||||
{ "id": "cycle-0002", "day": "2026-03-02", "phase": "luteal" },
|
||||
{ "id": "cycle-0003", "day": "2026-03-03", "phase": "follicular" },
|
||||
{ "id": "cycle-0004", "day": "2026-03-04", "phase": "follicular" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "a gap between luteal and follicular marks nothing",
|
||||
"records": [
|
||||
{ "id": "cycle-0011", "day": "2026-03-10", "phase": "luteal" },
|
||||
{ "id": "cycle-0012", "day": "2026-03-12", "phase": "follicular" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "a literal menstrual phase marks its own day",
|
||||
"records": [
|
||||
{ "id": "cycle-0021", "day": "2026-04-01", "phase": "menstrual" },
|
||||
{ "id": "cycle-0022", "day": "2026-04-02", "phase": "Menstrual" },
|
||||
{ "id": "cycle-0023", "day": "2026-04-03", "phase": "follicular" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "page order is not chronological",
|
||||
"records": [
|
||||
{ "id": "cycle-0033", "day": "2026-05-03", "phase": "follicular" },
|
||||
{ "id": "cycle-0031", "day": "2026-05-01", "phase": "luteal" },
|
||||
{ "id": "cycle-0032", "day": "2026-05-02", "phase": "LUTEAL" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "a null phase does not break the run, an unknown phase does",
|
||||
"records": [
|
||||
{ "id": "cycle-0041", "day": "2026-06-01", "phase": "luteal" },
|
||||
{ "id": "cycle-0042", "day": "2026-06-02", "phase": null },
|
||||
{ "id": "cycle-0043", "day": "2026-06-03", "phase": "follicular" },
|
||||
{ "id": "cycle-0044", "day": "2026-06-10", "phase": "luteal" },
|
||||
{ "id": "cycle-0045", "day": "2026-06-11", "phase": "ovulatory" },
|
||||
{ "id": "cycle-0046", "day": "2026-06-12", "phase": "follicular" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "records without a day are dropped and duplicates collapse",
|
||||
"records": [
|
||||
{ "id": "cycle-0051", "day": "", "phase": "menstrual" },
|
||||
{ "id": "cycle-0052", "day": "2026-07-01", "phase": "menstrual" },
|
||||
{ "id": "cycle-0053", "day": "2026-07-01", "phase": "menstrual" },
|
||||
{ "day": "2026-07-02" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "month boundary counts as adjacent",
|
||||
"records": [
|
||||
{ "id": "cycle-0061", "day": "2026-08-31", "phase": "luteal" },
|
||||
{ "id": "cycle-0062", "day": "2026-09-01", "phase": "follicular" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "an empty series",
|
||||
"records": []
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,48 @@
|
||||
[
|
||||
{
|
||||
"id": "activity-0001",
|
||||
"day": "2026-03-02",
|
||||
"timestamp": "2026-03-02T04:00:00-05:00",
|
||||
"steps": 8432,
|
||||
"active_calories": 412.7,
|
||||
"total_calories": 2380,
|
||||
"equivalent_walking_distance": 9100
|
||||
},
|
||||
{
|
||||
"id": "activity-0002",
|
||||
"day": "2026-03-03",
|
||||
"timestamp": "2026-03-03T04:00:00-05:00",
|
||||
"steps": 0,
|
||||
"active_calories": 0
|
||||
},
|
||||
{
|
||||
"id": "activity-0003",
|
||||
"day": "2026-03-04",
|
||||
"steps": null,
|
||||
"active_calories": 250
|
||||
},
|
||||
{
|
||||
"id": "activity-0004",
|
||||
"day": "2026-03-05",
|
||||
"steps": -5,
|
||||
"active_calories": -1
|
||||
},
|
||||
{
|
||||
"id": "activity-0005",
|
||||
"day": "2026-03-06",
|
||||
"steps": 1234.6,
|
||||
"active_calories": 98.765
|
||||
},
|
||||
{
|
||||
"id": "activity-0006",
|
||||
"day": "2026-03-07",
|
||||
"equivalent_walking_distance": 4200
|
||||
},
|
||||
{
|
||||
"id": "activity-0007",
|
||||
"day": "2026-03-08",
|
||||
"timestamp": "not-a-date",
|
||||
"steps": 5000,
|
||||
"active_calories": 300
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "daily-sleep-0001",
|
||||
"day": "2026-03-02",
|
||||
"timestamp": "2026-03-02T00:00:00+00:00",
|
||||
"score": 84
|
||||
},
|
||||
{ "id": "daily-sleep-0002", "day": "2026-03-03", "score": null },
|
||||
{ "id": "daily-sleep-0003", "day": "2026-03-04", "score": 0 },
|
||||
{
|
||||
"id": "daily-sleep-0004",
|
||||
"day": "2026-03-05",
|
||||
"timestamp": "not-a-date",
|
||||
"score": 77
|
||||
},
|
||||
{ "id": "daily-sleep-0005", "day": "2026-03-06", "score": 90.555 },
|
||||
{ "id": "daily-sleep-0006", "day": "2026-03-07" }
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
[
|
||||
{
|
||||
"id": "spo2-0001",
|
||||
"day": "2026-03-02",
|
||||
"spo2_percentage": { "average": 96.4 }
|
||||
},
|
||||
{
|
||||
"id": "spo2-0002",
|
||||
"day": "2026-03-03",
|
||||
"spo2_percentage": { "average": null }
|
||||
},
|
||||
{ "id": "spo2-0003", "day": "2026-03-04", "spo2_percentage": null },
|
||||
{ "id": "spo2-0004", "day": "2026-03-05" },
|
||||
{
|
||||
"id": "spo2-0005",
|
||||
"day": "2026-03-06",
|
||||
"spo2_percentage": { "average": 0 }
|
||||
},
|
||||
{
|
||||
"id": "spo2-0006",
|
||||
"day": "2026-03-07",
|
||||
"spo2_percentage": { "average": 97.125 }
|
||||
},
|
||||
{
|
||||
"id": "spo2-0007",
|
||||
"day": "2026-03-08",
|
||||
"spo2_percentage": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{ "bpm": 62, "source": "awake", "timestamp": "2026-03-02T08:00:00Z" },
|
||||
{ "bpm": 19, "source": "rest", "timestamp": "2026-03-02T08:05:00Z" },
|
||||
{ "bpm": 20, "source": "sleep", "timestamp": "2026-03-02T03:00:00Z" },
|
||||
{ "bpm": 250, "source": "workout", "timestamp": "2026-03-02T17:30:00Z" },
|
||||
{ "bpm": 251, "source": "workout", "timestamp": "2026-03-02T17:31:00Z" },
|
||||
{ "bpm": 71.6, "source": "live", "timestamp": "2026-03-02T09:00:00.000Z" },
|
||||
{ "bpm": null, "source": "awake", "timestamp": "2026-03-02T09:05:00Z" },
|
||||
{ "bpm": 66, "source": "awake" },
|
||||
{ "bpm": 66, "source": "awake", "timestamp": "not-a-date" },
|
||||
{
|
||||
"bpm": 74,
|
||||
"source": "session",
|
||||
"timestamp": "2026-03-02T10:15:30+02:00"
|
||||
},
|
||||
{ "bpm": 55, "source": null, "timestamp": null }
|
||||
]
|
||||
@@ -0,0 +1,71 @@
|
||||
[
|
||||
{
|
||||
"id": "readiness-0001",
|
||||
"day": "2026-03-02",
|
||||
"timestamp": "2026-03-02T00:00:00+00:00",
|
||||
"score": 82,
|
||||
"temperature_deviation": -0.21,
|
||||
"temperature_trend_deviation": 0.05,
|
||||
"contributors": {
|
||||
"activity_balance": 78,
|
||||
"body_temperature": 95,
|
||||
"hrv_balance": 70,
|
||||
"previous_day_activity": 84,
|
||||
"previous_night": 80,
|
||||
"recovery_index": 90,
|
||||
"resting_heart_rate": 88,
|
||||
"sleep_balance": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "readiness-0002",
|
||||
"day": "2026-03-03",
|
||||
"score": 75,
|
||||
"temperature_deviation": 0.35
|
||||
},
|
||||
{
|
||||
"id": "readiness-0003",
|
||||
"day": "2026-03-04",
|
||||
"score": null,
|
||||
"temperature_deviation": 0.1,
|
||||
"contributors": null
|
||||
},
|
||||
{
|
||||
"id": "readiness-0004",
|
||||
"day": "2026-03-05",
|
||||
"timestamp": "2026-03-05T00:00:00+00:00",
|
||||
"score": 0,
|
||||
"temperature_deviation": 0
|
||||
},
|
||||
{
|
||||
"id": "readiness-0005",
|
||||
"day": "2026-03-06",
|
||||
"timestamp": "2026-03-06T00:00:00+00:00",
|
||||
"score": null,
|
||||
"temperature_deviation": null
|
||||
},
|
||||
{
|
||||
"id": "readiness-0006",
|
||||
"day": "2026-03-07",
|
||||
"timestamp": "not-a-date",
|
||||
"score": 70,
|
||||
"temperature_deviation": 0.2
|
||||
},
|
||||
{
|
||||
"id": "readiness-0007",
|
||||
"day": "2026-03-08",
|
||||
"timestamp": "2026-03-08T06:15:00+02:00",
|
||||
"score": 88.456,
|
||||
"temperature_deviation": -0.125
|
||||
},
|
||||
{
|
||||
"id": "readiness-0008",
|
||||
"day": "2026-03-29",
|
||||
"score": 64
|
||||
},
|
||||
{
|
||||
"id": "readiness-0009",
|
||||
"day": "2026-11-01",
|
||||
"temperature_deviation": 0.48
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{ "id": "resilience-0001", "day": "2026-03-02", "level": "limited" },
|
||||
{ "id": "resilience-0002", "day": "2026-03-03", "level": "adequate" },
|
||||
{ "id": "resilience-0003", "day": "2026-03-04", "level": "Solid" },
|
||||
{ "id": "resilience-0004", "day": "2026-03-05", "level": "STRONG" },
|
||||
{ "id": "resilience-0005", "day": "2026-03-06", "level": "exceptional" },
|
||||
{ "id": "resilience-0006", "day": "2026-03-07", "level": "unknown_band" },
|
||||
{ "id": "resilience-0007", "day": "2026-03-08", "level": null },
|
||||
{ "id": "resilience-0008", "day": "2026-03-09", "level": "" },
|
||||
{ "day": "2026-03-10", "level": "solid" }
|
||||
]
|
||||
@@ -0,0 +1,135 @@
|
||||
[
|
||||
{
|
||||
"id": "sleep-0001",
|
||||
"day": "2026-03-02",
|
||||
"type": "long_sleep",
|
||||
"bedtime_start": "2026-03-01T22:30:00+01:00",
|
||||
"bedtime_end": "2026-03-02T06:40:00+01:00",
|
||||
"total_sleep_duration": 26400,
|
||||
"time_in_bed": 29400,
|
||||
"efficiency": 91,
|
||||
"light_sleep_duration": 14400,
|
||||
"deep_sleep_duration": 5400,
|
||||
"rem_sleep_duration": 6600,
|
||||
"awake_time": 3000,
|
||||
"average_heart_rate": 58.25,
|
||||
"lowest_heart_rate": 52.4,
|
||||
"average_hrv": 48.5,
|
||||
"average_breath": 14.25,
|
||||
"sleep_phase_5_min": "44222211113333222241"
|
||||
},
|
||||
{
|
||||
"id": "sleep-0002",
|
||||
"day": "2026-03-03",
|
||||
"type": "sleep",
|
||||
"bedtime_start": "2026-03-02T23:00:00+01:00",
|
||||
"bedtime_end": "2026-03-03T07:00:00+01:00",
|
||||
"efficiency": 88,
|
||||
"light_sleep_duration": 14400,
|
||||
"deep_sleep_duration": 5400,
|
||||
"rem_sleep_duration": 6300,
|
||||
"awake_time": 1800,
|
||||
"lowest_heart_rate": 0,
|
||||
"average_hrv": 0,
|
||||
"average_breath": null,
|
||||
"sleep_phase_5_min": null
|
||||
},
|
||||
{
|
||||
"id": "sleep-0003",
|
||||
"day": "2026-03-02",
|
||||
"type": "late_nap",
|
||||
"bedtime_start": "2026-03-02T14:10:00+01:00",
|
||||
"bedtime_end": "2026-03-02T14:40:00+01:00",
|
||||
"efficiency": 83.333,
|
||||
"light_sleep_duration": 1500,
|
||||
"deep_sleep_duration": 0,
|
||||
"rem_sleep_duration": null,
|
||||
"awake_time": 0,
|
||||
"lowest_heart_rate": 61.6,
|
||||
"average_hrv": 35.125,
|
||||
"average_breath": 15
|
||||
},
|
||||
{
|
||||
"id": "sleep-0004",
|
||||
"day": "2026-03-04",
|
||||
"type": "rest",
|
||||
"bedtime_start": "2026-03-04T15:00:00+01:00",
|
||||
"bedtime_end": "2026-03-04T15:45:00+01:00",
|
||||
"efficiency": 70,
|
||||
"light_sleep_duration": 2100,
|
||||
"average_hrv": 40
|
||||
},
|
||||
{
|
||||
"id": "sleep-0005",
|
||||
"day": "2026-03-05",
|
||||
"type": "deleted",
|
||||
"bedtime_start": "2026-03-04T23:30:00+01:00",
|
||||
"bedtime_end": "2026-03-05T06:30:00+01:00",
|
||||
"efficiency": 90,
|
||||
"sleep_phase_5_min": "2222"
|
||||
},
|
||||
{
|
||||
"id": "sleep-0006",
|
||||
"day": "2026-03-06",
|
||||
"light_sleep_duration": 12000,
|
||||
"deep_sleep_duration": 4800,
|
||||
"rem_sleep_duration": 5700,
|
||||
"awake_time": 900,
|
||||
"average_breath": 13.8,
|
||||
"sleep_phase_5_min": "22113322"
|
||||
},
|
||||
{
|
||||
"id": "sleep-0007",
|
||||
"day": "2026-03-07",
|
||||
"type": "long_sleep",
|
||||
"bedtime_start": "2026-03-06T23:15:00-05:00",
|
||||
"bedtime_end": "2026-03-07T00:00:00-05:00",
|
||||
"efficiency": 85,
|
||||
"sleep_phase_5_min": "0011522"
|
||||
},
|
||||
{
|
||||
"id": "sleep-0008",
|
||||
"day": "2026-03-08",
|
||||
"type": "long_sleep",
|
||||
"bedtime_start": "2026-03-07T23:00:00+00:00",
|
||||
"bedtime_end": "2026-03-08T07:00:00+00:00",
|
||||
"light_sleep_duration": 13200,
|
||||
"deep_sleep_duration": 6000,
|
||||
"rem_sleep_duration": 6000,
|
||||
"awake_time": 1200,
|
||||
"sleep_phase_5_min": "0000"
|
||||
},
|
||||
{
|
||||
"id": "sleep-0009",
|
||||
"day": "2026-03-09",
|
||||
"type": "long_sleep",
|
||||
"bedtime_start": "2026-03-08T23:00:00+00:00",
|
||||
"bedtime_end": "garbage",
|
||||
"efficiency": 90,
|
||||
"sleep_phase_5_min": "2222"
|
||||
},
|
||||
{
|
||||
"id": "sleep-0010",
|
||||
"day": "2026-03-10",
|
||||
"type": "sleep",
|
||||
"bedtime_start": "not-a-date",
|
||||
"bedtime_end": "2026-03-10T06:00:00+00:00",
|
||||
"light_sleep_duration": -60,
|
||||
"deep_sleep_duration": 3601,
|
||||
"rem_sleep_duration": 4000,
|
||||
"awake_time": null,
|
||||
"average_hrv": -3,
|
||||
"lowest_heart_rate": 49.5,
|
||||
"sleep_phase_5_min": "1122"
|
||||
},
|
||||
{
|
||||
"id": "sleep-0011",
|
||||
"day": "2026-03-11",
|
||||
"type": "sleep",
|
||||
"bedtime_start": "2026-03-10T22:00:00+00:00",
|
||||
"bedtime_end": "2026-03-11T06:00:00+00:00",
|
||||
"efficiency": 0,
|
||||
"light_sleep_duration": 0,
|
||||
"sleep_phase_5_min": ""
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "vo2-0001",
|
||||
"day": "2026-03-02",
|
||||
"timestamp": "2026-03-02T00:00:00+00:00",
|
||||
"vo2_max": 42.3
|
||||
},
|
||||
{ "id": "vo2-0002", "day": "2026-03-03", "vo2_max": null },
|
||||
{ "id": "vo2-0003", "day": "2026-03-04", "vo2_max": 0 },
|
||||
{ "id": "vo2-0004", "day": "2026-03-05", "vo2_max": -1 },
|
||||
{ "id": "vo2-0005", "day": "2026-03-06", "vo2_max": 38.756 },
|
||||
{
|
||||
"id": "vo2-0006",
|
||||
"day": "2026-03-07",
|
||||
"timestamp": "not-a-date",
|
||||
"vo2_max": 40
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,77 @@
|
||||
[
|
||||
{
|
||||
"id": "workout-0001",
|
||||
"day": "2026-03-02",
|
||||
"activity": "running",
|
||||
"calories": 412,
|
||||
"distance": 7520.5,
|
||||
"start_datetime": "2026-03-02T07:00:00+01:00",
|
||||
"end_datetime": "2026-03-02T07:45:30+01:00",
|
||||
"intensity": "moderate",
|
||||
"source": "confirmed",
|
||||
"label": "Morning loop"
|
||||
},
|
||||
{
|
||||
"id": "workout-0002",
|
||||
"day": "2026-03-03",
|
||||
"activity": "underwater_basket_weaving",
|
||||
"calories": 120.6,
|
||||
"distance": null,
|
||||
"start_datetime": "2026-03-03T18:00:00-05:00",
|
||||
"end_datetime": "2026-03-03T18:30:00-05:00",
|
||||
"intensity": "easy",
|
||||
"source": "manual"
|
||||
},
|
||||
{
|
||||
"id": "workout-0003",
|
||||
"activity": null,
|
||||
"calories": null,
|
||||
"distance": 0,
|
||||
"start_datetime": "2026-03-04T12:00:00Z",
|
||||
"end_datetime": "2026-03-04T12:20:00Z"
|
||||
},
|
||||
{
|
||||
"id": "workout-0004",
|
||||
"activity": "cycling",
|
||||
"start_datetime": "2026-03-05T10:00:00Z",
|
||||
"end_datetime": "2026-03-05T09:59:59Z"
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"activity": "walking",
|
||||
"start_datetime": "2026-03-06T10:00:00Z",
|
||||
"end_datetime": "2026-03-06T10:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": "workout-0006",
|
||||
"activity": "walking",
|
||||
"start_datetime": "not-a-date",
|
||||
"end_datetime": "2026-03-07T10:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": "workout-0007",
|
||||
"activity": "Yoga",
|
||||
"calories": -5,
|
||||
"distance": -10,
|
||||
"start_datetime": "2026-03-08T06:30:00Z",
|
||||
"end_datetime": "2026-03-08T06:30:00Z",
|
||||
"intensity": "hard",
|
||||
"source": "autodetected"
|
||||
},
|
||||
{
|
||||
"id": "workout-0008",
|
||||
"activity": "strength_training",
|
||||
"calories": 0,
|
||||
"start_datetime": "2026-03-09T17:00:00.250Z",
|
||||
"end_datetime": "2026-03-09T17:50:00.749Z",
|
||||
"source": "workout_heart_rate"
|
||||
},
|
||||
{
|
||||
"id": "workout-0009",
|
||||
"activity": "hiking",
|
||||
"calories": 980.4,
|
||||
"distance": 15200,
|
||||
"start_datetime": "2026-03-10T09:00:00+01:00",
|
||||
"end_datetime": "not-a-date"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,125 @@
|
||||
[
|
||||
{
|
||||
"id": "cv-age-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "VASCULAR_AGE",
|
||||
"value": 38,
|
||||
"unit": "years",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "vascular_age"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cv-age-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "VASCULAR_AGE",
|
||||
"value": 38,
|
||||
"unit": "years",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "vascular_age"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cv-age-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "VASCULAR_AGE",
|
||||
"value": 38,
|
||||
"unit": "years",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "vascular_age"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "cv-age-0002", "day": "2026-03-03", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "cv-age-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "cv-age-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "cv-age-0003", "day": "2026-03-04", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "cv-age-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "cv-age-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"day": "2026-03-05",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "VASCULAR_AGE",
|
||||
"value": 41.56,
|
||||
"unit": "years",
|
||||
"measuredAt": "2026-03-05T12:00:00.000Z",
|
||||
"fieldTag": "vascular_age"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"day": "2026-03-05",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "VASCULAR_AGE",
|
||||
"value": 41.56,
|
||||
"unit": "years",
|
||||
"measuredAt": "2026-03-05T11:00:00.000Z",
|
||||
"fieldTag": "vascular_age"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"day": "2026-03-05",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "VASCULAR_AGE",
|
||||
"value": 41.56,
|
||||
"unit": "years",
|
||||
"measuredAt": "2026-03-05T17:00:00.000Z",
|
||||
"fieldTag": "vascular_age"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "cv-age-0005", "day": "2026-03-06", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "cv-age-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "cv-age-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
[
|
||||
{
|
||||
"scenario": "luteal then follicular on adjacent days marks the follicular day",
|
||||
"periodDays": ["2026-03-03"]
|
||||
},
|
||||
{
|
||||
"scenario": "a gap between luteal and follicular marks nothing",
|
||||
"periodDays": []
|
||||
},
|
||||
{
|
||||
"scenario": "a literal menstrual phase marks its own day",
|
||||
"periodDays": ["2026-04-01", "2026-04-02"]
|
||||
},
|
||||
{
|
||||
"scenario": "page order is not chronological",
|
||||
"periodDays": ["2026-05-03"]
|
||||
},
|
||||
{
|
||||
"scenario": "a null phase does not break the run, an unknown phase does",
|
||||
"periodDays": []
|
||||
},
|
||||
{
|
||||
"scenario": "records without a day are dropped and duplicates collapse",
|
||||
"periodDays": ["2026-07-01"]
|
||||
},
|
||||
{
|
||||
"scenario": "month boundary counts as adjacent",
|
||||
"periodDays": ["2026-09-01"]
|
||||
},
|
||||
{ "scenario": "an empty series", "periodDays": [] }
|
||||
]
|
||||
@@ -0,0 +1,272 @@
|
||||
[
|
||||
{
|
||||
"id": "activity-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 8432,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-02T09:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 412.7,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-02T09:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 8432,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-02T09:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 412.7,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-02T09:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 8432,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-02T09:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 412.7,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-02T09:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-03T09:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 0,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-03T09:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-03T09:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 0,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-03T09:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-03T09:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 0,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-03T09:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 250,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 250,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 250,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "activity-0004", "day": "2026-03-05", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "activity-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "activity-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "activity-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 1235,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 98.77,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 1235,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 98.77,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activity-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 1235,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "steps"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 98.77,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "active_energy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "activity-0006", "day": "2026-03-07", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "activity-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "activity-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "activity-0007", "day": "2026-03-08", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "activity-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "activity-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,167 @@
|
||||
[
|
||||
{
|
||||
"id": "daily-sleep-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 84,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 84,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 84,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "daily-sleep-0002", "day": "2026-03-03", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "daily-sleep-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "daily-sleep-0004", "day": "2026-03-05", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "daily-sleep-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 90.56,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 90.56,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_SCORE",
|
||||
"value": 90.56,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "daily-sleep-0006", "day": "2026-03-07", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "daily-sleep-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "daily-sleep-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,151 @@
|
||||
[
|
||||
{
|
||||
"id": "spo2-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 96.4,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spo2-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 96.4,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spo2-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 96.4,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "spo2-0002", "day": "2026-03-03", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "spo2-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "spo2-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "spo2-0003", "day": "2026-03-04", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "spo2-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "spo2-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "spo2-0004", "day": "2026-03-05", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "spo2-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "spo2-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "spo2-0005", "day": "2026-03-06", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "spo2-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "spo2-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "spo2-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 97.13,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-07T12:00:00.000Z",
|
||||
"fieldTag": "spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spo2-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 97.13,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-07T11:00:00.000Z",
|
||||
"fieldTag": "spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spo2-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 97.13,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-07T17:00:00.000Z",
|
||||
"fieldTag": "spo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "spo2-0007", "day": "2026-03-08", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "spo2-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "spo2-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,68 @@
|
||||
[
|
||||
{
|
||||
"timestamp": "2026-03-02T08:00:00Z",
|
||||
"bpm": 62,
|
||||
"row": {
|
||||
"type": "PULSE",
|
||||
"value": 62,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T08:00:00.000Z",
|
||||
"fieldTag": "hr",
|
||||
"externalId": "heartrate:2026-03-02T08:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{ "timestamp": "2026-03-02T08:05:00Z", "bpm": 19, "row": null },
|
||||
{
|
||||
"timestamp": "2026-03-02T03:00:00Z",
|
||||
"bpm": 20,
|
||||
"row": {
|
||||
"type": "PULSE",
|
||||
"value": 20,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T03:00:00.000Z",
|
||||
"fieldTag": "hr",
|
||||
"externalId": "heartrate:2026-03-02T03:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-03-02T17:30:00Z",
|
||||
"bpm": 250,
|
||||
"row": {
|
||||
"type": "PULSE",
|
||||
"value": 250,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T17:30:00.000Z",
|
||||
"fieldTag": "hr",
|
||||
"externalId": "heartrate:2026-03-02T17:30:00.000Z"
|
||||
}
|
||||
},
|
||||
{ "timestamp": "2026-03-02T17:31:00Z", "bpm": 251, "row": null },
|
||||
{
|
||||
"timestamp": "2026-03-02T09:00:00.000Z",
|
||||
"bpm": 71.6,
|
||||
"row": {
|
||||
"type": "PULSE",
|
||||
"value": 72,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T09:00:00.000Z",
|
||||
"fieldTag": "hr",
|
||||
"externalId": "heartrate:2026-03-02T09:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{ "timestamp": "2026-03-02T09:05:00Z", "bpm": null, "row": null },
|
||||
{ "timestamp": null, "bpm": 66, "row": null },
|
||||
{ "timestamp": "not-a-date", "bpm": 66, "row": null },
|
||||
{
|
||||
"timestamp": "2026-03-02T10:15:30+02:00",
|
||||
"bpm": 74,
|
||||
"row": {
|
||||
"type": "PULSE",
|
||||
"value": 74,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T08:15:30.000Z",
|
||||
"fieldTag": "hr",
|
||||
"externalId": "heartrate:2026-03-02T08:15:30.000Z"
|
||||
}
|
||||
},
|
||||
{ "timestamp": null, "bpm": 55, "row": null }
|
||||
]
|
||||
@@ -0,0 +1,406 @@
|
||||
[
|
||||
{
|
||||
"id": "readiness-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 82,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": -0.21,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 82,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": -0.21,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 82,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": -0.21,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 75,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.35,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 75,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.35,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 75,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.35,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.1,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.1,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.1,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-05T00:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-05T00:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-05T00:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-05T00:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-05T00:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-05T00:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "readiness-0005", "day": "2026-03-06", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "readiness-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "readiness-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "readiness-0006", "day": "2026-03-07", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "readiness-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "readiness-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "readiness-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 88.46,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-08T04:15:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": -0.13,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-08T04:15:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 88.46,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-08T04:15:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": -0.13,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-08T04:15:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 88.46,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-08T04:15:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": -0.13,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-08T04:15:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0008",
|
||||
"day": "2026-03-29",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 64,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-29T12:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0008",
|
||||
"day": "2026-03-29",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 64,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-29T10:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0008",
|
||||
"day": "2026-03-29",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 64,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-29T16:00:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0009",
|
||||
"day": "2026-11-01",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.48,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-11-01T12:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0009",
|
||||
"day": "2026-11-01",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.48,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-11-01T11:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readiness-0009",
|
||||
"day": "2026-11-01",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "BODY_TEMPERATURE_DEVIATION",
|
||||
"value": 0.48,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-11-01T17:00:00.000Z",
|
||||
"fieldTag": "temp_deviation"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,293 @@
|
||||
[
|
||||
{
|
||||
"id": "resilience-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 1,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 1,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 1,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 2,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 2,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 2,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 3,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 3,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 3,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 4,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-05T12:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 4,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-05T11:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 4,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-05T17:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 5,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 5,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resilience-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 5,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "resilience-0006", "day": "2026-03-07", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "resilience-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "resilience-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "resilience-0007", "day": "2026-03-08", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "resilience-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "resilience-0007",
|
||||
"day": "2026-03-08",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "resilience-0008", "day": "2026-03-09", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "resilience-0008",
|
||||
"day": "2026-03-09",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "resilience-0008",
|
||||
"day": "2026-03-09",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"day": "2026-03-10",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 3,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-10T12:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"day": "2026-03-10",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 3,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-10T11:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"day": "2026-03-10",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RESILIENCE",
|
||||
"value": 3,
|
||||
"unit": "level",
|
||||
"measuredAt": "2026-03-10T16:00:00.000Z",
|
||||
"fieldTag": "resilience"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{ "type": "long_sleep", "retired": false },
|
||||
{ "type": "sleep", "retired": false },
|
||||
{ "type": "late_nap", "retired": false },
|
||||
{ "type": "rest", "retired": true },
|
||||
{ "type": "deleted", "retired": true },
|
||||
{ "type": "REST", "retired": false },
|
||||
{ "type": "", "retired": false },
|
||||
{ "type": null, "retired": false },
|
||||
{ "type": null, "retired": false }
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,138 @@
|
||||
[
|
||||
{
|
||||
"id": "vo2-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 42.3,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "vo2_max"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vo2-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 42.3,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "vo2_max"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vo2-0001",
|
||||
"day": "2026-03-02",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 42.3,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-03-02T00:00:00.000Z",
|
||||
"fieldTag": "vo2_max"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "vo2-0002", "day": "2026-03-03", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "vo2-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "vo2-0002",
|
||||
"day": "2026-03-03",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "vo2-0003", "day": "2026-03-04", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "vo2-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "vo2-0003",
|
||||
"day": "2026-03-04",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{ "id": "vo2-0004", "day": "2026-03-05", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "vo2-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "vo2-0004",
|
||||
"day": "2026-03-05",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "vo2-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 38.76,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "vo2_max"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vo2-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 38.76,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "vo2_max"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vo2-0005",
|
||||
"day": "2026-03-06",
|
||||
"zone": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "VO2_MAX",
|
||||
"value": 38.76,
|
||||
"unit": "mL/(kg·min)",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "vo2_max"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "vo2-0006", "day": "2026-03-07", "zone": null, "rows": [] },
|
||||
{
|
||||
"id": "vo2-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "Europe/Berlin",
|
||||
"rows": []
|
||||
},
|
||||
{
|
||||
"id": "vo2-0006",
|
||||
"day": "2026-03-07",
|
||||
"zone": "America/New_York",
|
||||
"rows": []
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"id": "workout-0001",
|
||||
"row": {
|
||||
"externalId": "workout-0001",
|
||||
"sportType": "running",
|
||||
"startedAt": "2026-03-02T06:00:00.000Z",
|
||||
"endedAt": "2026-03-02T06:45:30.000Z",
|
||||
"durationSec": 2730,
|
||||
"totalEnergyKcal": 412,
|
||||
"totalDistanceM": 7520.5,
|
||||
"metadata": {
|
||||
"ouraActivity": "running",
|
||||
"ouraIntensity": "moderate",
|
||||
"ouraSource": "confirmed"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "workout-0002",
|
||||
"row": {
|
||||
"externalId": "workout-0002",
|
||||
"sportType": "other",
|
||||
"startedAt": "2026-03-03T23:00:00.000Z",
|
||||
"endedAt": "2026-03-03T23:30:00.000Z",
|
||||
"durationSec": 1800,
|
||||
"totalEnergyKcal": 121,
|
||||
"totalDistanceM": null,
|
||||
"metadata": {
|
||||
"ouraActivity": "underwater_basket_weaving",
|
||||
"ouraIntensity": "easy",
|
||||
"ouraSource": "manual"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "workout-0003",
|
||||
"row": {
|
||||
"externalId": "workout-0003",
|
||||
"sportType": "other",
|
||||
"startedAt": "2026-03-04T12:00:00.000Z",
|
||||
"endedAt": "2026-03-04T12:20:00.000Z",
|
||||
"durationSec": 1200,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": 0,
|
||||
"metadata": {}
|
||||
}
|
||||
},
|
||||
{ "id": "workout-0004", "row": null },
|
||||
{ "id": "", "row": null },
|
||||
{ "id": "workout-0006", "row": null },
|
||||
{
|
||||
"id": "workout-0007",
|
||||
"row": {
|
||||
"externalId": "workout-0007",
|
||||
"sportType": "yoga",
|
||||
"startedAt": "2026-03-08T06:30:00.000Z",
|
||||
"endedAt": "2026-03-08T06:30:00.000Z",
|
||||
"durationSec": 0,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"metadata": {
|
||||
"ouraActivity": "Yoga",
|
||||
"ouraIntensity": "hard",
|
||||
"ouraSource": "autodetected"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "workout-0008",
|
||||
"row": {
|
||||
"externalId": "workout-0008",
|
||||
"sportType": "strength",
|
||||
"startedAt": "2026-03-09T17:00:00.250Z",
|
||||
"endedAt": "2026-03-09T17:50:00.749Z",
|
||||
"durationSec": 3000,
|
||||
"totalEnergyKcal": 0,
|
||||
"totalDistanceM": null,
|
||||
"metadata": {
|
||||
"ouraActivity": "strength_training",
|
||||
"ouraSource": "workout_heart_rate"
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "id": "workout-0009", "row": null }
|
||||
]
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* What each Oura mapper makes of a fixed set of payloads, pinned in
|
||||
* `__golden__/map-<resource>.json`.
|
||||
*
|
||||
* The fixtures under `__fixtures__/` are synthetic records in the documented
|
||||
* Oura v2 wire shape, chosen to walk every branch the mappers have: a missing
|
||||
* or zero score, a rejected or deleted sleep period, a nap beside the main
|
||||
* sleep, a hypnogram with unknown digits, a record without its instant, an
|
||||
* unparseable timestamp, an out-of-band heart rate, an unknown activity. Every
|
||||
* mapper that takes a zone runs without one and under two explicit zones, so
|
||||
* the date-only anchors are recorded where they land.
|
||||
*
|
||||
* The golden was written before the mappers moved out of `client.ts`, so a
|
||||
* green run is the proof that the move changed nothing.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { format } from "prettier";
|
||||
|
||||
import {
|
||||
derivePeriodDaysFromCyclePhases,
|
||||
isRetiredOuraSleepPeriod,
|
||||
mapCardiovascularAge,
|
||||
mapDailyActivity,
|
||||
mapDailySleep,
|
||||
mapDailySpo2,
|
||||
mapHeartRateSample,
|
||||
mapReadiness,
|
||||
mapResilience,
|
||||
mapSleep,
|
||||
mapVo2Max,
|
||||
type MappedMeasurement,
|
||||
type OuraCardiovascularAge,
|
||||
type OuraCyclePhase,
|
||||
type OuraDailyActivity,
|
||||
type OuraDailySleep,
|
||||
type OuraDailySpo2,
|
||||
type OuraHeartRateSample,
|
||||
type OuraReadiness,
|
||||
type OuraResilience,
|
||||
type OuraSleep,
|
||||
type OuraVo2Max,
|
||||
type OuraWorkout,
|
||||
} from "../client";
|
||||
import { mapOuraWorkout } from "../sync-workouts";
|
||||
|
||||
import cardiovascularAgeFixture from "./__fixtures__/cardiovascular-age.json";
|
||||
import cyclePhasesFixture from "./__fixtures__/cycle-phases.json";
|
||||
import dailyActivityFixture from "./__fixtures__/daily-activity.json";
|
||||
import dailySleepFixture from "./__fixtures__/daily-sleep.json";
|
||||
import dailySpo2Fixture from "./__fixtures__/daily-spo2.json";
|
||||
import heartRateFixture from "./__fixtures__/heartrate.json";
|
||||
import readinessFixture from "./__fixtures__/readiness.json";
|
||||
import resilienceFixture from "./__fixtures__/resilience.json";
|
||||
import sleepFixture from "./__fixtures__/sleep.json";
|
||||
import vo2MaxFixture from "./__fixtures__/vo2-max.json";
|
||||
import workoutsFixture from "./__fixtures__/workouts.json";
|
||||
|
||||
/** The golden is written in the repository's JSON format, so the format check passes on it unchanged. */
|
||||
async function golden(value: unknown): Promise<string> {
|
||||
return format(JSON.stringify(value), { parser: "json" });
|
||||
}
|
||||
|
||||
/** No zone (the mapper's own default), then two zones on either side of UTC. */
|
||||
const ZONES = [undefined, "Europe/Berlin", "America/New_York"] as const;
|
||||
|
||||
/** Runs a zone-aware mapper over every record under every zone. */
|
||||
function acrossZones<T extends { id?: string; day: string }>(
|
||||
records: readonly T[],
|
||||
map: (record: T, tz?: string) => MappedMeasurement[],
|
||||
) {
|
||||
return records.flatMap((record) =>
|
||||
ZONES.map((zone) => ({
|
||||
id: record.id ?? null,
|
||||
day: record.day,
|
||||
zone: zone ?? null,
|
||||
rows: map(record, zone),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
describe("Oura mappers against the golden fixtures", () => {
|
||||
it("maps daily readiness", async () => {
|
||||
const records: OuraReadiness[] = readinessFixture;
|
||||
await expect(
|
||||
await golden(acrossZones(records, mapReadiness)),
|
||||
).toMatchFileSnapshot("./__golden__/map-readiness.json");
|
||||
});
|
||||
|
||||
it("maps sleep periods and flags retired ones", async () => {
|
||||
const records: OuraSleep[] = sleepFixture;
|
||||
const output = acrossZones(records, mapSleep).map((entry) => ({
|
||||
...entry,
|
||||
retired: isRetiredOuraSleepPeriod(
|
||||
records.find((r) => r.id === entry.id) ?? {},
|
||||
),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-sleep.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("classifies every sleep type as retired or kept", async () => {
|
||||
const types = [
|
||||
"long_sleep",
|
||||
"sleep",
|
||||
"late_nap",
|
||||
"rest",
|
||||
"deleted",
|
||||
"REST",
|
||||
"",
|
||||
null,
|
||||
undefined,
|
||||
];
|
||||
const output = types.map((type) => ({
|
||||
type: type ?? null,
|
||||
retired: isRetiredOuraSleepPeriod({ type }),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-sleep-retired.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps the daily sleep score", async () => {
|
||||
const records: OuraDailySleep[] = dailySleepFixture;
|
||||
await expect(
|
||||
await golden(acrossZones(records, mapDailySleep)),
|
||||
).toMatchFileSnapshot("./__golden__/map-daily-sleep.json");
|
||||
});
|
||||
|
||||
it("maps daily SpO2", async () => {
|
||||
const records: OuraDailySpo2[] = dailySpo2Fixture;
|
||||
await expect(
|
||||
await golden(acrossZones(records, mapDailySpo2)),
|
||||
).toMatchFileSnapshot("./__golden__/map-daily-spo2.json");
|
||||
});
|
||||
|
||||
it("maps daily activity", async () => {
|
||||
const records: OuraDailyActivity[] = dailyActivityFixture;
|
||||
await expect(
|
||||
await golden(acrossZones(records, mapDailyActivity)),
|
||||
).toMatchFileSnapshot("./__golden__/map-daily-activity.json");
|
||||
});
|
||||
|
||||
it("maps VO2 max", async () => {
|
||||
const records: OuraVo2Max[] = vo2MaxFixture;
|
||||
await expect(
|
||||
await golden(acrossZones(records, mapVo2Max)),
|
||||
).toMatchFileSnapshot("./__golden__/map-vo2-max.json");
|
||||
});
|
||||
|
||||
it("maps cardiovascular age", async () => {
|
||||
const records: OuraCardiovascularAge[] = cardiovascularAgeFixture;
|
||||
await expect(
|
||||
await golden(acrossZones(records, mapCardiovascularAge)),
|
||||
).toMatchFileSnapshot("./__golden__/map-cardiovascular-age.json");
|
||||
});
|
||||
|
||||
it("maps the resilience level", async () => {
|
||||
const records: OuraResilience[] = resilienceFixture;
|
||||
await expect(
|
||||
await golden(acrossZones(records, mapResilience)),
|
||||
).toMatchFileSnapshot("./__golden__/map-resilience.json");
|
||||
});
|
||||
|
||||
it("maps heart-rate samples", async () => {
|
||||
const samples: OuraHeartRateSample[] = heartRateFixture;
|
||||
const output = samples.map((sample) => ({
|
||||
timestamp: sample.timestamp ?? null,
|
||||
bpm: sample.bpm ?? null,
|
||||
row: mapHeartRateSample(sample),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-heartrate.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("derives period days from cycle phases", async () => {
|
||||
const scenarios: Array<{ scenario: string; records: OuraCyclePhase[] }> =
|
||||
cyclePhasesFixture;
|
||||
const output = scenarios.map(({ scenario, records }) => ({
|
||||
scenario,
|
||||
periodDays: derivePeriodDaysFromCyclePhases(records),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-cycle-phases.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps workouts", async () => {
|
||||
const records: OuraWorkout[] = workoutsFixture;
|
||||
const output = records.map((record) => ({
|
||||
id: record.id,
|
||||
row: mapOuraWorkout(record),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-workouts.json",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
[
|
||||
{
|
||||
"start_time": "2026-03-02T00:00:00",
|
||||
"active_calories": 512.345,
|
||||
"steps": 9876,
|
||||
"calories": 2450,
|
||||
"distance_from_steps": 7123.456
|
||||
},
|
||||
{
|
||||
"start_time": "2026-03-03T00:00:00.000",
|
||||
"active_calories": 0,
|
||||
"steps": 0,
|
||||
"distance_from_steps": 0
|
||||
},
|
||||
{
|
||||
"start_time": "2026-03-04",
|
||||
"steps": 4321.6,
|
||||
"active_calories": null,
|
||||
"distance_from_steps": null
|
||||
},
|
||||
{
|
||||
"start_time": "2026-03-05T00:00:00",
|
||||
"steps": -5,
|
||||
"active_calories": -10,
|
||||
"distance_from_steps": -1
|
||||
},
|
||||
{
|
||||
"active_calories": 300,
|
||||
"steps": 5000
|
||||
},
|
||||
{
|
||||
"start_time": "garbled-day",
|
||||
"steps": 1000
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"date": "2026-03-02",
|
||||
"cardio_load": 87.654,
|
||||
"strain": 1.2,
|
||||
"tolerance": 0.9,
|
||||
"cardio_load_ratio": 1.33
|
||||
},
|
||||
{ "date": "2026-03-03", "cardio_load": 0 },
|
||||
{ "date": "2026-03-04", "cardio_load": null, "strain": 0.4 },
|
||||
{ "date": "2026-03-05", "cardio_load": -3 },
|
||||
{ "date": "2026-03-06" },
|
||||
{ "date": "not-a-date", "cardio_load": 50 }
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
[
|
||||
{
|
||||
"id": "exercise-0001",
|
||||
"start_time": "2026-03-02T07:00:00",
|
||||
"start_time_utc_offset": 60,
|
||||
"duration": "PT1H2M30S",
|
||||
"sport": "RUNNING",
|
||||
"detailed_sport_info": "TRAIL_RUNNING",
|
||||
"calories": 645.5,
|
||||
"distance": 10250.4,
|
||||
"heart_rate": { "average": 148.6, "maximum": 176 },
|
||||
"training_load": 112.3,
|
||||
"device": "Polar Vantage V3",
|
||||
"device_id": "device-0001"
|
||||
},
|
||||
{
|
||||
"id": 4200017,
|
||||
"start_time": "2026-03-03T18:30:00-05:00",
|
||||
"start_time_utc_offset": -300,
|
||||
"duration": "PT45M",
|
||||
"sport": "CYCLING",
|
||||
"calories": 0,
|
||||
"distance": 0,
|
||||
"heart_rate": { "average": 0, "maximum": null }
|
||||
},
|
||||
{
|
||||
"id": "exercise-0003",
|
||||
"start_time": "2026-03-04T06:15:00Z",
|
||||
"duration": "P1DT2H",
|
||||
"detailed_sport_info": "SOMETHING_POLAR_INVENTED",
|
||||
"heart_rate": null
|
||||
},
|
||||
{
|
||||
"id": "exercise-0004",
|
||||
"start_time": "2026-03-05T12:00:00",
|
||||
"duration": "not-a-duration",
|
||||
"sport": "STRENGTH_TRAINING",
|
||||
"calories": -5,
|
||||
"distance": null,
|
||||
"training_load": null
|
||||
},
|
||||
{
|
||||
"id": "exercise-0005",
|
||||
"start_time": "2026-03-06T09:00:00",
|
||||
"start_time_utc_offset": 330,
|
||||
"duration": "PT30M15.6S",
|
||||
"sport": "OTHER"
|
||||
},
|
||||
{ "id": null, "start_time": "2026-03-07T08:00:00", "duration": "PT20M" },
|
||||
{ "id": " ", "start_time": "2026-03-07T08:00:00", "duration": "PT20M" },
|
||||
{ "id": "exercise-0008", "start_time": "garbled", "duration": "PT20M" },
|
||||
{ "id": "exercise-0009", "duration": "PT20M", "sport": "WALKING" }
|
||||
]
|
||||
@@ -0,0 +1,45 @@
|
||||
[
|
||||
{
|
||||
"date": "2026-03-02",
|
||||
"nightly_recharge_status": 4,
|
||||
"ans_charge": 3.456,
|
||||
"ans_charge_status": 3,
|
||||
"heart_rate_avg": 55.6,
|
||||
"heart_rate_variability_avg": 42.123,
|
||||
"breathing_rate_avg": 14.25
|
||||
},
|
||||
{
|
||||
"date": "2026-03-03",
|
||||
"nightly_recharge_status": 1,
|
||||
"ans_charge": -7.2,
|
||||
"heart_rate_avg": 0,
|
||||
"heart_rate_variability_avg": 0,
|
||||
"breathing_rate_avg": 0
|
||||
},
|
||||
{
|
||||
"date": "2026-03-04",
|
||||
"nightly_recharge_status": 6,
|
||||
"ans_charge": 0
|
||||
},
|
||||
{
|
||||
"date": "2026-03-05",
|
||||
"nightly_recharge_status": 9,
|
||||
"heart_rate_avg": 61
|
||||
},
|
||||
{
|
||||
"date": "2026-03-06",
|
||||
"nightly_recharge_status": 0,
|
||||
"ans_charge": null,
|
||||
"heart_rate_avg": null,
|
||||
"heart_rate_variability_avg": 38,
|
||||
"breathing_rate_avg": null
|
||||
},
|
||||
{
|
||||
"date": "2026-03-07"
|
||||
},
|
||||
{
|
||||
"date": "not-a-date",
|
||||
"nightly_recharge_status": 3,
|
||||
"heart_rate_avg": 58
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
[
|
||||
{
|
||||
"date": "2026-03-02",
|
||||
"sleep_start_time": "2026-03-01T23:10:00+01:00",
|
||||
"sleep_end_time": "2026-03-02T07:05:00+01:00",
|
||||
"light_sleep": 14400,
|
||||
"deep_sleep": 5400,
|
||||
"rem_sleep": 6300,
|
||||
"total_interruption_duration": 2400,
|
||||
"sleep_charge": 3,
|
||||
"sleep_score": 82.5,
|
||||
"heart_rate_samples": { "00:00": 58, "00:05": 56 }
|
||||
},
|
||||
{
|
||||
"date": "2026-03-03",
|
||||
"sleep_start_time": "2026-03-02T22:30:00-05:00",
|
||||
"sleep_end_time": "2026-03-03T06:00:00-05:00",
|
||||
"light_sleep": 16200,
|
||||
"deep_sleep": 0,
|
||||
"rem_sleep": 7200,
|
||||
"total_interruption_duration": 0
|
||||
},
|
||||
{
|
||||
"date": "2026-03-04",
|
||||
"light_sleep": 15000,
|
||||
"deep_sleep": 4800,
|
||||
"rem_sleep": 6000,
|
||||
"total_interruption_duration": 1234,
|
||||
"sleep_score": 76
|
||||
},
|
||||
{
|
||||
"date": "2026-03-05",
|
||||
"sleep_start_time": "2026-03-05T06:00:00Z",
|
||||
"sleep_end_time": "2026-03-05T06:00:00Z",
|
||||
"light_sleep": 0,
|
||||
"deep_sleep": null,
|
||||
"rem_sleep": 3000,
|
||||
"sleep_score": null
|
||||
},
|
||||
{
|
||||
"date": "2026-03-06",
|
||||
"sleep_start_time": "garbled",
|
||||
"sleep_end_time": "2026-03-06T06:30:00Z",
|
||||
"light_sleep": 13500,
|
||||
"rem_sleep": 5400,
|
||||
"total_interruption_duration": 900,
|
||||
"sleep_score": 70
|
||||
},
|
||||
{
|
||||
"date": "2026-03-07",
|
||||
"sleep_start_time": "2026-03-06T23:00:00Z",
|
||||
"sleep_end_time": "2026-03-07T06:45:00Z",
|
||||
"light_sleep": 12345.6,
|
||||
"deep_sleep": 4567,
|
||||
"rem_sleep": 5678,
|
||||
"total_interruption_duration": null,
|
||||
"sleep_score": 0
|
||||
},
|
||||
{
|
||||
"date": "not-a-date",
|
||||
"light_sleep": 14000
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"source_device_id": "device-0001",
|
||||
"test_time": 1772438400,
|
||||
"blood_oxygen_percent": 97.456
|
||||
},
|
||||
{ "test_time": 1772524800, "blood_oxygen_percent": 100 },
|
||||
{
|
||||
"source_device_id": "device-0001",
|
||||
"test_time": 1772611200,
|
||||
"blood_oxygen_percent": 0
|
||||
},
|
||||
{
|
||||
"source_device_id": "device-0001",
|
||||
"test_time": 1772697600,
|
||||
"blood_oxygen_percent": 100.5
|
||||
},
|
||||
{
|
||||
"source_device_id": "device-0002",
|
||||
"test_time": 1772784000,
|
||||
"blood_oxygen_percent": null
|
||||
},
|
||||
{ "source_device_id": "device-0002", "blood_oxygen_percent": 95 }
|
||||
]
|
||||
@@ -0,0 +1,260 @@
|
||||
[
|
||||
{
|
||||
"record": "2026-03-02T00:00:00",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 9876,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 512.35,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "active_energy",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 7123.46,
|
||||
"unit": "meters",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "distance",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 9876,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 512.35,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "active_energy",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 7123.46,
|
||||
"unit": "meters",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "distance",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 9876,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 512.35,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "active_energy",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 7123.46,
|
||||
"unit": "meters",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "distance",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-03T00:00:00.000",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 0,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "active_energy",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 0,
|
||||
"unit": "meters",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "distance",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 0,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "active_energy",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 0,
|
||||
"unit": "meters",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "distance",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 0,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "ACTIVE_ENERGY_BURNED",
|
||||
"value": 0,
|
||||
"unit": "kcal",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "active_energy",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "WALKING_RUNNING_DISTANCE",
|
||||
"value": 0,
|
||||
"unit": "meters",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "distance",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-04",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 4322,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 4322,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "ACTIVITY_STEPS",
|
||||
"value": 4322,
|
||||
"unit": "steps",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "steps",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-05T00:00:00",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{ "tz": "Europe/Berlin", "rows": [] },
|
||||
{ "tz": "America/New_York", "rows": [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "(no start_time)",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{ "tz": "Europe/Berlin", "rows": [] },
|
||||
{ "tz": "America/New_York", "rows": [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "garbled-day",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": { "threw": "RangeError: Invalid time value" }
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": { "threw": "RangeError: Invalid time value" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,128 @@
|
||||
[
|
||||
{
|
||||
"record": "2026-03-02",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "CARDIO_LOAD",
|
||||
"value": 87.65,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "cardio_load",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "CARDIO_LOAD",
|
||||
"value": 87.65,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "cardio_load",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "CARDIO_LOAD",
|
||||
"value": 87.65,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "cardio_load",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-03",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "CARDIO_LOAD",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "cardio_load",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "CARDIO_LOAD",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "cardio_load",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "CARDIO_LOAD",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "cardio_load",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-04",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{ "tz": "Europe/Berlin", "rows": [] },
|
||||
{ "tz": "America/New_York", "rows": [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-05",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{ "tz": "Europe/Berlin", "rows": [] },
|
||||
{ "tz": "America/New_York", "rows": [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-06",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{ "tz": "Europe/Berlin", "rows": [] },
|
||||
{ "tz": "America/New_York", "rows": [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "not-a-date",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": { "threw": "RangeError: Invalid time value" }
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": { "threw": "RangeError: Invalid time value" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,93 @@
|
||||
[
|
||||
{
|
||||
"id": "exercise-0001",
|
||||
"row": {
|
||||
"externalId": "exercise-0001",
|
||||
"sportType": "running",
|
||||
"startedAt": "2026-03-02T06:00:00.000Z",
|
||||
"endedAt": "2026-03-02T07:02:30.000Z",
|
||||
"durationSec": 3750,
|
||||
"totalEnergyKcal": 645.5,
|
||||
"totalDistanceM": 10250.4,
|
||||
"avgHeartRate": 149,
|
||||
"maxHeartRate": 176,
|
||||
"elevationM": null,
|
||||
"metadata": {
|
||||
"polarSport": "RUNNING",
|
||||
"polarDetailedSportInfo": "TRAIL_RUNNING",
|
||||
"polarDevice": "Polar Vantage V3",
|
||||
"polarDeviceId": "device-0001",
|
||||
"trainingLoad": 112.3,
|
||||
"startTimeUtcOffset": 60
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4200017,
|
||||
"row": {
|
||||
"externalId": "4200017",
|
||||
"sportType": "cycling",
|
||||
"startedAt": "2026-03-03T23:30:00.000Z",
|
||||
"endedAt": "2026-03-04T00:15:00.000Z",
|
||||
"durationSec": 2700,
|
||||
"totalEnergyKcal": 0,
|
||||
"totalDistanceM": 0,
|
||||
"avgHeartRate": 0,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "polarSport": "CYCLING", "startTimeUtcOffset": -300 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "exercise-0003",
|
||||
"row": {
|
||||
"externalId": "exercise-0003",
|
||||
"sportType": "other",
|
||||
"startedAt": "2026-03-04T06:15:00.000Z",
|
||||
"endedAt": "2026-03-05T08:15:00.000Z",
|
||||
"durationSec": 93600,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "polarDetailedSportInfo": "SOMETHING_POLAR_INVENTED" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "exercise-0004",
|
||||
"row": {
|
||||
"externalId": "exercise-0004",
|
||||
"sportType": "strength",
|
||||
"startedAt": "2026-03-05T12:00:00.000Z",
|
||||
"endedAt": "2026-03-05T12:00:00.000Z",
|
||||
"durationSec": 0,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "polarSport": "STRENGTH_TRAINING" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "exercise-0005",
|
||||
"row": {
|
||||
"externalId": "exercise-0005",
|
||||
"sportType": "other",
|
||||
"startedAt": "2026-03-06T03:30:00.000Z",
|
||||
"endedAt": "2026-03-06T04:00:16.000Z",
|
||||
"durationSec": 1816,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "polarSport": "OTHER", "startTimeUtcOffset": 330 }
|
||||
}
|
||||
},
|
||||
{ "id": null, "row": null },
|
||||
{ "id": " ", "row": null },
|
||||
{ "id": "exercise-0008", "row": null },
|
||||
{ "id": "exercise-0009", "row": null }
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"normaliseExerciseList": [
|
||||
{ "label": "bare array", "result": [{ "id": "exercise-0001" }] },
|
||||
{ "label": "exercises key", "result": [{ "id": "exercise-0001" }] },
|
||||
{ "label": "data key", "result": [{ "id": "exercise-0001" }] },
|
||||
{ "label": "items key", "result": [{ "id": "exercise-0001" }] },
|
||||
{ "label": "records key", "result": [{ "id": "exercise-0001" }] },
|
||||
{ "label": "first known key wins", "result": [{ "id": "exercise-0001" }] },
|
||||
{ "label": "empty known key", "result": [] },
|
||||
{ "label": "unknown key", "result": [] },
|
||||
{ "label": "known key not an array", "result": [] },
|
||||
{ "label": "null", "result": [] },
|
||||
{ "label": "empty string", "result": [] },
|
||||
{ "label": "number", "result": [] }
|
||||
],
|
||||
"combinePolarStart": [
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00",
|
||||
"offset": "60",
|
||||
"result": "2026-03-02T06:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00",
|
||||
"offset": "-300",
|
||||
"result": "2026-03-02T12:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00",
|
||||
"offset": "0",
|
||||
"result": "2026-03-02T07:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00",
|
||||
"offset": "null",
|
||||
"result": "2026-03-02T07:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00",
|
||||
"offset": "NaN",
|
||||
"result": "2026-03-02T07:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00.500",
|
||||
"offset": "330",
|
||||
"result": "2026-03-02T01:30:00.500Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00Z",
|
||||
"offset": "120",
|
||||
"result": "2026-03-02T07:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00+02:00",
|
||||
"offset": "120",
|
||||
"result": "2026-03-02T05:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"startTime": "2026-03-02T07:00:00-0500",
|
||||
"offset": "-300",
|
||||
"result": "2026-03-02T12:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"startTime": " 2026-03-02T07:00:00 ",
|
||||
"offset": "60",
|
||||
"result": "2026-03-02T06:00:00.000Z"
|
||||
},
|
||||
{ "startTime": "garbled", "offset": "60", "result": null },
|
||||
{ "startTime": "", "offset": "60", "result": null },
|
||||
{ "startTime": " ", "offset": "60", "result": null },
|
||||
{ "startTime": null, "offset": "60", "result": null },
|
||||
{ "startTime": null, "offset": "(undefined)", "result": null }
|
||||
],
|
||||
"parseIsoDuration": [
|
||||
{ "raw": "PT2H44M30S", "seconds": 9870 },
|
||||
{ "raw": "PT45S", "seconds": 45 },
|
||||
{ "raw": "PT45M", "seconds": 2700 },
|
||||
{ "raw": "P1DT2H", "seconds": 93600 },
|
||||
{ "raw": "P2D", "seconds": 172800 },
|
||||
{ "raw": "PT30M15.6S", "seconds": 1816 },
|
||||
{ "raw": "PT0.4S", "seconds": 0 },
|
||||
{ "raw": "pt1h", "seconds": 3600 },
|
||||
{ "raw": " PT10M ", "seconds": 600 },
|
||||
{ "raw": "PT", "seconds": 0 },
|
||||
{ "raw": "P", "seconds": 0 },
|
||||
{ "raw": "PT-5M", "seconds": 0 },
|
||||
{ "raw": "1:02:30", "seconds": 0 },
|
||||
{ "raw": "", "seconds": 0 },
|
||||
{ "raw": null, "seconds": 0 },
|
||||
{ "raw": "(undefined)", "seconds": 0 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
[
|
||||
{
|
||||
"record": "2026-03-02",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 60,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 42.12,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "hrv_rmssd",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 56,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "rhr",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "RESPIRATORY_RATE",
|
||||
"value": 14.25,
|
||||
"unit": "breaths/min",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "resp_rate",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": 3.46,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T12:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 60,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 42.12,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "hrv_rmssd",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 56,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "rhr",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "RESPIRATORY_RATE",
|
||||
"value": 14.25,
|
||||
"unit": "breaths/min",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "resp_rate",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": 3.46,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T11:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 60,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 42.12,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "hrv_rmssd",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 56,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "rhr",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "RESPIRATORY_RATE",
|
||||
"value": 14.25,
|
||||
"unit": "breaths/min",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "resp_rate",
|
||||
"day": "2026-03-02"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": 3.46,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T17:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-02"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-03",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": -7.2,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T12:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": -7.2,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-03"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": -7.2,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T17:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-03"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-04",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 100,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 100,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 100,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "ANS_CHARGE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "ans_charge",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-05",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 100,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-05T12:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-05"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 61,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-05T12:00:00.000Z",
|
||||
"fieldTag": "rhr",
|
||||
"day": "2026-03-05"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 100,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-05T11:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-05"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 61,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-05T11:00:00.000Z",
|
||||
"fieldTag": "rhr",
|
||||
"day": "2026-03-05"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 100,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-05T17:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-05"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 61,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-05T17:00:00.000Z",
|
||||
"fieldTag": "rhr",
|
||||
"day": "2026-03-05"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-06",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 38,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "hrv_rmssd",
|
||||
"day": "2026-03-06"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 38,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "hrv_rmssd",
|
||||
"day": "2026-03-06"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "recovery",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 38,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "hrv_rmssd",
|
||||
"day": "2026-03-06"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-07",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{ "tz": "Europe/Berlin", "rows": [] },
|
||||
{ "tz": "America/New_York", "rows": [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "not-a-date",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": { "threw": "RangeError: Invalid time value" }
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": { "threw": "RangeError: Invalid time value" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,811 @@
|
||||
[
|
||||
{
|
||||
"record": "2026-03-02",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 40,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-01T22:50:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 240,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T02:50:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T04:20:00.000Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 105,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 475,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 82.5,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 40,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-01T22:50:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 240,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T02:50:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T04:20:00.000Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 105,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 475,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 82.5,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 40,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-01T22:50:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 240,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T02:50:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T04:20:00.000Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 105,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-02:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 475,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 82.5,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T06:05:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-03",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 270,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T08:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-03:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 120,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T10:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-03:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 450,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 270,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T08:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-03:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 120,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T10:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-03:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 450,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 270,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T08:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-03:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 120,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T10:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-03:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 450,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-03T11:00:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-04",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 20.57,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 250,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 80,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 100,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 76,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-04T12:00:00.000Z",
|
||||
"fieldTag": "sleep_score",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 20.57,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 250,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 80,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 100,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 76,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-04T11:00:00.000Z",
|
||||
"fieldTag": "sleep_score",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 20.57,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 250,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 80,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 100,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-04"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 76,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-04T17:00:00.000Z",
|
||||
"fieldTag": "sleep_score",
|
||||
"day": "2026-03-04"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-05",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 0,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T12:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-05"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 50,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T12:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-05"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 0,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T11:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-05"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 50,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T11:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-05"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 0,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T17:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-05"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 50,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T17:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-05"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-06",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 225,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 70,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-06T12:00:00.000Z",
|
||||
"fieldTag": "sleep_score",
|
||||
"day": "2026-03-06"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 225,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 70,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-06T11:00:00.000Z",
|
||||
"fieldTag": "sleep_score",
|
||||
"day": "2026-03-06"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 15,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 225,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"day": "2026-03-06"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 70,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-06T17:00:00.000Z",
|
||||
"fieldTag": "sleep_score",
|
||||
"day": "2026-03-06"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "2026-03-07",
|
||||
"byZone": [
|
||||
{
|
||||
"tz": null,
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 205.76,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T02:25:45.600Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 76.12,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T03:41:52.600Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 94.63,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T05:16:30.600Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 465,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T06:45:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 0,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-07T06:45:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 205.76,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T02:25:45.600Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 76.12,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T03:41:52.600Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 94.63,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T05:16:30.600Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 465,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T06:45:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 0,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-07T06:45:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 205.76,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T02:25:45.600Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 76.12,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T03:41:52.600Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 94.63,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T05:16:30.600Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep:2026-03-07:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 465,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-07T06:45:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 0,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-07T06:45:00.000Z",
|
||||
"fieldTag": "sleep_score"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"record": "not-a-date",
|
||||
"byZone": [
|
||||
{ "tz": null, "rows": [] },
|
||||
{
|
||||
"tz": "Europe/Berlin",
|
||||
"rows": { "threw": "RangeError: Invalid time value" }
|
||||
},
|
||||
{
|
||||
"tz": "America/New_York",
|
||||
"rows": { "threw": "RangeError: Invalid time value" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"test_time": 1772438400,
|
||||
"rows": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 97.46,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T08:00:00.000Z",
|
||||
"fieldTag": "spo2",
|
||||
"externalId": "spo2:1772438400:device-0001"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"test_time": 1772524800,
|
||||
"rows": [
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 100,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-03T08:00:00.000Z",
|
||||
"fieldTag": "spo2",
|
||||
"externalId": "spo2:1772524800:unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "test_time": 1772611200, "rows": [] },
|
||||
{ "test_time": 1772697600, "rows": [] },
|
||||
{ "test_time": 1772784000, "rows": [] },
|
||||
{ "test_time": null, "rows": [] }
|
||||
]
|
||||
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* What each Polar mapper makes of a fixed set of payloads, pinned in
|
||||
* `__golden__/`.
|
||||
*
|
||||
* The fixtures under `__fixtures__/` are synthetic payloads in the documented
|
||||
* AccessLink wire shape: complete and sparse records, zero and negative
|
||||
* values, missing or unparseable dates and sleep windows, offsets other than
|
||||
* UTC, and exercise ids of either type. The date-keyed mappers run once per
|
||||
* zone (none, Europe/Berlin, America/New_York), because the zone decides
|
||||
* where a day's noon anchor lands. The goldens were written while the mappers
|
||||
* still lived in `client.ts`, so a green run after they move proves the move
|
||||
* changed nothing.
|
||||
*/
|
||||
import { format } from "prettier";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
combinePolarStart,
|
||||
mapActivity,
|
||||
mapCardioLoad,
|
||||
mapExercise,
|
||||
mapNightlyRecharge,
|
||||
mapSleep,
|
||||
mapSpo2,
|
||||
normaliseExerciseList,
|
||||
parseIsoDuration,
|
||||
type PolarActivity,
|
||||
type PolarCardioLoad,
|
||||
type PolarExercise,
|
||||
type PolarNightlyRecharge,
|
||||
type PolarSleep,
|
||||
type PolarSpo2,
|
||||
} from "../client";
|
||||
import activityFixture from "./__fixtures__/activity.json";
|
||||
import cardioLoadFixture from "./__fixtures__/cardio-load.json";
|
||||
import exercisesFixture from "./__fixtures__/exercises.json";
|
||||
import nightlyRechargeFixture from "./__fixtures__/nightly-recharge.json";
|
||||
import sleepFixture from "./__fixtures__/sleep.json";
|
||||
import spo2Fixture from "./__fixtures__/spo2.json";
|
||||
|
||||
const recharges: readonly PolarNightlyRecharge[] = nightlyRechargeFixture;
|
||||
const sleeps: readonly PolarSleep[] = sleepFixture;
|
||||
const activities: readonly PolarActivity[] = activityFixture;
|
||||
const cardioLoads: readonly PolarCardioLoad[] = cardioLoadFixture;
|
||||
const spo2Tests: readonly PolarSpo2[] = spo2Fixture;
|
||||
const exercises: readonly PolarExercise[] = exercisesFixture;
|
||||
|
||||
const ZONES = [undefined, "Europe/Berlin", "America/New_York"] as const;
|
||||
|
||||
/** The golden is written in the repository's JSON format, so the format check passes on it unchanged. */
|
||||
async function golden(value: unknown): Promise<string> {
|
||||
return format(JSON.stringify(value), { parser: "json" });
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapper's result, or the error it throws. An unparseable date throws once
|
||||
* a zone is in play (the noon anchor is computed through `Intl`), and that
|
||||
* behaviour is part of what the golden pins.
|
||||
*/
|
||||
function attempt(run: () => unknown): unknown {
|
||||
try {
|
||||
return run();
|
||||
} catch (err) {
|
||||
return {
|
||||
threw: err instanceof Error ? `${err.name}: ${err.message}` : String(err),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Every record mapped once per zone, labelled so a diff names its input. */
|
||||
function perZone<T>(
|
||||
records: readonly T[],
|
||||
label: (record: T) => string,
|
||||
map: (record: T, tz?: string) => unknown,
|
||||
) {
|
||||
return records.map((record) => ({
|
||||
record: label(record),
|
||||
byZone: ZONES.map((tz) => ({
|
||||
tz: tz ?? null,
|
||||
rows: attempt(() => map(record, tz)),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
describe("Polar mappers against the fixture payloads", () => {
|
||||
it("maps nightly recharges", async () => {
|
||||
const output = perZone(recharges, (r) => r.date, mapNightlyRecharge);
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-nightly-recharge.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps sleeps", async () => {
|
||||
const output = perZone(sleeps, (s) => s.date, mapSleep);
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-sleep.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps daily activity", async () => {
|
||||
const output = perZone(
|
||||
activities,
|
||||
(a) => a.start_time ?? "(no start_time)",
|
||||
mapActivity,
|
||||
);
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-activity.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps cardio load", async () => {
|
||||
const output = perZone(cardioLoads, (c) => c.date, mapCardioLoad);
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-cardio-load.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps SpO2 tests", async () => {
|
||||
const output = spo2Tests.map((record) => ({
|
||||
test_time: record.test_time ?? null,
|
||||
rows: mapSpo2(record),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-spo2.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps exercises", async () => {
|
||||
const output = exercises.map((record) => ({
|
||||
id: record.id ?? null,
|
||||
row: mapExercise(record),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-exercises.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("normalises exercise list bodies, combines start times and parses durations", async () => {
|
||||
const one = { id: "exercise-0001" };
|
||||
const bodies: Array<[string, unknown]> = [
|
||||
["bare array", [one]],
|
||||
["exercises key", { exercises: [one] }],
|
||||
["data key", { data: [one] }],
|
||||
["items key", { items: [one] }],
|
||||
["records key", { records: [one] }],
|
||||
["first known key wins", { records: [{ id: "b" }], exercises: [one] }],
|
||||
["empty known key", { exercises: [] }],
|
||||
["unknown key", { results: [one] }],
|
||||
["known key not an array", { exercises: one }],
|
||||
["null", null],
|
||||
["empty string", ""],
|
||||
["number", 42],
|
||||
];
|
||||
const starts: Array<
|
||||
[string | null | undefined, number | null | undefined]
|
||||
> = [
|
||||
["2026-03-02T07:00:00", 60],
|
||||
["2026-03-02T07:00:00", -300],
|
||||
["2026-03-02T07:00:00", 0],
|
||||
["2026-03-02T07:00:00", null],
|
||||
["2026-03-02T07:00:00", Number.NaN],
|
||||
["2026-03-02T07:00:00.500", 330],
|
||||
["2026-03-02T07:00:00Z", 120],
|
||||
["2026-03-02T07:00:00+02:00", 120],
|
||||
["2026-03-02T07:00:00-0500", -300],
|
||||
[" 2026-03-02T07:00:00 ", 60],
|
||||
["garbled", 60],
|
||||
["", 60],
|
||||
[" ", 60],
|
||||
[null, 60],
|
||||
[undefined, undefined],
|
||||
];
|
||||
const durations: Array<string | null | undefined> = [
|
||||
"PT2H44M30S",
|
||||
"PT45S",
|
||||
"PT45M",
|
||||
"P1DT2H",
|
||||
"P2D",
|
||||
"PT30M15.6S",
|
||||
"PT0.4S",
|
||||
"pt1h",
|
||||
" PT10M ",
|
||||
"PT",
|
||||
"P",
|
||||
"PT-5M",
|
||||
"1:02:30",
|
||||
"",
|
||||
null,
|
||||
undefined,
|
||||
];
|
||||
const output = {
|
||||
normaliseExerciseList: bodies.map(([label, body]) => ({
|
||||
label,
|
||||
result: normaliseExerciseList(body),
|
||||
})),
|
||||
combinePolarStart: starts.map(([startTime, offset]) => ({
|
||||
startTime: startTime ?? null,
|
||||
offset: offset === undefined ? "(undefined)" : String(offset),
|
||||
result: combinePolarStart(startTime, offset),
|
||||
})),
|
||||
parseIsoDuration: durations.map((raw) => ({
|
||||
raw: raw === undefined ? "(undefined)" : raw,
|
||||
seconds: parseIsoDuration(raw),
|
||||
})),
|
||||
};
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-helpers.json",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
[
|
||||
{
|
||||
"label": "run with heart rate, power and a detail read",
|
||||
"summary": {
|
||||
"id": 10000000001,
|
||||
"name": "Morning Run",
|
||||
"distance": 10234.5,
|
||||
"moving_time": 3120,
|
||||
"elapsed_time": 3300,
|
||||
"total_elevation_gain": 85.2,
|
||||
"type": "Run",
|
||||
"sport_type": "TrailRun",
|
||||
"start_date": "2026-03-02T06:00:00Z",
|
||||
"start_date_local": "2026-03-02T07:00:00Z",
|
||||
"average_heartrate": 151.6,
|
||||
"max_heartrate": 178.2,
|
||||
"has_heartrate": true,
|
||||
"average_watts": 245.3,
|
||||
"weighted_average_watts": 252,
|
||||
"device_watts": true,
|
||||
"average_cadence": 86.5,
|
||||
"trainer": false,
|
||||
"commute": false,
|
||||
"gear_id": "gear-0001"
|
||||
},
|
||||
"detail": {
|
||||
"id": 10000000001,
|
||||
"start_date": "2026-03-02T06:00:00Z",
|
||||
"calories": 712.4,
|
||||
"description": "Easy loop around the park"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "summary-only ride without heart rate",
|
||||
"summary": {
|
||||
"id": 10000000002,
|
||||
"name": "Lunch Ride",
|
||||
"distance": 32500,
|
||||
"moving_time": 4500,
|
||||
"elapsed_time": 5100,
|
||||
"total_elevation_gain": 210,
|
||||
"type": "Ride",
|
||||
"sport_type": "Ride",
|
||||
"start_date": "2026-03-03T11:30:00Z",
|
||||
"has_heartrate": false,
|
||||
"average_watts": 180.5,
|
||||
"device_watts": false,
|
||||
"trainer": true,
|
||||
"commute": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "summary without a start, filled from the detail",
|
||||
"summary": {
|
||||
"id": 10000000003,
|
||||
"name": "Afternoon Walk",
|
||||
"sport_type": "Walk",
|
||||
"start_date": null
|
||||
},
|
||||
"detail": {
|
||||
"id": 10000000003,
|
||||
"start_date": "2026-03-04T15:00:00+01:00",
|
||||
"distance": 4200,
|
||||
"moving_time": 2700,
|
||||
"elapsed_time": 3000,
|
||||
"total_elevation_gain": 12,
|
||||
"average_heartrate": 98.4,
|
||||
"max_heartrate": 121.5,
|
||||
"calories": 0,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "legacy type only",
|
||||
"summary": {
|
||||
"id": 10000000004,
|
||||
"name": "Weekend Hike",
|
||||
"type": "Hike",
|
||||
"sport_type": null,
|
||||
"start_date": "2026-03-07T08:00:00Z",
|
||||
"moving_time": 10800,
|
||||
"elapsed_time": null,
|
||||
"distance": 14000.25
|
||||
},
|
||||
"detail": null
|
||||
},
|
||||
{
|
||||
"label": "unknown sport, moving time only",
|
||||
"summary": {
|
||||
"id": 10000000005,
|
||||
"sport_type": "SomethingStravaAddedLater",
|
||||
"start_date": "2026-03-08T18:00:00-05:00",
|
||||
"moving_time": 1800
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "no sport label anywhere",
|
||||
"summary": {
|
||||
"id": 10000000006,
|
||||
"start_date": "2026-03-09T07:00:00Z",
|
||||
"elapsed_time": 1500
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "zero and negative values",
|
||||
"summary": {
|
||||
"id": 10000000007,
|
||||
"name": "",
|
||||
"distance": 0,
|
||||
"moving_time": -10,
|
||||
"elapsed_time": 0,
|
||||
"total_elevation_gain": -3.5,
|
||||
"sport_type": "Virtual Ride",
|
||||
"start_date": "2026-03-10T19:00:00Z",
|
||||
"average_heartrate": -1,
|
||||
"max_heartrate": 0,
|
||||
"gear_id": ""
|
||||
},
|
||||
"detail": {
|
||||
"id": 10000000007,
|
||||
"calories": -12,
|
||||
"sport_type": "VirtualRide"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "detail sport type used when the summary has none",
|
||||
"summary": {
|
||||
"id": 10000000008,
|
||||
"start_date": "2026-03-11T05:45:00Z",
|
||||
"moving_time": 2400,
|
||||
"elapsed_time": 2460
|
||||
},
|
||||
"detail": {
|
||||
"id": 10000000008,
|
||||
"sport_type": "weight_training",
|
||||
"average_heartrate": 112,
|
||||
"calories": 250.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "no start anywhere",
|
||||
"summary": {
|
||||
"id": 10000000009,
|
||||
"sport_type": "Swim",
|
||||
"moving_time": 1800
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "unparseable start",
|
||||
"summary": {
|
||||
"id": 10000000010,
|
||||
"sport_type": "Swim",
|
||||
"start_date": "yesterday morning",
|
||||
"moving_time": 1800
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "no durations at all",
|
||||
"summary": {
|
||||
"id": 10000000011,
|
||||
"sport_type": "Yoga",
|
||||
"start_date": "2026-03-12T20:00:00Z",
|
||||
"has_heartrate": null
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,190 @@
|
||||
[
|
||||
{
|
||||
"label": "run with heart rate, power and a detail read",
|
||||
"summaryHasHeartRate": true,
|
||||
"row": {
|
||||
"externalId": "10000000001",
|
||||
"sportType": "running",
|
||||
"startedAt": "2026-03-02T06:00:00.000Z",
|
||||
"endedAt": "2026-03-02T06:55:00.000Z",
|
||||
"durationSec": 3120,
|
||||
"totalEnergyKcal": 712.4,
|
||||
"totalDistanceM": 10234.5,
|
||||
"avgHeartRate": 152,
|
||||
"maxHeartRate": 178,
|
||||
"elevationM": 85.2,
|
||||
"metadata": {
|
||||
"stravaName": "Morning Run",
|
||||
"stravaDescription": "Easy loop around the park",
|
||||
"elapsedTimeSec": 3300,
|
||||
"stravaType": "TrailRun",
|
||||
"averageWatts": 245.3,
|
||||
"weightedAverageWatts": 252,
|
||||
"deviceWatts": true,
|
||||
"averageCadence": 86.5,
|
||||
"trainer": false,
|
||||
"commute": false,
|
||||
"gearId": "gear-0001"
|
||||
},
|
||||
"summaryOnly": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "summary-only ride without heart rate",
|
||||
"summaryHasHeartRate": false,
|
||||
"row": {
|
||||
"externalId": "10000000002",
|
||||
"sportType": "cycling",
|
||||
"startedAt": "2026-03-03T11:30:00.000Z",
|
||||
"endedAt": "2026-03-03T12:55:00.000Z",
|
||||
"durationSec": 4500,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": 32500,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": 210,
|
||||
"metadata": {
|
||||
"stravaName": "Lunch Ride",
|
||||
"elapsedTimeSec": 5100,
|
||||
"stravaType": "Ride",
|
||||
"averageWatts": 180.5,
|
||||
"deviceWatts": false,
|
||||
"trainer": true,
|
||||
"commute": true
|
||||
},
|
||||
"summaryOnly": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "summary without a start, filled from the detail",
|
||||
"summaryHasHeartRate": false,
|
||||
"row": {
|
||||
"externalId": "10000000003",
|
||||
"sportType": "walking",
|
||||
"startedAt": "2026-03-04T14:00:00.000Z",
|
||||
"endedAt": "2026-03-04T14:50:00.000Z",
|
||||
"durationSec": 2700,
|
||||
"totalEnergyKcal": 0,
|
||||
"totalDistanceM": 4200,
|
||||
"avgHeartRate": 98,
|
||||
"maxHeartRate": 122,
|
||||
"elevationM": 12,
|
||||
"metadata": {
|
||||
"stravaName": "Afternoon Walk",
|
||||
"elapsedTimeSec": 3000,
|
||||
"stravaType": "Walk"
|
||||
},
|
||||
"summaryOnly": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "legacy type only",
|
||||
"summaryHasHeartRate": false,
|
||||
"row": {
|
||||
"externalId": "10000000004",
|
||||
"sportType": "hiking",
|
||||
"startedAt": "2026-03-07T08:00:00.000Z",
|
||||
"endedAt": "2026-03-07T11:00:00.000Z",
|
||||
"durationSec": 10800,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": 14000.25,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "stravaName": "Weekend Hike", "stravaType": "Hike" },
|
||||
"summaryOnly": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "unknown sport, moving time only",
|
||||
"summaryHasHeartRate": false,
|
||||
"row": {
|
||||
"externalId": "10000000005",
|
||||
"sportType": "other",
|
||||
"startedAt": "2026-03-08T23:00:00.000Z",
|
||||
"endedAt": "2026-03-08T23:30:00.000Z",
|
||||
"durationSec": 1800,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "stravaType": "SomethingStravaAddedLater" },
|
||||
"summaryOnly": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "no sport label anywhere",
|
||||
"summaryHasHeartRate": false,
|
||||
"row": {
|
||||
"externalId": "10000000006",
|
||||
"sportType": "other",
|
||||
"startedAt": "2026-03-09T07:00:00.000Z",
|
||||
"endedAt": "2026-03-09T07:25:00.000Z",
|
||||
"durationSec": 1500,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "elapsedTimeSec": 1500, "stravaType": "Workout" },
|
||||
"summaryOnly": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "zero and negative values",
|
||||
"summaryHasHeartRate": true,
|
||||
"row": {
|
||||
"externalId": "10000000007",
|
||||
"sportType": "cycling",
|
||||
"startedAt": "2026-03-10T19:00:00.000Z",
|
||||
"endedAt": "2026-03-10T19:00:00.000Z",
|
||||
"durationSec": 0,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": 0,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": 0,
|
||||
"elevationM": null,
|
||||
"metadata": { "elapsedTimeSec": 0, "stravaType": "Virtual Ride" },
|
||||
"summaryOnly": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "detail sport type used when the summary has none",
|
||||
"summaryHasHeartRate": false,
|
||||
"row": {
|
||||
"externalId": "10000000008",
|
||||
"sportType": "strength",
|
||||
"startedAt": "2026-03-11T05:45:00.000Z",
|
||||
"endedAt": "2026-03-11T06:26:00.000Z",
|
||||
"durationSec": 2400,
|
||||
"totalEnergyKcal": 250.5,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": 112,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "elapsedTimeSec": 2460, "stravaType": "weight_training" },
|
||||
"summaryOnly": false
|
||||
}
|
||||
},
|
||||
{ "label": "no start anywhere", "summaryHasHeartRate": false, "row": null },
|
||||
{ "label": "unparseable start", "summaryHasHeartRate": false, "row": null },
|
||||
{
|
||||
"label": "no durations at all",
|
||||
"summaryHasHeartRate": false,
|
||||
"row": {
|
||||
"externalId": "10000000011",
|
||||
"sportType": "yoga",
|
||||
"startedAt": "2026-03-12T20:00:00.000Z",
|
||||
"endedAt": "2026-03-12T20:00:00.000Z",
|
||||
"durationSec": 0,
|
||||
"totalEnergyKcal": null,
|
||||
"totalDistanceM": null,
|
||||
"avgHeartRate": null,
|
||||
"maxHeartRate": null,
|
||||
"elevationM": null,
|
||||
"metadata": { "stravaType": "Yoga" },
|
||||
"summaryOnly": true
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{ "label": "no headers", "seconds": 450 },
|
||||
{ "label": "retry-after seconds", "seconds": 120 },
|
||||
{ "label": "retry-after HTTP date", "seconds": 150 },
|
||||
{ "label": "retry-after zero falls through", "seconds": 450 },
|
||||
{ "label": "15-minute window spent", "seconds": 450 },
|
||||
{ "label": "daily window spent", "seconds": 49950 },
|
||||
{ "label": "daily read window spent", "seconds": 49950 },
|
||||
{ "label": "malformed pair", "seconds": 450 },
|
||||
{ "label": "exactly on a quarter hour", "seconds": 900 },
|
||||
{ "label": "half a second before midnight, daily spent", "seconds": 1 }
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{ "raw": "Run", "sportType": "running" },
|
||||
{ "raw": "TrailRun", "sportType": "running" },
|
||||
{ "raw": "VirtualRide", "sportType": "cycling" },
|
||||
{ "raw": "Virtual Ride", "sportType": "cycling" },
|
||||
{ "raw": "virtual_ride", "sportType": "cycling" },
|
||||
{ "raw": " WeightTraining ", "sportType": "strength" },
|
||||
{ "raw": "HighIntensityIntervalTraining", "sportType": "hiit" },
|
||||
{ "raw": "Snowshoe", "sportType": "hiking" },
|
||||
{ "raw": "Workout", "sportType": "other" },
|
||||
{ "raw": "SomethingStravaAddedLater", "sportType": "other" },
|
||||
{ "raw": "", "sportType": "other" },
|
||||
{ "raw": " ", "sportType": "other" },
|
||||
{ "raw": null, "sportType": "other" },
|
||||
{ "raw": "(undefined)", "sportType": "other" }
|
||||
]
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* What each Strava mapper makes of a fixed set of payloads, pinned in
|
||||
* `__golden__/`.
|
||||
*
|
||||
* The fixtures under `__fixtures__/` are synthetic `SummaryActivity`
|
||||
* payloads in the documented v3 wire shape, each paired with the
|
||||
* `DetailedActivity` the sync would read for it (or none): heart rate on the
|
||||
* summary or only on the detail, a start only on the detail, legacy `type`
|
||||
* labels, unknown sports, zero and negative values, and the records the
|
||||
* mapper refuses. The rate-limit wait and the sport lookup take scalar
|
||||
* inputs, so their cases are tables in this file. The goldens were written
|
||||
* while the mappers still lived in `client.ts`, so a green run after they
|
||||
* move proves the move changed nothing.
|
||||
*/
|
||||
import { format } from "prettier";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
mapActivity,
|
||||
stravaRateLimitRetryAfterSeconds,
|
||||
summaryHasHeartRate,
|
||||
type StravaDetailedActivity,
|
||||
type StravaSummaryActivity,
|
||||
} from "../client";
|
||||
import { mapStravaSportType } from "../sport-map";
|
||||
import activitiesFixture from "./__fixtures__/activities.json";
|
||||
|
||||
interface ActivityCase {
|
||||
label: string;
|
||||
summary: StravaSummaryActivity;
|
||||
detail?: StravaDetailedActivity | null;
|
||||
}
|
||||
|
||||
const activities: readonly ActivityCase[] = activitiesFixture;
|
||||
|
||||
/** The golden is written in the repository's JSON format, so the format check passes on it unchanged. */
|
||||
async function golden(value: unknown): Promise<string> {
|
||||
return format(JSON.stringify(value), { parser: "json" });
|
||||
}
|
||||
|
||||
describe("Strava mappers against the fixture payloads", () => {
|
||||
it("maps activities", async () => {
|
||||
const output = activities.map(({ label, summary, detail }) => ({
|
||||
label,
|
||||
summaryHasHeartRate: summaryHasHeartRate(summary),
|
||||
row: mapActivity(summary, detail),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-activities.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves sport labels", async () => {
|
||||
const labels: Array<string | null | undefined> = [
|
||||
"Run",
|
||||
"TrailRun",
|
||||
"VirtualRide",
|
||||
"Virtual Ride",
|
||||
"virtual_ride",
|
||||
" WeightTraining ",
|
||||
"HighIntensityIntervalTraining",
|
||||
"Snowshoe",
|
||||
"Workout",
|
||||
"SomethingStravaAddedLater",
|
||||
"",
|
||||
" ",
|
||||
null,
|
||||
undefined,
|
||||
];
|
||||
const output = labels.map((raw) => ({
|
||||
raw: raw === undefined ? "(undefined)" : raw,
|
||||
sportType: mapStravaSportType(raw),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-sport.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("derives the wait after a rate limit", async () => {
|
||||
const cases: Array<{
|
||||
label: string;
|
||||
headers: Record<string, string>;
|
||||
now: string;
|
||||
}> = [
|
||||
{ label: "no headers", headers: {}, now: "2026-03-02T10:07:30.000Z" },
|
||||
{
|
||||
label: "retry-after seconds",
|
||||
headers: { "retry-after": "120" },
|
||||
now: "2026-03-02T10:07:30.000Z",
|
||||
},
|
||||
{
|
||||
label: "retry-after HTTP date",
|
||||
headers: { "retry-after": "Mon, 02 Mar 2026 10:10:00 GMT" },
|
||||
now: "2026-03-02T10:07:30.000Z",
|
||||
},
|
||||
{
|
||||
label: "retry-after zero falls through",
|
||||
headers: { "retry-after": "0" },
|
||||
now: "2026-03-02T10:07:30.000Z",
|
||||
},
|
||||
{
|
||||
label: "15-minute window spent",
|
||||
headers: {
|
||||
"x-ratelimit-limit": "200,2000",
|
||||
"x-ratelimit-usage": "200,450",
|
||||
},
|
||||
now: "2026-03-02T10:07:30.000Z",
|
||||
},
|
||||
{
|
||||
label: "daily window spent",
|
||||
headers: {
|
||||
"x-ratelimit-limit": "200,2000",
|
||||
"x-ratelimit-usage": "120,2000",
|
||||
},
|
||||
now: "2026-03-02T10:07:30.000Z",
|
||||
},
|
||||
{
|
||||
label: "daily read window spent",
|
||||
headers: {
|
||||
"x-ratelimit-limit": "200,2000",
|
||||
"x-ratelimit-usage": "40,500",
|
||||
"x-readratelimit-limit": "100,1000",
|
||||
"x-readratelimit-usage": "30,1001",
|
||||
},
|
||||
now: "2026-03-02T10:07:30.000Z",
|
||||
},
|
||||
{
|
||||
label: "malformed pair",
|
||||
headers: {
|
||||
"x-ratelimit-limit": "200",
|
||||
"x-ratelimit-usage": "abc,2000",
|
||||
},
|
||||
now: "2026-03-02T10:07:30.000Z",
|
||||
},
|
||||
{
|
||||
label: "exactly on a quarter hour",
|
||||
headers: {},
|
||||
now: "2026-03-02T10:15:00.000Z",
|
||||
},
|
||||
{
|
||||
label: "half a second before midnight, daily spent",
|
||||
headers: {
|
||||
"x-ratelimit-limit": "200,2000",
|
||||
"x-ratelimit-usage": "10,2500",
|
||||
},
|
||||
now: "2026-03-02T23:59:59.500Z",
|
||||
},
|
||||
];
|
||||
const output = cases.map(({ label, headers, now }) => ({
|
||||
label,
|
||||
seconds: stravaRateLimitRetryAfterSeconds(
|
||||
new Headers(headers),
|
||||
new Date(now),
|
||||
),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-rate-limit.json",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
[
|
||||
{ "height_meter": 1.8, "weight_kilogram": 78.456, "max_heart_rate": 192 },
|
||||
{ "height_meter": 1.655, "weight_kilogram": 62.3, "max_heart_rate": 187.6 },
|
||||
{},
|
||||
{ "weight_kilogram": 90 },
|
||||
{ "height_meter": 0, "weight_kilogram": 0, "max_heart_rate": 0 },
|
||||
{ "height_meter": -1.7, "weight_kilogram": -70, "max_heart_rate": -180 },
|
||||
{ "height_meter": 1.72345, "max_heart_rate": 175.4 }
|
||||
]
|
||||
@@ -0,0 +1,71 @@
|
||||
[
|
||||
{
|
||||
"id": 900001,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-01T22:30:00.000Z",
|
||||
"updated_at": "2026-03-02T22:10:00.000Z",
|
||||
"start": "2026-03-01T22:30:00.000Z",
|
||||
"end": "2026-03-02T22:05:00.000Z",
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"strain": 12.4567,
|
||||
"kilojoule": 9876.543,
|
||||
"average_heart_rate": 68.4,
|
||||
"max_heart_rate": 171.6
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 900002,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-02T22:05:00.000Z",
|
||||
"updated_at": "2026-03-03T09:00:00.000Z",
|
||||
"start": "2026-03-02T22:05:00.000Z",
|
||||
"end": null,
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"strain": 3.1,
|
||||
"kilojoule": 2500,
|
||||
"average_heart_rate": 61.5,
|
||||
"max_heart_rate": 120.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 900003,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-03T22:00:00.000Z",
|
||||
"updated_at": "2026-03-03T22:00:00.000Z",
|
||||
"start": "2026-03-03T22:00:00.000Z",
|
||||
"score_state": "PENDING_SCORE",
|
||||
"score": null
|
||||
},
|
||||
{
|
||||
"id": 900004,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-04T23:15:00.000+01:00",
|
||||
"updated_at": "2026-03-05T23:00:00.000+01:00",
|
||||
"start": "2026-03-04T23:15:00.000+01:00",
|
||||
"end": "2026-03-05T22:50:00.000+01:00",
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"strain": 0,
|
||||
"kilojoule": 0,
|
||||
"average_heart_rate": 0,
|
||||
"max_heart_rate": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 900005,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-05T21:00:00.000-05:00",
|
||||
"updated_at": "2026-03-06T21:00:00.000-05:00",
|
||||
"start": "2026-03-05T21:00:00.000-05:00",
|
||||
"end": "2026-03-06T20:45:00.000-05:00",
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"strain": 21,
|
||||
"kilojoule": 18000.005,
|
||||
"average_heart_rate": 95,
|
||||
"max_heart_rate": 198
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"cycle_id": 900001,
|
||||
"sleep_id": "sleep-0001",
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-02T06:40:00.000Z",
|
||||
"updated_at": "2026-03-02T06:45:00.000Z",
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"user_calibrating": false,
|
||||
"recovery_score": 64,
|
||||
"resting_heart_rate": 54,
|
||||
"hrv_rmssd_milli": 48.2,
|
||||
"spo2_percentage": 96.5,
|
||||
"skin_temp_celsius": 33.4
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle_id": 900002,
|
||||
"sleep_id": "sleep-0002",
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-03T06:40:00.000Z",
|
||||
"updated_at": "2026-03-03T06:41:00.000Z",
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"user_calibrating": false,
|
||||
"recovery_score": 81,
|
||||
"resting_heart_rate": 50,
|
||||
"hrv_rmssd_milli": 61.73456
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle_id": 900003,
|
||||
"sleep_id": "sleep-0003",
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-04T06:40:00.000Z",
|
||||
"updated_at": "2026-03-04T06:40:00.000Z",
|
||||
"score_state": "PENDING_SCORE",
|
||||
"score": null
|
||||
},
|
||||
{
|
||||
"cycle_id": 900004,
|
||||
"sleep_id": "sleep-0004",
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-05T06:40:00.000Z",
|
||||
"updated_at": "2026-03-05T06:40:00.000Z",
|
||||
"score_state": "UNSCORABLE",
|
||||
"score": null
|
||||
},
|
||||
{
|
||||
"cycle_id": 900005,
|
||||
"sleep_id": "sleep-0005",
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-06T05:00:00.000Z",
|
||||
"updated_at": "2026-03-06T07:15:30.000+02:00",
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"user_calibrating": true,
|
||||
"recovery_score": 0,
|
||||
"resting_heart_rate": 0,
|
||||
"hrv_rmssd_milli": 0,
|
||||
"spo2_percentage": 0,
|
||||
"skin_temp_celsius": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle_id": 900006,
|
||||
"sleep_id": "sleep-0006",
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-07T06:40:00.000Z",
|
||||
"updated_at": "2026-03-07T01:30:00.000-05:00",
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"user_calibrating": false,
|
||||
"recovery_score": 100,
|
||||
"resting_heart_rate": 41.666,
|
||||
"hrv_rmssd_milli": 132.005,
|
||||
"skin_temp_celsius": 34.125
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,139 @@
|
||||
[
|
||||
{
|
||||
"id": "sleep-0001",
|
||||
"cycle_id": 900001,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-02T06:40:00.000Z",
|
||||
"updated_at": "2026-03-02T06:45:00.000Z",
|
||||
"start": "2026-03-01T22:30:00.000Z",
|
||||
"end": "2026-03-02T06:30:00.000Z",
|
||||
"nap": false,
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"stage_summary": {
|
||||
"total_in_bed_time_milli": 28800000,
|
||||
"total_awake_time_milli": 1800000,
|
||||
"total_light_sleep_time_milli": 14400000,
|
||||
"total_slow_wave_sleep_time_milli": 5400000,
|
||||
"total_rem_sleep_time_milli": 7200000,
|
||||
"disturbance_count": 7
|
||||
},
|
||||
"sleep_needed": {
|
||||
"baseline_milli": 27000000,
|
||||
"need_from_sleep_debt_milli": 1800000,
|
||||
"need_from_recent_strain_milli": 900000,
|
||||
"need_from_recent_nap_milli": -600000
|
||||
},
|
||||
"respiratory_rate": 15.234,
|
||||
"sleep_performance_percentage": 88,
|
||||
"sleep_consistency_percentage": 72,
|
||||
"sleep_efficiency_percentage": 93.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sleep-0002",
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-02T14:50:00.000Z",
|
||||
"updated_at": "2026-03-02T14:52:00.000Z",
|
||||
"start": "2026-03-02T14:00:00.000Z",
|
||||
"end": "2026-03-02T14:45:00.000Z",
|
||||
"nap": true,
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"stage_summary": {
|
||||
"total_in_bed_time_milli": 2700000,
|
||||
"total_awake_time_milli": 0,
|
||||
"total_light_sleep_time_milli": 2100000,
|
||||
"total_slow_wave_sleep_time_milli": 600000,
|
||||
"total_rem_sleep_time_milli": 0
|
||||
},
|
||||
"sleep_needed": {
|
||||
"baseline_milli": 0,
|
||||
"need_from_sleep_debt_milli": 0,
|
||||
"need_from_recent_strain_milli": 0,
|
||||
"need_from_recent_nap_milli": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sleep-0003",
|
||||
"cycle_id": 900003,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-04T06:40:00.000Z",
|
||||
"updated_at": "2026-03-04T06:40:00.000Z",
|
||||
"start": "2026-03-03T23:00:00.000Z",
|
||||
"end": "2026-03-04T06:30:00.000Z",
|
||||
"nap": false,
|
||||
"score_state": "PENDING_SCORE",
|
||||
"score": null
|
||||
},
|
||||
{
|
||||
"id": "sleep-0004",
|
||||
"cycle_id": 900004,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-05T07:10:00.000+01:00",
|
||||
"updated_at": "2026-03-05T07:12:00.000+01:00",
|
||||
"start": "2026-03-04T23:40:00.000+01:00",
|
||||
"end": "2026-03-05T07:05:00.000+01:00",
|
||||
"nap": false,
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"stage_summary": {
|
||||
"total_in_bed_time_milli": 26700000,
|
||||
"total_awake_time_milli": 2345678,
|
||||
"total_light_sleep_time_milli": 12345678,
|
||||
"total_slow_wave_sleep_time_milli": 4567890,
|
||||
"total_rem_sleep_time_milli": 6543210,
|
||||
"disturbance_count": 0
|
||||
},
|
||||
"sleep_needed": {
|
||||
"baseline_milli": 28123456,
|
||||
"need_from_sleep_debt_milli": 0,
|
||||
"need_from_recent_strain_milli": 456789,
|
||||
"need_from_recent_nap_milli": 0
|
||||
},
|
||||
"respiratory_rate": 0,
|
||||
"sleep_performance_percentage": 0,
|
||||
"sleep_efficiency_percentage": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sleep-0005",
|
||||
"cycle_id": 900005,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-06T06:00:00.000-05:00",
|
||||
"updated_at": "2026-03-06T06:05:00.000-05:00",
|
||||
"start": "2026-03-05T22:15:00.000-05:00",
|
||||
"end": "2026-03-06T05:45:00.000-05:00",
|
||||
"nap": false,
|
||||
"score_state": "SCORED",
|
||||
"score": {
|
||||
"stage_summary": {
|
||||
"total_in_bed_time_milli": 0,
|
||||
"total_awake_time_milli": 0,
|
||||
"total_light_sleep_time_milli": 0,
|
||||
"total_slow_wave_sleep_time_milli": 0,
|
||||
"total_rem_sleep_time_milli": 0
|
||||
},
|
||||
"sleep_needed": {
|
||||
"baseline_milli": 25200000,
|
||||
"need_from_sleep_debt_milli": 3600000,
|
||||
"need_from_recent_strain_milli": 0,
|
||||
"need_from_recent_nap_milli": 0
|
||||
},
|
||||
"sleep_consistency_percentage": 55
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sleep-0006",
|
||||
"cycle_id": 900006,
|
||||
"user_id": 10001,
|
||||
"created_at": "2026-03-07T08:00:00.000Z",
|
||||
"updated_at": "2026-03-07T08:00:00.000Z",
|
||||
"start": "2026-03-07T00:10:00.000Z",
|
||||
"end": "2026-03-07T07:50:00.000Z",
|
||||
"nap": false,
|
||||
"score_state": "UNSCORABLE",
|
||||
"score": null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"input": {
|
||||
"height_meter": 1.8,
|
||||
"weight_kilogram": 78.456,
|
||||
"max_heart_rate": 192
|
||||
},
|
||||
"mapped": { "weightKg": 78.46, "maxHeartRate": 192, "heightCm": 180 }
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"height_meter": 1.655,
|
||||
"weight_kilogram": 62.3,
|
||||
"max_heart_rate": 187.6
|
||||
},
|
||||
"mapped": { "weightKg": 62.3, "maxHeartRate": 188, "heightCm": 165.5 }
|
||||
},
|
||||
{
|
||||
"input": {},
|
||||
"mapped": { "weightKg": null, "maxHeartRate": null, "heightCm": null }
|
||||
},
|
||||
{
|
||||
"input": { "weight_kilogram": 90 },
|
||||
"mapped": { "weightKg": 90, "maxHeartRate": null, "heightCm": null }
|
||||
},
|
||||
{
|
||||
"input": { "height_meter": 0, "weight_kilogram": 0, "max_heart_rate": 0 },
|
||||
"mapped": { "weightKg": null, "maxHeartRate": null, "heightCm": null }
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"height_meter": -1.7,
|
||||
"weight_kilogram": -70,
|
||||
"max_heart_rate": -180
|
||||
},
|
||||
"mapped": { "weightKg": null, "maxHeartRate": null, "heightCm": null }
|
||||
},
|
||||
{
|
||||
"input": { "height_meter": 1.72345, "max_heart_rate": 175.4 },
|
||||
"mapped": { "weightKg": null, "maxHeartRate": 175, "heightCm": 172.35 }
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,135 @@
|
||||
[
|
||||
{
|
||||
"id": 900001,
|
||||
"rows": [
|
||||
{
|
||||
"type": "DAY_STRAIN",
|
||||
"value": 12.46,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-01T22:30:00.000Z",
|
||||
"fieldTag": "day_strain"
|
||||
},
|
||||
{
|
||||
"type": "ENERGY_EXPENDITURE_KJ",
|
||||
"value": 9876.54,
|
||||
"unit": "kJ",
|
||||
"measuredAt": "2026-03-01T22:30:00.000Z",
|
||||
"fieldTag": "energy_kj"
|
||||
},
|
||||
{
|
||||
"type": "AVERAGE_HEART_RATE",
|
||||
"value": 68,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-01T22:30:00.000Z",
|
||||
"fieldTag": "avg_hr"
|
||||
},
|
||||
{
|
||||
"type": "MAX_HEART_RATE",
|
||||
"value": 172,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-01T22:30:00.000Z",
|
||||
"fieldTag": "max_hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 900002,
|
||||
"rows": [
|
||||
{
|
||||
"type": "DAY_STRAIN",
|
||||
"value": 3.1,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T22:05:00.000Z",
|
||||
"fieldTag": "day_strain"
|
||||
},
|
||||
{
|
||||
"type": "ENERGY_EXPENDITURE_KJ",
|
||||
"value": 2500,
|
||||
"unit": "kJ",
|
||||
"measuredAt": "2026-03-02T22:05:00.000Z",
|
||||
"fieldTag": "energy_kj"
|
||||
},
|
||||
{
|
||||
"type": "AVERAGE_HEART_RATE",
|
||||
"value": 62,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T22:05:00.000Z",
|
||||
"fieldTag": "avg_hr"
|
||||
},
|
||||
{
|
||||
"type": "MAX_HEART_RATE",
|
||||
"value": 121,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T22:05:00.000Z",
|
||||
"fieldTag": "max_hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": 900003, "rows": [] },
|
||||
{
|
||||
"id": 900004,
|
||||
"rows": [
|
||||
{
|
||||
"type": "DAY_STRAIN",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-04T22:15:00.000Z",
|
||||
"fieldTag": "day_strain"
|
||||
},
|
||||
{
|
||||
"type": "ENERGY_EXPENDITURE_KJ",
|
||||
"value": 0,
|
||||
"unit": "kJ",
|
||||
"measuredAt": "2026-03-04T22:15:00.000Z",
|
||||
"fieldTag": "energy_kj"
|
||||
},
|
||||
{
|
||||
"type": "AVERAGE_HEART_RATE",
|
||||
"value": 0,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-04T22:15:00.000Z",
|
||||
"fieldTag": "avg_hr"
|
||||
},
|
||||
{
|
||||
"type": "MAX_HEART_RATE",
|
||||
"value": 0,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-04T22:15:00.000Z",
|
||||
"fieldTag": "max_hr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 900005,
|
||||
"rows": [
|
||||
{
|
||||
"type": "DAY_STRAIN",
|
||||
"value": 21,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-06T02:00:00.000Z",
|
||||
"fieldTag": "day_strain"
|
||||
},
|
||||
{
|
||||
"type": "ENERGY_EXPENDITURE_KJ",
|
||||
"value": 18000.01,
|
||||
"unit": "kJ",
|
||||
"measuredAt": "2026-03-06T02:00:00.000Z",
|
||||
"fieldTag": "energy_kj"
|
||||
},
|
||||
{
|
||||
"type": "AVERAGE_HEART_RATE",
|
||||
"value": 95,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-06T02:00:00.000Z",
|
||||
"fieldTag": "avg_hr"
|
||||
},
|
||||
{
|
||||
"type": "MAX_HEART_RATE",
|
||||
"value": 198,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-06T02:00:00.000Z",
|
||||
"fieldTag": "max_hr"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,143 @@
|
||||
[
|
||||
{
|
||||
"sleep_id": "sleep-0001",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 64,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-02T06:45:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 48.2,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-02T06:45:00.000Z",
|
||||
"fieldTag": "hrv_rmssd"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 54,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-02T06:45:00.000Z",
|
||||
"fieldTag": "rhr"
|
||||
},
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 96.5,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T06:45:00.000Z",
|
||||
"fieldTag": "spo2"
|
||||
},
|
||||
{
|
||||
"type": "SKIN_TEMPERATURE",
|
||||
"value": 33.4,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-02T06:45:00.000Z",
|
||||
"fieldTag": "skin_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sleep_id": "sleep-0002",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 81,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-03T06:41:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 61.73,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-03T06:41:00.000Z",
|
||||
"fieldTag": "hrv_rmssd"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 50,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-03T06:41:00.000Z",
|
||||
"fieldTag": "rhr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "sleep_id": "sleep-0003", "rows": [] },
|
||||
{ "sleep_id": "sleep-0004", "rows": [] },
|
||||
{
|
||||
"sleep_id": "sleep-0005",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 0,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-06T05:15:30.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 0,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-06T05:15:30.000Z",
|
||||
"fieldTag": "hrv_rmssd"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 0,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-06T05:15:30.000Z",
|
||||
"fieldTag": "rhr"
|
||||
},
|
||||
{
|
||||
"type": "OXYGEN_SATURATION",
|
||||
"value": 0,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-06T05:15:30.000Z",
|
||||
"fieldTag": "spo2"
|
||||
},
|
||||
{
|
||||
"type": "SKIN_TEMPERATURE",
|
||||
"value": 0,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-06T05:15:30.000Z",
|
||||
"fieldTag": "skin_temp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sleep_id": "sleep-0006",
|
||||
"rows": [
|
||||
{
|
||||
"type": "RECOVERY_SCORE",
|
||||
"value": 100,
|
||||
"unit": "score",
|
||||
"measuredAt": "2026-03-07T06:30:00.000Z",
|
||||
"fieldTag": "recovery"
|
||||
},
|
||||
{
|
||||
"type": "HRV_RMSSD",
|
||||
"value": 132.01,
|
||||
"unit": "ms",
|
||||
"measuredAt": "2026-03-07T06:30:00.000Z",
|
||||
"fieldTag": "hrv_rmssd"
|
||||
},
|
||||
{
|
||||
"type": "RESTING_HEART_RATE",
|
||||
"value": 41.67,
|
||||
"unit": "bpm",
|
||||
"measuredAt": "2026-03-07T06:30:00.000Z",
|
||||
"fieldTag": "rhr"
|
||||
},
|
||||
{
|
||||
"type": "SKIN_TEMPERATURE",
|
||||
"value": 34.13,
|
||||
"unit": "celsius",
|
||||
"measuredAt": "2026-03-07T06:30:00.000Z",
|
||||
"fieldTag": "skin_temp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,246 @@
|
||||
[
|
||||
{
|
||||
"id": "sleep-0001",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 30,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-01T23:00:00.000Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"externalId": "sleep-0001:seg:sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 240,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T03:00:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep-0001:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 90,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T04:30:00.000Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep-0001:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 120,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:30:00.000Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep-0001:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 480,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:30:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_NEED",
|
||||
"value": 485,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T06:30:00.000Z",
|
||||
"fieldTag": "sleep_need"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 88,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T06:30:00.000Z",
|
||||
"fieldTag": "sleep_perf"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_EFFICIENCY",
|
||||
"value": 93.75,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T06:30:00.000Z",
|
||||
"fieldTag": "sleep_eff"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_CONSISTENCY",
|
||||
"value": 72,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-02T06:30:00.000Z",
|
||||
"fieldTag": "sleep_consistency"
|
||||
},
|
||||
{
|
||||
"type": "RESPIRATORY_RATE",
|
||||
"value": 15.23,
|
||||
"unit": "breaths/min",
|
||||
"measuredAt": "2026-03-02T06:30:00.000Z",
|
||||
"fieldTag": "resp_rate"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DISTURBANCE_COUNT",
|
||||
"value": 7,
|
||||
"unit": "count",
|
||||
"measuredAt": "2026-03-02T06:30:00.000Z",
|
||||
"fieldTag": "disturbances"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sleep-0002",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 35,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T14:35:00.000Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep-0002:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 10,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T14:45:00.000Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep-0002:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 45,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T14:45:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_NEED",
|
||||
"value": 0,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-02T14:45:00.000Z",
|
||||
"fieldTag": "sleep_need"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "sleep-0003", "rows": [] },
|
||||
{
|
||||
"id": "sleep-0004",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 39.09,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-04T23:19:05.678Z",
|
||||
"fieldTag": "sleep_awake",
|
||||
"externalId": "sleep-0004:seg:sleep_awake",
|
||||
"sleepStage": "AWAKE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 205.76,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T02:44:51.356Z",
|
||||
"fieldTag": "sleep_core",
|
||||
"externalId": "sleep-0004:seg:sleep_core",
|
||||
"sleepStage": "CORE",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 76.13,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T04:00:59.246Z",
|
||||
"fieldTag": "sleep_deep",
|
||||
"externalId": "sleep-0004:seg:sleep_deep",
|
||||
"sleepStage": "DEEP",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 109.05,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T05:50:02.456Z",
|
||||
"fieldTag": "sleep_rem",
|
||||
"externalId": "sleep-0004:seg:sleep_rem",
|
||||
"sleepStage": "REM",
|
||||
"reconstructed": true
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DURATION",
|
||||
"value": 445,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T06:05:00.000Z",
|
||||
"fieldTag": "sleep_in_bed",
|
||||
"sleepStage": "IN_BED"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_NEED",
|
||||
"value": 476.34,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-05T06:05:00.000Z",
|
||||
"fieldTag": "sleep_need"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_PERFORMANCE",
|
||||
"value": 0,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-05T06:05:00.000Z",
|
||||
"fieldTag": "sleep_perf"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_EFFICIENCY",
|
||||
"value": 100,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-05T06:05:00.000Z",
|
||||
"fieldTag": "sleep_eff"
|
||||
},
|
||||
{
|
||||
"type": "RESPIRATORY_RATE",
|
||||
"value": 0,
|
||||
"unit": "breaths/min",
|
||||
"measuredAt": "2026-03-05T06:05:00.000Z",
|
||||
"fieldTag": "resp_rate"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_DISTURBANCE_COUNT",
|
||||
"value": 0,
|
||||
"unit": "count",
|
||||
"measuredAt": "2026-03-05T06:05:00.000Z",
|
||||
"fieldTag": "disturbances"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sleep-0005",
|
||||
"rows": [
|
||||
{
|
||||
"type": "SLEEP_NEED",
|
||||
"value": 480,
|
||||
"unit": "minutes",
|
||||
"measuredAt": "2026-03-06T10:45:00.000Z",
|
||||
"fieldTag": "sleep_need"
|
||||
},
|
||||
{
|
||||
"type": "SLEEP_CONSISTENCY",
|
||||
"value": 55,
|
||||
"unit": "%",
|
||||
"measuredAt": "2026-03-06T10:45:00.000Z",
|
||||
"fieldTag": "sleep_consistency"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "sleep-0006", "rows": [] }
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
[
|
||||
{ "sportId": 0, "sportName": "Running", "sportType": "running" },
|
||||
{ "sportId": 1, "sportName": null, "sportType": "cycling" },
|
||||
{
|
||||
"sportId": 52,
|
||||
"sportName": "ignored when the id resolves",
|
||||
"sportType": "hiking"
|
||||
},
|
||||
{ "sportId": 9999, "sportName": "Hiking/Rucking", "sportType": "hiking" },
|
||||
{ "sportId": null, "sportName": "hiking_rucking", "sportType": "hiking" },
|
||||
{ "sportId": null, "sportName": " Track & Field ", "sportType": "running" },
|
||||
{
|
||||
"sportId": null,
|
||||
"sportName": "Functional Fitness",
|
||||
"sportType": "crossTraining"
|
||||
},
|
||||
{ "sportId": null, "sportName": "whoop_sport_63", "sportType": "other" },
|
||||
{
|
||||
"sportId": null,
|
||||
"sportName": "Underwater Basket Weaving",
|
||||
"sportType": "other"
|
||||
},
|
||||
{ "sportId": null, "sportName": " ", "sportType": "other" },
|
||||
{ "sportId": 9999, "sportName": null, "sportType": "other" },
|
||||
{ "sportId": -1, "sportName": null, "sportType": "other" },
|
||||
{ "sportId": null, "sportName": null, "sportType": "other" }
|
||||
]
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* What each WHOOP mapper makes of a fixed set of payloads, pinned in
|
||||
* `__golden__/`.
|
||||
*
|
||||
* The fixtures under `__fixtures__/` are synthetic payloads in the documented
|
||||
* v2 wire shape: scored and unscored records, naps, absent optional fields,
|
||||
* zero values, offsets other than UTC, and body values that must be refused.
|
||||
* The goldens were written while the mappers still lived in `client.ts`, so a
|
||||
* green run after they move proves the move changed nothing.
|
||||
*
|
||||
* The workout row is built inside `upsertWhoopWorkout`, next to the database
|
||||
* write, so only its pure sport lookup (`mapWhoopSportType`) is pinned here.
|
||||
*/
|
||||
import { format } from "prettier";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
mapBody,
|
||||
mapCycle,
|
||||
mapRecovery,
|
||||
mapSleep,
|
||||
type WhoopBodyMeasurement,
|
||||
type WhoopCycle,
|
||||
type WhoopRecovery,
|
||||
type WhoopSleep,
|
||||
} from "../client";
|
||||
import { mapWhoopSportType } from "../sport-map";
|
||||
import bodyFixture from "./__fixtures__/body.json";
|
||||
import cycleFixture from "./__fixtures__/cycle.json";
|
||||
import recoveryFixture from "./__fixtures__/recovery.json";
|
||||
import sleepFixture from "./__fixtures__/sleep.json";
|
||||
|
||||
const recoveries: readonly WhoopRecovery[] = recoveryFixture;
|
||||
const sleeps: readonly WhoopSleep[] = sleepFixture;
|
||||
const cycles: readonly WhoopCycle[] = cycleFixture;
|
||||
const bodies: readonly WhoopBodyMeasurement[] = bodyFixture;
|
||||
|
||||
/** The golden is written in the repository's JSON format, so the format check passes on it unchanged. */
|
||||
async function golden(value: unknown): Promise<string> {
|
||||
return format(JSON.stringify(value), { parser: "json" });
|
||||
}
|
||||
|
||||
describe("WHOOP mappers against the fixture payloads", () => {
|
||||
it("maps recoveries", async () => {
|
||||
const output = recoveries.map((record) => ({
|
||||
sleep_id: record.sleep_id,
|
||||
rows: mapRecovery(record),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-recovery.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps sleeps", async () => {
|
||||
const output = sleeps.map((record) => ({
|
||||
id: record.id,
|
||||
rows: mapSleep(record),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-sleep.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps cycles", async () => {
|
||||
const output = cycles.map((record) => ({
|
||||
id: record.id,
|
||||
rows: mapCycle(record),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-cycle.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps body measurements", async () => {
|
||||
const output = bodies.map((record) => ({
|
||||
input: record,
|
||||
mapped: mapBody(record),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-body.json",
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves workout sports", async () => {
|
||||
const inputs: Array<[number | undefined, string | undefined]> = [
|
||||
[0, "Running"],
|
||||
[1, undefined],
|
||||
[52, "ignored when the id resolves"],
|
||||
[9999, "Hiking/Rucking"],
|
||||
[undefined, "hiking_rucking"],
|
||||
[undefined, " Track & Field "],
|
||||
[undefined, "Functional Fitness"],
|
||||
[undefined, "whoop_sport_63"],
|
||||
[undefined, "Underwater Basket Weaving"],
|
||||
[undefined, " "],
|
||||
[9999, undefined],
|
||||
[-1, undefined],
|
||||
[undefined, undefined],
|
||||
];
|
||||
const output = inputs.map(([sportId, sportName]) => ({
|
||||
sportId: sportId ?? null,
|
||||
sportName: sportName ?? null,
|
||||
sportType: mapWhoopSportType(sportId, sportName),
|
||||
}));
|
||||
await expect(await golden(output)).toMatchFileSnapshot(
|
||||
"./__golden__/map-sport.json",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user