Files
health-smart-mirror/src/app.py
2022-11-07 17:12:36 -05:00

340 lines
14 KiB
Python

import pandas as pd
import streamlit as st
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from api.hdat_reader import HDatReader, FitBit
from datetime import datetime as dt
from datetime import time
import os
from pathlib import Path
fitbit_data = FitBit(Path(os.getcwd(), 'fitbit_data'))
st.set_page_config(
layout='wide'
)
class App:
def __init__(self):
pass
def display_users_calories(self):
df = fitbit_data.daily_calories()
result = df[df['Id'] == st.session_state.selected_user]
daily_calories = px.bar(result, x='ActivityDay', y='Calories')
df = fitbit_data.hourly_calories()
result = df[(df['Id'] == st.session_state.selected_user)]
result.loc[:, 'ActivityHour'] = pd.to_datetime(result['ActivityHour'])
date = st.date_input('Select a date', result['ActivityHour'].min(),
min_value=result['ActivityHour'].min(),
max_value=result['ActivityHour'].max())
start = st.time_input('Select a starting hour', value=time())
end = st.time_input('Select an ending hour', time(23, 59, 59))
start = dt.combine(date, start)
end = dt.combine(date, end)
result = result[(result['ActivityHour'] >= start) &
(result['ActivityHour'] <= end)]
hourly_calories = px.bar(result, x='ActivityHour', y='Calories')
daily_calories.update_layout(title='Daily Calories')
hourly_calories.update_layout(title='Hourly Calories')
return daily_calories, hourly_calories
def display_daily_steps(self):
df = fitbit_data.daily_steps()
result = df[df['Id'] == st.session_state.selected_user]
bar = px.bar(result, x='ActivityDay', y='StepTotal')
bar.update_layout(title='Daily Steps')
return bar
def display_day_sleep(self):
df = fitbit_data.day_sleep()
df['TotalMinutesAsleep'] = pd.to_numeric(df['TotalMinutesAsleep'])
df['SleepHours'] = df['TotalMinutesAsleep'] / 60
result = df[df['Id'] == st.session_state.selected_user]
if result.empty:
return None
result.loc[:, 'SleepDay'] = pd.to_datetime(result['SleepDay'])
start = st.date_input('Select a date', value=result['SleepDay'].min(),
min_value=result['SleepDay'].min(),
max_value=result['SleepDay'].max(), key='day_start_sleep_date')
end = st.date_input('Select an end date', value=result['SleepDay'].max(), min_value=start,
max_value=result['SleepDay'].max(), key='day_end_sleep_date')
result = result[(result['SleepDay'].dt.date >= start) & (result['SleepDay'].dt.date <= end)]
fig = go.Figure()
fig.add_trace(go.Bar(x=result['SleepDay'], y=result['TotalMinutesAsleep'] / 60, name='Hours Asleep',
hovertemplate="Hours=%{y}"))
fig.add_trace(go.Bar(x=result['SleepDay'], y=result['TotalTimeInBed'] / 60, name='Time In Bed'))
fig.update_xaxes(title_text='Date')
fig.update_yaxes(title_text='Hours')
fig.update_layout(title='Sleep Data')
return fig
def display_user_intensities(self):
df = fitbit_data.daily_intensities()
user_data = df[df['Id'] == st.session_state.selected_user]
user_data.loc[:, 'ActivityDay'] = pd.to_datetime(user_data['ActivityDay'])
start = st.date_input('Select a start date', value=user_data['ActivityDay'].min(), min_value=user_data['ActivityDay'].min(),
max_value=user_data['ActivityDay'].max())
end = st.date_input('Select an end date', value=user_data['ActivityDay'].max(), min_value=start,
max_value=user_data['ActivityDay'].max())
user_data = user_data[(user_data['ActivityDay'].dt.date >= start) & (user_data['ActivityDay'].dt.date <= end)]
fig = go.Figure()
fig.add_trace(go.Bar(x=user_data['ActivityDay'], y=user_data['SedentaryMinutes'], name='Sedentary'))
fig.add_trace(go.Bar(x=user_data['ActivityDay'], y=user_data['LightlyActiveMinutes'], name='Lightly Active'))
fig.add_trace(go.Bar(x=user_data['ActivityDay'], y=user_data['FairlyActiveMinutes'], name='Fairly Active'))
fig.add_trace(go.Bar(x=user_data['ActivityDay'], y=user_data['VeryActiveMinutes'], name='Very Active'))
fig.update_xaxes(title_text='Date')
fig.update_yaxes(title_text='Minutes')
fig.update_layout(title='Daily User Intensities')
return fig
def display_user_weight(self):
df = fitbit_data.weight_log_info()
user_data = df[df['Id'] == st.session_state.selected_user]
if user_data.empty:
return None
fig = go.Figure()
fig.add_hrect(y0=18.5, y1=24.9, line_width=0, fillcolor='green', opacity=0.1, name='Normal weight')
fig.add_trace(go.Scatter(x=user_data['Date'], y=user_data['WeightPounds'], name='Weight', line=dict(width=2), yaxis='y2'))
fig.add_trace(go.Scatter(x=user_data['Date'], y=user_data['BMI'], name='BMI', line=dict(width=2, dash='dash')))
fig.update_layout(title='BMI and Mass', xaxis=dict(title='Date'), yaxis2=dict(title='lbs', side='right', overlaying='y'), yaxis=dict(title='kg/m²'))
return fig
def avg_sleep(self):
df = fitbit_data.day_sleep()
df['TotalMinutesAsleep'] = pd.to_numeric(df['TotalMinutesAsleep'])
df['SleepHours'] = df['TotalMinutesAsleep'] / 60
result = df[df['Id'] == st.session_state.selected_user]
if result.empty:
return np.nan, 'No sleep data available', 'off'
result.loc[:, 'SleepDay'] = pd.to_datetime(result['SleepDay'])
sleep = result['SleepHours'].mean()
return round(sleep, 2), f'{round(sleep - 8, 2)} hours'
def avg_steps(self):
df = fitbit_data.daily_steps()
result = df[df['Id'] == st.session_state.selected_user]
if result.empty:
return np.nan
steps = int(result['StepTotal'].mean())
return steps, f'{steps - 4774} compared to the US average'
def avg_calories(self):
df = fitbit_data.daily_calories()
result = df[df['Id'] == st.session_state.selected_user]
if result.empty:
return np.nan
return round(result['Calories'].mean(), 2)
def mass(self):
df = fitbit_data.weight_log_info()
result = df[df['Id'] == st.session_state.selected_user]
if result.empty:
return np.nan
return round(result[result['Date'] == result['Date'].max()]['WeightPounds'], 2)
def bmi(self):
df = fitbit_data.weight_log_info()
result = df[df['Id'] == st.session_state.selected_user]
if result.empty:
return np.nan, 'No data available', 'off'
bmi = round(result[result['Date'] == result['Date'].max()]['BMI'], 2)
bmi = float(bmi)
if bmi < 18.5:
return bmi, 'Underweight', 'off'
elif 18.5 <= bmi <= 24.9:
return bmi, 'Normal', 'normal'
elif 24.9 < bmi <= 29.9:
return bmi, 'Overweight', 'off'
else:
return bmi, 'Obese', 'inverse'
def run(self):
def set_selected_user():
st.session_state.selected_user = demo[st.session_state.selected_username]
def add_goal():
st.session_state.daily_goals[st.session_state.selected_user].append(st.session_state.inp_goal_task)
def clear_goals():
st.session_state.daily_goals[st.session_state.selected_user].clear()
demo = {
'John': 1503960366,
'Smith': 6962181067,
'Jake': 7086361926,
'Arnold': 8792009665,
'Max': 8877689391
}
if 'selected_user' not in st.session_state:
st.session_state.selected_user = demo['John']
if 'daily_goals' not in st.session_state:
st.session_state.daily_goals = {
1503960366: ['Workout', 'Reach 10000 Steps'],
6962181067: [],
7086361926: ['Go to the gym', 'Do high intensity workouts'],
8792009665: ['Go to bed earlier', 'Adjust calorie intake'],
8877689391: ['Lower BMI']
}
st.markdown('### Select a profile')
st.selectbox('Select a user ID', options=demo, key='selected_username',
on_change=set_selected_user)
st.markdown('### Tasks and Goals\n' + '\n'.join([f'- {item}' for item in st.session_state.daily_goals[st.session_state.selected_user]]))
c1, _, _ = st.columns(3)
with c1:
st.text_input('Input new goal/ task', key='inp_goal_task', on_change=add_goal)
st.button('Clear goal', on_click=clear_goals, key='clr_goal_btn')
tabs = st.empty()
overview, calories, steps, intensities, sleep, weight = tabs.tabs(['Overview', 'Calories',
'Steps', 'Intensities', 'Sleep', 'Weight'])
with overview:
st.subheader('Health Overview')
c1, c2, c3 = st.columns(3)
with c1:
st.metric('Average Sleep (Hours)', *self.avg_sleep())
with c2:
st.metric('Average Daily Steps', *self.avg_steps(), help='US average from healthline.com')
st.metric('Weight', self.mass())
with c3:
st.metric('Daily Caloric Intake', self.avg_calories(), round(self.avg_calories() - 2000, 2),
help='Based on daily 2000 calories')
st.metric('BMI', *self.bmi())
with calories:
cc1, cc2 = st.columns(2)
with cc1:
st.markdown(
"""
### About Calories
- Women are recommended to consume 2,000 calories a day
- Men are recommended to consume 2,500 calories a day
- Calorie consumption is relative. You should use a calorie calculator online to find what is right
for you based on height, age, activity level, etc.
"""
)
st.subheader('Hourly consumed calorie controls')
fig, fig2 = self.display_users_calories()
with cc2:
st.plotly_chart(fig2)
st.plotly_chart(fig)
with steps:
fig = self.display_daily_steps()
c1, c2 = st.columns(2)
with c1:
st.markdown(
"""
### About Steps
- ~7,500 steps per day can help reduce the risk of death
- The CDC recommends ~30,000 steps per week, with a minimum of 15,000
- You should aim for at least 2,000 steps per day to meet CDC minimum guidelines
- If you're not sure how to get started try:
- Using the stairs instead of the elevator
- Talk a short walk during breaks
- Park farther away when running errands
- Perform housekeeping activities
Sources: healthline.com, Harvard
"""
)
with c2:
st.plotly_chart(fig)
with intensities:
c1, c2 = st.columns(2)
with c1:
st.markdown(
"""
### About Intensities
- Intensities are measures of how active you were throughout your day.
- Sedentary is synonymous with being idle while very active is at the opposite end of that spectrum.
- As a general goal, it is recommended by the Mayo clinic to aim for **30 minutes** of active exercise a day.
"""
)
st.subheader('Date controls')
fig = self.display_user_intensities()
with c2:
st.plotly_chart(fig)
with sleep:
sc1, sc2 = st.columns(2)
with sc1:
st.markdown(
"""
### Helpful Tips & Information
- For adults the target to reach for sleep is 7-9 hours per 24 hours.
- Sleep quality is just as important as the quantity.
- Try to avoid interruptions.
"""
)
control_header = st.empty()
fig = self.display_day_sleep()
if fig is not None:
control_header.subheader('Date controls')
with sc2:
if fig is None:
st.info('You have not reported any sleep information.')
else:
st.plotly_chart(fig)
with weight:
fig = self.display_user_weight()
c1, c2 = st.columns(2)
with c2:
if fig is None:
st.info('You have not reported any weight or BMI information.')
else:
st.plotly_chart(fig)
with c1:
st.markdown(
"""
### Helpful Tips & Information
Your goal should be to aim for the green area with your BMI.
The BMI plot is an indicator for where you stand in terms of weight.
- Underweight: <18.5
- Normal: 18.5 - 24.9
- Overweight: 25.0-29.9
- Obese: >30.0
"""
)
def main_window(self):
bar = px.bar(self.data, x='startDate', y='sleepDuration')
st.plotly_chart(bar)
self.data['date'] = pd.to_datetime(self.data['startDate']).dt.date
self.data = self.data[
(self.data['sleepDuration'] > 4) & (self.data['date'] > pd.to_datetime('2020-01-01').date())]
line = px.line(self.data, x='date', y='sleepDuration')
st.plotly_chart(line)
app = App()
app.run()