diff --git a/config/settings/base.py b/config/settings/base.py index 667aabf..b8e2e72 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -203,3 +203,4 @@ DDM_SEARCH_BP_ID = os.environ['DDM_SEARCH_BP_ID'] DDM_FITBIT_PROJECT_ID = os.environ['DDM_FITBIT_PROJECT_ID'] +DDM_FITBIT_API_TOKEN = os.environ['DDM_FITBIT_API_TOKEN'] diff --git a/digital_meal/templates/digital_meal/individual_fitbit_report.html b/digital_meal/templates/digital_meal/individual_fitbit_report.html index 6d1c185..d5465d9 100644 --- a/digital_meal/templates/digital_meal/individual_fitbit_report.html +++ b/digital_meal/templates/digital_meal/individual_fitbit_report.html @@ -7,9 +7,9 @@ - {% block title %}Individual Report{% endblock %} + {% block title %}Dein Fitbit Report{% endblock %} - + {% block site_css %} @@ -20,8 +20,6 @@ {% endblock extra_head %} -{% load static %} -
@@ -35,29 +33,53 @@
+ {% if sleep_plot or sleep_pulse_plot or activity_plot or steps_plot %}
-

Deine personalisierte Auswertung deiner Fitbit-Daten

+
+

Deine personalisierte Auswertung deiner Fitbit-Daten

+
+
+ Symbolbild FitBit Studie +
-
- Symbolbild FitBit Studie -
-
-

In dieser persönlichen Auswertung findest Du Informationen zu Deinen persönlichen Fitbit-Daten. +

In dieser persönlichen Auswertung findest Du Informationen zu Deinen persönlichen Aktivitäts-Daten,
+ wie sie von FitBit aufgezeichnet wurden.

Scrolle nach unten, um mehr zu erfahren.

+
+
+ {% else %} +
+
+
+

Deine personalisierte Auswertung deiner Fitbit-Daten

+
+
+ Symbolbild FitBit Studie
+ +
+

+ Danke für das Interesse und die Teilnahme an unserer Studie. +

+

+ Leider konnten wir deine Daten nicht abrufen. Entweder hast du keine + Daten gespendet oder es ist beim Abrufen der Daten ein Fehler aufgetreten. +

+
+ {% endif %}
- + {% if sleep_plot or sleep_pulse_plot %}
@@ -73,17 +95,32 @@

Deine personalisierte Auswertung deiner Fitbi

Dein Schlaf

- Informationen zu Schlafdaten + Die folgenden Schlafdaten wurden aus Deinem FitBit-Datenexport ausgelesen:

-
[evt. Grafik]
+
+ {% if sleep_plot %} +
+

Anzahl Minuten im Tiefschlaf

+ {{ sleep_plot.div | safe }} +
+ {% endif %} + {% if sleep_pulse_plot %} +
+

Ruhepuls im Schlaf

+ {{ sleep_pulse_plot.div | safe }} +
+ {% endif %} +
+ {% endif %} + {% if activity_plot %}
@@ -97,20 +134,23 @@

Dein Schlaf

-

Deine Aktivitäten der letzten Wochen

+

Dein Aktivitätslevel der letzten Wochen

- In den letzten Wochen hat dein FitBit-Gerät an X Tagen Aktivitäten aufgezeichnet. + Unten siehst Du eine Übersicht der von FitBit aufgezeichneten Aktivitäten zwischen + {{ activity_date_min }} und {{ activity_date_max }}.

- [evt. GRAFIK] + {{ activity_plot.div | safe }}
+ {% endif %} - + + {% if steps_plot %}
@@ -125,19 +165,24 @@

Deine Aktivitäten der letzten Wochen

-

Weitere Daten

+

Schritte

- Hier könnte ein informativer Text stehen. + Unten siehst Du eine Übersicht der von FitBit aufgezeichneten Schritte:

- +
+
+ {{ steps_plot.div | safe }} +
+
+ {% endif %} - +
@@ -183,7 +228,6 @@

