diff --git a/generators/pdk_withings_device.py b/generators/pdk_withings_device.py index f665255..dca963c 100644 --- a/generators/pdk_withings_device.py +++ b/generators/pdk_withings_device.py @@ -1,13 +1,22 @@ import calendar import csv +import datetime import tempfile from zipfile import ZipFile import arrow +from django.template.loader import render_to_string +from django.utils import timezone + from ..models import DataPoint +SECONDARY_IDENTIFIER_ACTIVITY = 'activity-measures' +SECONDARY_IDENTIFIER_INTRADAY = 'intraday-activity' +SECONDARY_IDENTIFIER_SLEEP = 'sleep-measures' +SECONDARY_IDENTIFIER_BODY = 'body' + SECONDARY_FIELDS = { 'intraday-activity': [ 'intraday_activity_history', @@ -47,6 +56,9 @@ ] } +def generator_name(identifier): # pylint: disable=unused-argument + return 'Withings Device' + def extract_secondary_identifier(properties): if 'datastream' in properties: return properties['datastream'] @@ -105,3 +117,19 @@ def compile_report(generator, sources): # pylint: disable=too-many-locals export_file.write(secondary_filename, secondary_filename.split('/')[-1]) return filename + +def data_table(source, generator): + context = {} + context['source'] = source + context['generator_identifier'] = generator + + end = timezone.now() + start = end - datetime.timedelta(days=7) + + context['activity_values'] = DataPoint.objects.filter(source=source.identifier, generator_identifier=generator, secondary_identifier=SECONDARY_IDENTIFIER_ACTIVITY, created__gt=start, created__lte=end).order_by('created') + context['intraday_values'] = DataPoint.objects.filter(source=source.identifier, generator_identifier=generator, secondary_identifier=SECONDARY_IDENTIFIER_INTRADAY, created__gt=start, created__lte=end).order_by('created') + context['sleep_values'] = DataPoint.objects.filter(source=source.identifier, generator_identifier=generator, secondary_identifier=SECONDARY_IDENTIFIER_SLEEP, created__lte=end).order_by('created') # created__gt=start, + context['body_values'] = DataPoint.objects.filter(source=source.identifier, generator_identifier=generator, secondary_identifier=SECONDARY_IDENTIFIER_BODY, created__lte=end).order_by('created') + + return render_to_string('pdk_wearable_withings_device_table_template.html', context) + diff --git a/management/commands/pdk_status_withings_device_check_last_upload.py b/management/commands/pdk_status_withings_device_check_last_upload.py new file mode 100644 index 0000000..20fd152 --- /dev/null +++ b/management/commands/pdk_status_withings_device_check_last_upload.py @@ -0,0 +1,90 @@ +# pylint: disable=line-too-long, no-member + +from django.core.management.base import BaseCommand +from django.utils import timezone + +from ...decorators import handle_lock + +from ...models import DataPoint, DataSource, DataSourceAlert + +GENERATOR = 'pdk-withings-device' +CRITICAL_DAYS = 2 +WARNING_DAYS = 1 + +class Command(BaseCommand): + help = 'Runs the Withings device upload status check to alert when a device sync is overdue.' + + @handle_lock + def handle(self, *args, **options): # pylint: disable=too-many-branches, too-many-statements + now = timezone.now() + + for source in DataSource.objects.all(): + last_alert = DataSourceAlert.objects.filter(data_source=source, generator_identifier=GENERATOR, active=True).order_by('-created').first() + + last_upload = DataPoint.objects.filter(source=source.identifier, generator_identifier=GENERATOR).order_by('-created').first() + + alert_name = None + alert_details = {} + alert_level = 'info' + + delta = now - last_upload.created + + if last_upload is not None: + if delta.days >= CRITICAL_DAYS: + alert_name = 'Withings upload is critically overdue' + alert_details['message'] = 'Latest Withings upload was ' + str(delta.days) + ' days ago.' + alert_level = 'critical' + + elif delta.days >= WARNING_DAYS: + alert_name = 'Withings upload is overdue' + alert_details['message'] = 'Latest Withings upload was 1 day ago.' + alert_level = 'warning' + + if alert_name is not None: + if last_alert is None or last_alert.alert_name != alert_name or last_alert.alert_level != alert_level: + if last_alert is not None: + last_alert.active = False + last_alert.updated = timezone.now() + last_alert.save() + + new_alert = DataSourceAlert(alert_name=alert_name, data_source=source, generator_identifier=GENERATOR) + new_alert.alert_level = alert_level + new_alert.update_alert_details(alert_details) + new_alert.created = timezone.now() + new_alert.updated = timezone.now() + new_alert.active = True + + new_alert.save() + else: + last_alert.updated = timezone.now() + last_alert.update_alert_details(alert_details) + + last_alert.save() + elif last_alert is not None: + last_alert.updated = timezone.now() + last_alert.active = False + + last_alert.save() + else: + alert_name = 'Withing data never uploaded' + alert_details['message'] = 'No Withings data is available from this user.' + + if last_alert is None or last_alert.alert_name != alert_name or last_alert.alert_level != alert_level: + if last_alert is not None: + last_alert.active = False + last_alert.updated = timezone.now() + last_alert.save() + + new_alert = DataSourceAlert(alert_name=alert_name, data_source=source, generator_identifier=GENERATOR) + new_alert.alert_level = alert_level + new_alert.update_alert_details(alert_details) + new_alert.created = timezone.now() + new_alert.updated = timezone.now() + new_alert.active = True + + new_alert.save() + else: + last_alert.updated = timezone.now() + last_alert.update_alert_details(alert_details) + + last_alert.save() diff --git a/templates/pdk_wearable_withings_device_table_template.html b/templates/pdk_wearable_withings_device_table_template.html new file mode 100644 index 0000000..df7fba5 --- /dev/null +++ b/templates/pdk_wearable_withings_device_table_template.html @@ -0,0 +1,162 @@ +{% load mathfilters %} +{% load passive_data_kit %} + + + diff --git a/templatetags/passive_data_kit.py b/templatetags/passive_data_kit.py index 42dbc62..7fc052f 100644 --- a/templatetags/passive_data_kit.py +++ b/templatetags/passive_data_kit.py @@ -400,3 +400,7 @@ def render(self, context): pass return generator + +@register.filter("to_datetime") +def to_datetime(value): + return arrow.get(value).datetime