def get_heart_rate_zone_today(): """ Endpoint for getting heart rate zone data from the FitBit API. :return: """ heart_rate_zone = HeartRateTimeSeries(config={'database': 'fitbit'}) form = DateForm(request.form) value = 'Updated heart rate zone data for {}.' search_date = str(date.today()) if request.method == 'POST': if request.form.get( 'year_options' ): # set search_date, default to today if none supplied search_date = f"{request.form['year_options']}-{request.form['month_options']}-{request.form['day_options']}" # collect search date information from the dropdown forms if they're all supplied. rows = heart_rate_zone.get_heart_rate_zone_for_day( database='fitbit', target_date=search_date) rows = [i for i in rows] else: # request.method == 'GET' # no date supplied, just return data for today. heart_rate_zone.config = {'base_date': date.today(), 'period': '1d'} statement = heart_daily_table.select().where( heart_daily_table.columns.date == str(date.today())) rows = Database(database='fitbit', schema='heart').engine.execute(statement) return render_template(template_name_or_list="index.html", value=value.format(search_date), rows=rows, form=form, month_options=month_options, day_options=day_options, year_options=year_options)
def get_heart_rate_zone_for_day(self, database: str = 'fitbit', target_date: str = 'today'): """ Retrieves heart rate data for one day only. This method should not be used to add batch data - i.e., iterating through a list of dates is likely to trigger rate limit errors. :param database: Database to insert into. :param target_date: Date to retrieve heart rate zone data for. :return: """ if target_date != 'today': self.config.update({ 'base_date': target_date, 'end_date': target_date, 'database': database }) else: today = date.today().strftime('%Y-%m-%d') self.config.update({ 'base_date': today, 'end_date': today, 'database': database }) db = Database(database=database, schema=self.config['schema']) rows = self.insert_data(db, table=heart_daily_table) return rows
def check_if_steps_need_update(base_date='2021-01-01'): activity_api = Activity(config={'base_date': base_date}) response = activity_api.query_daily_activity_summary() steps_total = response['summary']['steps'] # get steps in db database = Database('fitbit', 'activity') connection = database.engine.connect() response = connection.execute( "select * from activity.steps_intraday where date = '{}'".format( base_date)).fetchall() steps_intraday = 0 for result in response: steps_intraday += result[2] if steps_total != steps_intraday: match = False msg = '\nSteps total does not equal steps intraday.\nNeed to rerun get_steps for {}'.format( base_date) comparison = '\nSteps total: {}\nSteps intraday: {}'.format( steps_total, steps_intraday) print(msg) print(comparison) else: match = True msg = '\nSteps total matches steps intraday.' print(msg) return match
def insert_data(self): """ Extracts, transforms & loads the data specified by the self.config dict. :return: """ data = self.query() parsed_rows = self.parse_response(data) db = Database(self.config['database'], schema=self.config['schema']) session = sessionmaker(bind=db.engine)() for row in parsed_rows: insert_statement = insert(bodyfat_table).values( date=row.date, fat=row.fat, logid=row.logId, source=row.source, time=row.time ) try: session.execute(insert_statement) session.commit() except IntegrityError: # record already exists pass session.close()
def get_avg_steps_over_last_week(period='7d'): database = Database('fitbit', 'activity') end_date = datetime.today() start_date = end_date - timedelta(days=period) database = Database('fitbit', 'activity') connection = database.engine.connect() response = connection.execute(f""" SELECT date, SUM(STEPS) FROM activity.steps_intraday WHERE DATE between '{start_date}' and '{end_date}' GROUP BY date ORDER BY date DESC """).fetchall() steps = [] for i in response: steps.append(i[1]) print(sum(steps)) return
def get_total_calories_df(self, show=True): """ Sums calories for all days in heart_daily_table. :param show: if True, displays the dataframe in addition to returning it. :return: """ from fitnick.database.database import Database from pyspark.sql import functions as F database = Database(self.config['database'], schema='heart') database.create_spark_session() df = database.get_df_from_db('daily') agg_df = (df.groupBy(F.col('date')).agg( F.sum('calories')).alias('calories')).orderBy('date') if show: agg_df.show() return agg_df
def test_insert_steps_intraday(): activity = Activity( config={'database': 'fitbit_test', 'table': 'calories', 'sum_column': 'total', 'base_date': '2020-12-26' } ) database = Database('fitbit_test', 'activity') response = activity.insert_steps_intraday(database)
def test_insert_intraday_steps(): database = Database('fitbit_test', 'activity') connection = database.engine.connect() connection.execute(steps_intraday_table.delete()) activity = Activity( config={'database': 'fitbit_test', 'base_date': '2020-10-01'} ) rows = activity.insert_log_data(database, EXPECTED_DAILY_ACTIVITY_ROWS) assert len(rows) == 5
def get_heart_rate_zone_rows(heart_rate_zone): statement = heart_daily_table.select().where( heart_daily_table.columns.date == str(date.today())) rows = [ i for i in Database(database='fitbit', schema='heart').engine.execute( statement) ] # retrieve rows for today already in database, if there are none then get rows via fitnick if len(rows) == 0: rows = heart_rate_zone.get_heart_rate_zone_for_day(database='fitbit') return rows
def batch_load_steps(start_date='2020-01-01', end_date='2021-01-01'): start_date = date(2021, 1, 1) # start date database = Database('fitbit', 'activity') # end_date = set_batch_end_date(database, start_date) end_date = date(2021, 1, 6) # end date delta = end_date - start_date # as timedelta activity_api = Activity(config={'base_date': ''}) for i in tqdm(list(reversed(range(delta.days + 1)))): day = start_date + timedelta(days=i) activity_api.config['base_date'] = day response = activity_api.insert_steps_intraday(database) print(response)
def test_backfill_calories(): database = Database('fitbit_test', 'activity') connection = database.engine.connect() activity = Activity( config={'database': 'fitbit_test'} ) connection.execute(calories_table.delete()) rows = [i for i in connection.execute(calories_table.select())] assert len(rows) == 0 activity.backfill_calories(3) assert len([i for i in connection.execute(calories_table.select())]) == 3
def test_query_sleep_data(): database = Database('fitbit_test', 'sleep') connection = database.engine.connect() connection.execute(sleep_summary_table.delete()) sleep_data = SleepTimeSeries( config={ 'database': 'fitbit_test', 'base_date': '2020-09-05', 'end_date': '2020-09-05' }).batch_query_sleep_data() assert sleep_data == EXPECTED_API_RESPONSE
def backfill(self, period: int = 90): """ Backfills a database from the current day. Example: if run on 2020-09-06 with period=90, the database will populate for 2020-06-08 - 2020-09-06 :param period: Number of days to look backward. :return: """ self.config['base_date'] = ( date.today() - timedelta(days=period)).strftime('%Y-%m-%d') self.config['end_date'] = date.today().strftime('%Y-%m-%d') database = Database(database=self.config['database'], schema=self.config['schema']) self.insert_data(database=database, table=self.config['table'])
def test_insert_calorie_data(): database = Database('fitbit_test', 'activity') connection = database.engine.connect() connection.execute(calories_table.delete()) activity = Activity( config={'database': 'fitbit_test', 'base_date': '2020-10-01'} ) raw_data = activity.query_calorie_summary() row = activity.parse_calorie_summary('2020-10-01', raw_data) inserted_row = activity.insert_calorie_data(database, row) assert inserted_row == Calories(date='2020-10-01', total=3116, calories_bmr=1838, activity_calories=1467)
def test_check_for_duplicates(): """ Useful for asserting that there aren't duplicates in the database, which *should* be avoided in the code. """ database = Database('fitbit_test', 'heart') connection = database.engine.connect() HeartRateTimeSeries(config={ 'base_date': '2020-09-02', 'period': '1d', 'database': 'fitbit_test' }).insert_data(database, heart_daily_table) results = connection.execute(heart_daily_table.select().where( heart_daily_table.columns.date == '2020-09-02')) assert len([result for result in results]) == 4 connection.close()
def get_calories_for_day(self, day='2020-10-22'): """ Given a YYYY-MM-DD formatted string, retrieves calorie data from the FitBit API and inserts it into the database specified by the config. :param day: str, day to get calorie data for :return: """ from fitnick.database.database import Database self.config.update({'base_date': day}) raw_calorie_summary = self.query_calorie_summary() row = self.parse_calorie_summary(self.config['base_date'], raw_calorie_summary) database = Database(self.config['database'], 'activity') self.insert_calorie_data(database, row) return row
def test_insert_weight_data(): from fitnick.body.models.weight import weight_table database = Database('fitbit_test', 'weight') connection = database.engine.connect() connection.execute(weight_table.delete()) WeightTimeSeries(config={ 'database': 'fitbit_test', 'base_date': '2020-09-05', 'period': '1d' }).insert_data(database, weight_table) rows = [row for row in connection.execute(weight_table.select())] connection.close() assert sorted(rows) == sorted(EXPECTED_WEIGHT_DATA)
def test_insert_sleep(): """ Deletes & re-inserts data, then asserts the data was inserted as expected. :return: """ database = Database('fitbit_test', 'sleep') connection = database.engine.connect() connection.execute(sleep_summary_table.delete()) SleepTimeSeries( config={ 'database': 'fitbit_test', 'base_date': '2020-09-05', 'end_date': '2020-09-10' }).insert_data(database) rows = [row for row in connection.execute(sleep_summary_table.select())] assert len(rows) == 6
def test_insert_bodyfat(): """ Deletes & re-inserts data, then asserts the data was inserted as expected. :return: """ database = Database('fitbit_test', 'bodyfat') connection = database.engine.connect() connection.execute(bodyfat_table.delete()) BodyFat( config={ 'database': 'fitbit_test', 'base_date': '2020-09-05', 'period': '1d', 'table': 'daily' }).insert_data() rows = [row for row in connection.execute(bodyfat_table.select())] assert len(rows) == 1
def insert_intraday_data(self): """ Extracts, transforms & loads the intraday data specified by the self.config dict. :return: """ data = self.query() parsed_rows = self.parse_intraday_response( date=self.config['base_date'], intraday_response=data) db = Database(self.config['database'], schema=self.config['schema']) # create a session connected to the database in config session = sessionmaker(bind=db.engine)() for row in tqdm(parsed_rows): session.add(row) session.commit() session.close() return parsed_rows