Skip to content

Commit

Permalink
Merge pull request #10 from caarmen/support-parsing-fitbit-classic-data
Browse files Browse the repository at this point in the history
Support parsing fitbit classic data
  • Loading branch information
caarmen authored May 18, 2023
2 parents 5c30adb + 8003b25 commit 4f589c0
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 10 deletions.
145 changes: 145 additions & 0 deletions tests/data/fitbit_sleep_response_classic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{
"sleep": [
{
"dateOfSleep": "2023-05-17",
"duration": 27120000,
"efficiency": 97,
"endTime": "2023-05-17T08:07:30.000",
"infoCode": 1,
"isMainSleep": true,
"levels": {
"data": [
{
"dateTime": "2023-05-17T00:35:00.000",
"level": "awake",
"seconds": 120
},
{
"dateTime": "2023-05-17T00:37:00.000",
"level": "restless",
"seconds": 60
},
{
"dateTime": "2023-05-17T00:38:00.000",
"level": "asleep",
"seconds": 9900
},
{
"dateTime": "2023-05-17T03:23:00.000",
"level": "restless",
"seconds": 120
},
{
"dateTime": "2023-05-17T03:25:00.000",
"level": "asleep",
"seconds": 120
},
{
"dateTime": "2023-05-17T03:27:00.000",
"level": "restless",
"seconds": 60
},
{
"dateTime": "2023-05-17T03:28:00.000",
"level": "asleep",
"seconds": 2280
},
{
"dateTime": "2023-05-17T04:06:00.000",
"level": "restless",
"seconds": 60
},
{
"dateTime": "2023-05-17T04:07:00.000",
"level": "asleep",
"seconds": 2820
},
{
"dateTime": "2023-05-17T04:54:00.000",
"level": "restless",
"seconds": 60
},
{
"dateTime": "2023-05-17T04:55:00.000",
"level": "asleep",
"seconds": 4320
},
{
"dateTime": "2023-05-17T06:07:00.000",
"level": "restless",
"seconds": 60
},
{
"dateTime": "2023-05-17T06:08:00.000",
"level": "asleep",
"seconds": 900
},
{
"dateTime": "2023-05-17T06:23:00.000",
"level": "restless",
"seconds": 120
},
{
"dateTime": "2023-05-17T06:25:00.000",
"level": "asleep",
"seconds": 360
},
{
"dateTime": "2023-05-17T06:31:00.000",
"level": "restless",
"seconds": 60
},
{
"dateTime": "2023-05-17T06:32:00.000",
"level": "asleep",
"seconds": 180
},
{
"dateTime": "2023-05-17T06:35:00.000",
"level": "restless",
"seconds": 60
},
{
"dateTime": "2023-05-17T06:36:00.000",
"level": "asleep",
"seconds": 5460
}
],
"summary": {
"asleep": {
"count": 0,
"minutes": 439
},
"awake": {
"count": 1,
"minutes": 2
},
"restless": {
"count": 9,
"minutes": 11
}
}
},
"logId": 41366101052,
"logType": "auto_detected",
"minutesAfterWakeup": 0,
"minutesAsleep": 439,
"minutesAwake": 13,
"minutesToFallAsleep": 0,
"startTime": "2023-05-17T00:35:00.000",
"timeInBed": 452,
"type": "classic"
}
],
"summary": {
"stages": {
"deep": 0,
"light": 0,
"rem": 0,
"wake": 0
},
"totalMinutesAsleep": 439,
"totalSleepRecords": 1,
"totalTimeInBed": 452
}
}
11 changes: 11 additions & 0 deletions tests/services/fitbit/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
slack_alias="somebody",
),
),
(
"fitbit_sleep_response_classic.json",
SleepData(
start_time=datetime.datetime(2023, 5, 17, 0, 35, 0),
end_time=datetime.datetime(2023, 5, 17, 8, 7, 30),
sleep_minutes=439,
wake_minutes=2,
score=97,
slack_alias="somebody",
),
),
("fitbit_sleep_response_no_main_sleep_item.json", None),
],
)
Expand Down
52 changes: 42 additions & 10 deletions withingsslack/services/fitbit/parser.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import json
from typing import Optional, Self
from typing import Annotated, Literal, Optional, Self, Union
from withingsslack.services import models as svc_models
from pydantic import BaseModel
from pydantic import BaseModel, Field
import datetime


class FitbitSleepItemSummaryItem(BaseModel):
minutes: int


class FitbitSleepItemSummary(BaseModel):
class FitbitClassicSleepItemSummary(BaseModel):
awake: FitbitSleepItemSummaryItem
asleep: FitbitSleepItemSummaryItem


class FitbitStagesSleepItemSummary(BaseModel):
wake: FitbitSleepItemSummaryItem


class FitbitSleepItemLevels(BaseModel):
summary: FitbitSleepItemSummary
class FitbitStagesSleepItemLevels(BaseModel):
summary: FitbitStagesSleepItemSummary


class FitbitClassicSleepItemLevels(BaseModel):
summary: FitbitClassicSleepItemSummary


class FitbitSleepItem(BaseModel):
Expand All @@ -23,11 +32,25 @@ class FitbitSleepItem(BaseModel):
endTime: str
isMainSleep: bool
startTime: str
levels: FitbitSleepItemLevels


class FitbitClassicSleepItem(FitbitSleepItem):
type: Literal["classic"]
levels: FitbitClassicSleepItemLevels


class FitbitStagesSleepItem(FitbitSleepItem):
type: Literal["stages"]
levels: FitbitStagesSleepItemLevels


class FitbitSleep(BaseModel):
sleep: list[FitbitSleepItem]
sleep: list[
Annotated[
Union[FitbitClassicSleepItem, FitbitStagesSleepItem],
Field(discriminator="type"),
]
]

@classmethod
def parse(cls, input: str) -> Self:
Expand All @@ -45,14 +68,23 @@ def parse_sleep(input: str, slack_alias: str) -> Optional[svc_models.SleepData]:
if not main_sleep_item:
return None

wake_minutes = (
main_sleep_item.levels.summary.awake.minutes
if main_sleep_item.type == "classic"
else main_sleep_item.levels.summary.wake.minutes
)
asleep_minutes = (
main_sleep_item.levels.summary.asleep.minutes
if main_sleep_item.type == "classic"
else main_sleep_item.duration / 60000 - wake_minutes
)
return svc_models.SleepData(
start_time=datetime.datetime.strptime(
main_sleep_item.startTime, DATETIME_FORMAT
),
end_time=datetime.datetime.strptime(main_sleep_item.endTime, DATETIME_FORMAT),
score=main_sleep_item.efficiency,
sleep_minutes=main_sleep_item.duration / 60000
- main_sleep_item.levels.summary.wake.minutes,
wake_minutes=main_sleep_item.levels.summary.wake.minutes,
sleep_minutes=asleep_minutes,
wake_minutes=wake_minutes,
slack_alias=slack_alias,
)

0 comments on commit 4f589c0

Please sign in to comment.