-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Revert "Revert "Merge pull request #135 from cheejung/main""
This reverts commit f3c69a2.
- Loading branch information
1 parent
f3c69a2
commit 7517886
Showing
11 changed files
with
2,395 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.DS_Store | ||
.project | ||
*.pyc |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from datetime import datetime, timedelta | ||
|
||
import pytest | ||
|
||
import wearipedia | ||
|
||
data_formats = { | ||
"bpm": (int, float), | ||
"brpm": (int, float), | ||
"hrv": (int, float), | ||
"spo2": (int, float), | ||
"rest_cals": (int, float), | ||
"work_cals": (int, float), | ||
"active_cals": (int, float), | ||
"step_cals": (int, float), | ||
"total_cals": (int, float), | ||
"steps": (int, float), | ||
"distance": (int, float), | ||
} | ||
|
||
|
||
@pytest.mark.parametrize("real", [True, False]) | ||
def test_evo(real): | ||
start_synthetic = datetime(2023, 6, 5) | ||
end_synthetic = datetime(2023, 6, 20, 23, 59, 59) | ||
|
||
device = wearipedia.get_device( | ||
"biostrap/evo", | ||
start_date=datetime.strftime(start_synthetic, "%Y-%m-%d"), | ||
end_date=datetime.strftime(end_synthetic, "%Y-%m-%d"), | ||
) | ||
if real: | ||
wearipedia._authenticate_device("evo", device) | ||
|
||
for data_type, data_format in data_formats.items(): | ||
data = device.get_data(data_type) | ||
|
||
# Checks specific to date-keyed data | ||
if data_type in [ | ||
"rest_cals", | ||
"work_cals", | ||
"active_cals", | ||
"step_cals", | ||
"total_cals", | ||
]: | ||
dates = list(data.keys()) | ||
|
||
# Check dates are consecutive and within the range | ||
expected_date = start_synthetic | ||
for date_str in dates: | ||
date = datetime.strptime(date_str, "%Y-%m-%d") | ||
assert ( | ||
date == expected_date | ||
), f"Expected date {expected_date}, but got {date}" | ||
expected_date += timedelta(days=1) | ||
|
||
# Checks specific to datetime-keyed data | ||
elif data_type in ["steps", "distance"]: | ||
|
||
datetimes = [ | ||
datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S") for dt_str in data.keys() | ||
] | ||
|
||
# Check that datetimes are within the specified range. | ||
for dt in datetimes: | ||
assert ( | ||
start_synthetic <= dt <= end_synthetic | ||
), f"Datetime {dt} out of range" | ||
|
||
# Check that the datetimes are sequential and increase by a minute. | ||
datetimes.sort() | ||
for i in range(1, len(datetimes)): | ||
expected_dt = datetimes[i - 1] + timedelta(minutes=1) | ||
assert ( | ||
datetimes[i] == expected_dt | ||
), f"Expected datetime {expected_dt}, but got {datetimes[i]}" | ||
else: | ||
datetimes = [key[0] for key in data.keys()] | ||
|
||
# Check datetimes are within the range | ||
for datetime_str in datetimes: | ||
dt = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S") | ||
assert ( | ||
start_synthetic <= dt <= end_synthetic | ||
), f"Datetime {dt} out of range" | ||
|
||
# Check data values are of expected type | ||
for value in data.values(): | ||
assert isinstance( | ||
value, data_format | ||
), f"{data_type} data {value} is not a {data_format.__name__}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .evo import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import os | ||
import pickle | ||
import webbrowser | ||
from datetime import datetime | ||
from urllib.parse import urlencode | ||
|
||
import requests | ||
|
||
from ...utils import seed_everything | ||
from ..device import BaseDevice | ||
from .evo_fetch import * | ||
from .evo_gen import * | ||
|
||
class_name = "EVO" | ||
|
||
|
||
class EVO(BaseDevice): | ||
def __init__(self, seed=0, start_date="2023-06-05", end_date="2023-06-20"): | ||
params = { | ||
"seed": seed, | ||
"start_date": str(start_date), | ||
"end_date": str(end_date), | ||
} | ||
|
||
self._initialize_device_params( | ||
[ | ||
"activities", | ||
"bpm", | ||
"brpm", | ||
"hrv", | ||
"spo2", | ||
"rest_cals", | ||
"work_cals", | ||
"active_cals", | ||
"step_cals", | ||
"total_cals", | ||
"sleep_session", | ||
"sleep_detail", | ||
"steps", | ||
"distance", | ||
], | ||
params, | ||
{ | ||
"seed": 0, | ||
"synthetic_start_date": "2023-06-05", | ||
"synthetic_end_date": "2023-06-20", | ||
}, | ||
) | ||
|
||
def _default_params(self): | ||
return { | ||
"start_date": self.init_params["synthetic_start_date"], | ||
"end_date": self.init_params["synthetic_end_date"], | ||
} | ||
|
||
def _get_real(self, data_type, params): | ||
return fetch_real_data( | ||
self.access_token, params["start_date"], params["end_date"], data_type | ||
) | ||
|
||
def _filter_synthetic(self, data, data_type, params): | ||
start_date = params["start_date"] | ||
end_date = params["end_date"] | ||
|
||
start_datetime = f"{start_date} 00:00:00" | ||
end_datetime = f"{end_date} 23:59:59" | ||
|
||
# For data types that are stored with a date string as a key | ||
if data_type in [ | ||
"rest_cals", | ||
"work_cals", | ||
"active_cals", | ||
"step_cals", | ||
"total_cals", | ||
]: | ||
return { | ||
date: value | ||
for date, value in data.items() | ||
if start_date <= date <= end_date | ||
} | ||
|
||
# For data types that are stored with a datetime string as a key in a tuple | ||
elif data_type in [ | ||
"bpm", | ||
"brpm", | ||
"spo2", | ||
"hrv", | ||
]: | ||
return { | ||
key: value | ||
for key, value in data.items() | ||
if start_datetime <= key[0] <= end_datetime | ||
} | ||
|
||
# For steps and distance that use datetime strings as keys (assuming distance also has datetime as per steps) | ||
elif data_type in ["steps", "distance"]: | ||
return { | ||
datetime: value | ||
for datetime, value in data.items() | ||
if start_datetime <= datetime <= end_datetime | ||
} | ||
|
||
else: | ||
return data | ||
|
||
def _gen_synthetic(self): | ||
# generate random data according to seed | ||
seed_everything(self.init_params["seed"]) | ||
# and based on start and end dates | ||
( | ||
self.activities, | ||
self.bpm, | ||
self.brpm, | ||
self.hrv, | ||
self.spo2, | ||
self.rest_cals, | ||
self.work_cals, | ||
self.active_cals, | ||
self.step_cals, | ||
self.total_cals, | ||
self.sleep_session, | ||
self.sleep_detail, | ||
self.steps, | ||
self.distance, | ||
) = create_syn_data( | ||
self.init_params["synthetic_start_date"], | ||
self.init_params["synthetic_end_date"], | ||
) | ||
|
||
# We get the access token to make requests to the Biostrap API | ||
def _authenticate(self, auth_creds): | ||
self.client_id = auth_creds["client_id"] | ||
self.client_secret = auth_creds["client_secret"] | ||
redirect_uri = "https://127.0.0.1:8080" | ||
token_url = "https://auth.biostrap.com/token" | ||
|
||
# Generate the authorization URL and open it in the default web browser | ||
params = { | ||
"client_id": self.client_id, | ||
"response_type": "code", | ||
"redirect_uri": redirect_uri, | ||
} | ||
authorization_url = f"https://auth.biostrap.com/authorize?{urlencode(params)}" | ||
|
||
# Instruct the user to open the URL in their browser () | ||
print( | ||
f"Please go to the following URL and authorize access: {authorization_url}" | ||
) | ||
print( | ||
"If a page cannot be displayed, please make sure you are logged into the Biostrap account in your browser." | ||
) | ||
|
||
# Get the authorization response URL from the command line (the method Jack and I talked about didn't work) | ||
authorization_response = input("Enter the full callback URL: ") | ||
|
||
code = authorization_response.split("code=")[1].split("&")[0] | ||
|
||
# Now we can request the access token | ||
data = { | ||
"grant_type": "authorization_code", | ||
"code": code, | ||
"redirect_uri": redirect_uri, | ||
} | ||
auth = (self.client_id, self.client_secret) | ||
response = requests.post(token_url, auth=auth, data=data) | ||
|
||
# Get the access token from the response | ||
token_json = response.json() | ||
self.access_token = token_json["access_token"] |
Oops, something went wrong.