Über diesen Report

Dieser Report wurde automatisch basierend auf deinen hochgeladenen Daten generiert. Er ist für eine befristete Zeit für Personen, die über den Link verfügen, abrufbar. - Wenn du diesen Report später nochmals ansehen möchtest, kannst du ihn als PDF speichern.

@@ -193,7 +237,8 @@

Über diesen Report

- Bei Fragen oder Inputs kannst du uns jederzeit mit einer E-Mail an datadonation@ikmz.uzh.ch kontaktieren. + Bei Fragen oder Inputs kannst du uns jederzeit mit einer E-Mail an datadonation@ikmz.uzh.ch kontaktieren + oder uns direkt an unserem Scientifica-Stand ansprechen.

@@ -210,6 +255,10 @@

Über diesen Report

+{{ activity_plot.script | safe }} +{{ sleep_plot.script | safe }} +{{ sleep_pulse_plot.script | safe }} +{{ steps_plot.script | safe }} diff --git a/digital_meal/templates/digital_meal/individual_report.html b/digital_meal/templates/digital_meal/individual_report.html index 5cd9721..0089ef6 100644 --- a/digital_meal/templates/digital_meal/individual_report.html +++ b/digital_meal/templates/digital_meal/individual_report.html @@ -7,7 +7,7 @@ - {% block title %}Individual Report{% endblock %} + {% block title %}Dein YouTube Report{% endblock %} @@ -20,8 +20,6 @@ {% endblock extra_head %} -{% load static %} -
@@ -36,6 +34,7 @@
+ {% if dates_plot or weekday_use_plot or fav_video.n_watched > 1 or channel_plot or n_searches > 0 %}

Deine personalisierte Auswertung deiner YouTube-Nutzung

@@ -56,6 +55,28 @@

Deine personalisierte Auswertung deiner YouTu

+ {% else %} +
+
+
+

Deine personalisierte Auswertung deiner YouTube-Nutzung

+
+
+ Symbolbild FitBit Studie +
+
+ +
+

+ Danke für das Interesse und die Teilnahme an unserer Studie. +

+

+ Leider konnten wir deine Daten nicht abrufen. Entweder hast du keine + Daten gespendet oder es ist beim Abrufen der Daten ein Fehler aufgetreten. +

+
+
+ {% endif %}
@@ -230,7 +251,7 @@

Nach was Du gesucht hast

{% endif %} - + - -
@@ -275,7 +295,6 @@

Über diesen Report

Dieser Report wurde automatisch basierend auf deinen hochgeladenen Daten generiert. Er ist für eine befristete Zeit für Personen, die über den Link verfügen, abrufbar. - Wenn du diesen Report später nochmals ansehen möchtest, kannst du ihn als PDF speichern.

@@ -285,7 +304,8 @@

Über diesen Report

- Bei Fragen oder Inputs kannst du uns jederzeit mit einer E-Mail an datadonation@ikmz.uzh.ch kontaktieren. + Bei Fragen oder Inputs kannst du uns jederzeit mit einer E-Mail an datadonation@ikmz.uzh.ch kontaktieren + oder uns direkt an unserem Scientifica-Stand ansprechen.

diff --git a/digital_meal/templates/digital_meal/scientifica_landing.html b/digital_meal/templates/digital_meal/scientifica_landing.html index d26557a..30eba01 100644 --- a/digital_meal/templates/digital_meal/scientifica_landing.html +++ b/digital_meal/templates/digital_meal/scientifica_landing.html @@ -11,7 +11,7 @@ Data Donation Lab | Scientifica 2023 - + {% block site_css %} @@ -123,7 +123,7 @@

Spenden Sie Ihre Daten jetzt

- +
diff --git a/digital_meal/utils/fitbit_plots.py b/digital_meal/utils/fitbit_plots.py index 82ab7f3..766c4e6 100644 --- a/digital_meal/utils/fitbit_plots.py +++ b/digital_meal/utils/fitbit_plots.py @@ -1,19 +1,116 @@ +import math + +import pandas as pd from bokeh.embed import components +from bokeh.models.ranges import FactorRange +from bokeh.palettes import tol +from bokeh.plotting import figure def get_sleep_plot(data): - p = None + data['timestamp'] = pd.to_datetime(data['timestamp']) + data = data.replace('', None) + data['deep_sleep_in_minutes'] = data['deep_sleep_in_minutes'].astype('Int64') + data['resting_heart_rate'] = data['resting_heart_rate'].astype('Int64') + data['duration_score'] = data['duration_score'].astype('Int64') + + p = figure( + x_axis_type='datetime', + y_range=(0, data['deep_sleep_in_minutes'].max() + 20), + toolbar_location=None, + height=300 + ) + p.line(x='timestamp', y='deep_sleep_in_minutes', line_width=3, source=data) + p.border_fill_color = None + p.grid.grid_line_color = None + p.axis.minor_tick_line_color = None + p.background_fill_color = '#fff2ba' + script, div = components(p) + return {'script': script, 'div': div} + + +def get_heart_rate_sleep_plot(data): + data['timestamp'] = pd.to_datetime(data['timestamp']) + data = data.replace('', None) + data['deep_sleep_in_minutes'] = data['deep_sleep_in_minutes'].astype('Int64') + data['resting_heart_rate'] = data['resting_heart_rate'].astype('Int64') + data['duration_score'] = data['duration_score'].astype('Int64') + + p = figure( + x_axis_type='datetime', + y_range=(0, data['resting_heart_rate'].max() + 20), + toolbar_location=None, + height=300 + ) + p.line(x='timestamp', y='resting_heart_rate', line_width=3, color='orange', source=data) + p.border_fill_color = None + p.grid.grid_line_color = None + p.axis.minor_tick_line_color = None + p.background_fill_color = '#fff2ba' script, div = components(p) return {'script': script, 'div': div} def get_steps_plot(data): - p = None + data['dateTime'] = pd.to_datetime(data['dateTime']) + data = data.replace('', None) + data['value'] = data['value'].astype('Int64') + data_days = data.set_index('dateTime').groupby(pd.Grouper(freq='1D')).sum() + + p = figure( + x_axis_type='datetime', + y_range=(0, data_days['value'].max() + 200), + toolbar_location=None, + height=300 + ) + p.line(x='dateTime', y='value', line_width=3, color='purple', source=data_days) + p.border_fill_color = None + p.grid.grid_line_color = None + p.axis.minor_tick_line_color = None + p.background_fill_color = '#bbf3d4' script, div = components(p) return {'script': script, 'div': div} -def get_active_minutes_plot(data): - p = None +def get_active_minutes_plot(light_activity, moderate_activity, high_activity): + # rename dataframes + if light_activity is not None: + light_activity.rename(columns={'value': 'leicht'}, inplace=True) + if moderate_activity is not None: + moderate_activity.rename(columns={'value': 'moderat'}, inplace=True) + if high_activity is not None: + high_activity.rename(columns={'value': 'hoch'}, inplace=True) + + activity = light_activity.set_index('dateTime').join(moderate_activity.set_index('dateTime')) + activity = activity.join(high_activity.set_index('dateTime')) + + activity.reset_index(inplace=True) + activity['dateTime'] = pd.to_datetime(activity['dateTime']) + activity = activity.replace('', None) + + activity['leicht'] = activity['leicht'].astype(int) + activity['moderat'] = activity['moderat'].astype(int) + activity['hoch'] = activity['hoch'].astype(int) + + max_y = activity[['leicht', 'moderat', 'hoch']].sum(axis=1).max() + + p = figure( + x_axis_type='datetime', + y_range=(0, max_y + 20), + toolbar_location=None, + ) + p.grid.minor_grid_line_color = '#eeeeee' + + names = ['leicht', 'moderat', 'hoch'] + p.varea_stack(stackers=names, x='dateTime', color=tol['HighContrast'][3], legend_label=names, source=activity) + + p.legend.orientation = 'horizontal' + p.legend.background_fill_color = 'white' + p.background_fill_color = '#F1EDFF' # #D0C3FF + p.border_fill_color = None + p.grid.grid_line_color = None + p.axis.minor_tick_line_color = None + p.xaxis.major_label_orientation = math.pi/3 + script, div = components(p) return {'script': script, 'div': div} diff --git a/digital_meal/views.py b/digital_meal/views.py index 3a1ba12..891b194 100644 --- a/digital_meal/views.py +++ b/digital_meal/views.py @@ -1,5 +1,6 @@ from io import BytesIO +import pandas as pd import requests from django.conf import settings @@ -7,7 +8,7 @@ from django.template.loader import get_template from django.views.generic import TemplateView -from digital_meal.utils import yt_data, yt_plots +from digital_meal.utils import yt_data, yt_plots, fitbit_plots from xhtml2pdf import pisa @@ -114,15 +115,13 @@ def get_donation_data(project_id, participant_id, blueprint_id): and a blueprint id as query parameters. """ api_endpoint = f'{settings.DDM_BASE_URL}api/project/{project_id}/donations' - api_token = settings.DDM_API_TOKEN + api_token = settings.DDM_FITBIT_API_TOKEN headers = { 'Authorization': f'Token {api_token}' } payload = { - 'project': project_id, - 'participant': participant_id, - 'blueprint': blueprint_id + 'participants': participant_id } r = requests.get(api_endpoint, headers=headers, params=payload) @@ -137,12 +136,62 @@ def get_context_data(self, **kwargs): # 1) get data from api participant_id = self.kwargs['external_participant_id'] project_id = settings.DDM_FITBIT_PROJECT_ID - heart_rate_data = self.get_donation_data( + donated_data = self.get_donation_data( project_id, participant_id, blueprint_id=32) + data_lightly_active = donated_data.get('Minuten leichte Aktivität', None) + if data_lightly_active: + data_lightly_active = pd.DataFrame.from_dict(data_lightly_active[0]) + + activity_date_min = data_lightly_active.dateTime.min() + activity_date_max = data_lightly_active.dateTime.max() + else: + activity_date_min = None + activity_date_max = None + + data_moderately_active = donated_data.get('Minuten moderate Aktivität', None) + if data_moderately_active: + data_moderately_active = pd.DataFrame.from_dict(data_moderately_active[0]) + data_very_active = donated_data.get('Minuten hohe Aktivität', None) + if data_very_active: + data_very_active = pd.DataFrame.from_dict(data_very_active[0]) + + try: + activity_plot = fitbit_plots.get_active_minutes_plot(data_lightly_active, data_moderately_active, data_very_active) + except: + activity_plot = None + + # Schlafdaten + data_sleep = donated_data.get('Schlafdaten', None) + if data_sleep: + data_sleep = pd.DataFrame.from_dict(data_sleep[0]) + try: + sleep_plot = fitbit_plots.get_sleep_plot(data_sleep) + except: + sleep_plot = None + + try: + sleep_pulse_plot = fitbit_plots.get_heart_rate_sleep_plot(data_sleep) + except: + sleep_pulse_plot = None + + # Schrittdaten + data_steps = donated_data.get('Schritte', None) + if data_steps: + data_steps = pd.DataFrame.from_dict(data_steps[0]) + + steps_plot = fitbit_plots.get_steps_plot(data_steps) + # 3) add to context context.update({ - 'heart_rate_data': heart_rate_data + 'data': donated_data, + 'activity_plot': activity_plot, + 'activity_date_min': activity_date_min, + 'activity_date_max': activity_date_max, + 'sleep_plot': sleep_plot, + 'sleep_data': data_sleep, + 'sleep_pulse_plot': sleep_pulse_plot, + 'steps_plot': steps_plot }) return context