Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Withings data source work
  • Loading branch information
audaciouscode committed Jun 18, 2017
1 parent 7736ecd commit c359ee1
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 0 deletions.
28 changes: 28 additions & 0 deletions 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',
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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)

@@ -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()
162 changes: 162 additions & 0 deletions templates/pdk_wearable_withings_device_table_template.html
@@ -0,0 +1,162 @@
{% load mathfilters %}
{% load passive_data_kit %}
<div id="withings_tabs" style="display: none;">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#activity" aria-controls="activity" role="tab" data-toggle="tab">Activity Measures</a></li>
<li role="presentation"><a href="#intraday" aria-controls="intraday" role="tab" data-toggle="tab">Intraday Activity</a></li>
<li role="presentation"><a href="#sleep" aria-controls="sleep" role="tab" data-toggle="tab">Sleep Measures</a></li>
<li role="presentation"><a href="#body" aria-controls="body" role="tab" data-toggle="tab">Body Measures</a></li>
</ul>

<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="activity">
<table id="activity_values_table" class="table-striped" data-toggle="table" data-sort-name="created" data-sort-order="desc" data-pagination="true" style="z-index: 10;">
<thead>
<tr>
<th data-sortable="true" data-field="created">Created</th>
<th data-sortable="true">Distance</th>
<th data-sortable="true">Steps</th>
<th data-sortable="true">Intense Duration</th>
<th data-sortable="true">Moderate Duration</th>
<th data-sortable="true">Soft Duration</th>
<th data-sortable="true">Active Calories</th>
<th data-sortable="true">Total Calories</th>
</tr>
</thead>
<tbody>
{% for row in activity_values %}
{% with props=row.fetch_properties %}
<tr>
<td>
<span style="display: none;">
{{ row.created.isoformat }}
</span>
{{ row.created }}
</td>
<td>{{ props.distance }}m</td>
<td>{{ props.steps }}</td>

<td>{{ props.intense_activity_duration }}</td>
<td>{{ props.moderate_activity_duration }}</td>
<td>{{ props.soft_activity_duration }}</td>

<td>{{ props.active_calories }}</td>
<td>{{ props.total_calories }}</td>
</tr>
{% endwith %}
{% endfor %}
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="intraday">
<table id="intraday_table" class="table-striped" data-toggle="table" data-sort-name="created" data-sort-order="desc" data-pagination="true" style="z-index: 10;">
<thead>
<tr>
<th data-sortable="true" data-field="created">Created</th>
<th data-sortable="true">Distance</th>
<th data-sortable="true">Steps</th>
<th data-sortable="true">Activity Duration</th>
<th data-sortable="true">Elevation Climbed</th>
</tr>
</thead>
<tbody>
{% for row in intraday_values %}
{% with props=row.fetch_properties %}
<tr>
<td>
{% with start_date=props.activity_start|to_datetime %}
<span style="display: none;">
{{ start_date.isoformat }}
</span>

{{ start_date }}
{% endwith %}
</td>
<td>{{ props.distance }}</td>
<td>{{ props.steps }}</td>
<td>{{ props.activity_duration }}</td>
<td>{{ props.elevation_climbed }}</td>
</tr>
{% endwith %}
{% endfor %}
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="sleep">
<table id="sleep_measures_table" class="table-striped" data-toggle="table" data-sort-name="created" data-sort-order="desc" data-pagination="true" style="z-index: 10;">
<thead>
<tr>
<th data-sortable="true" data-field="created">Created</th>
<th data-sortable="true">State</th>
<th data-sortable="true">Duration (Seconds)</th>
<th data-sortable="true">Measurement Device</th>
</tr>
</thead>
<tbody>
{% for row in sleep_values %}
{% with props=row.fetch_properties %}
<tr>
<td>
{% with start_date=props.start_date|to_datetime %}
<span style="display: none;">
{{ start_date.isoformat }}
</span>

{{ start_date }} -- {{ row.pk }}
{% endwith %}
</td>
<td>{{ props.state }}</td>
<td>{{ props.end_date|sub:props.start_date }}</td>

<td>{{ props.measurement_device }}</td>
</tr>
{% endwith %}
{% endfor %}
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="body">

<table id="body_table" class="table-striped" data-toggle="table" data-sort-name="created" data-sort-order="desc" data-pagination="true" style="z-index: 10;">
<thead>
<tr>
<th data-sortable="true" data-field="created">Created</th>
<th data-sortable="true">Type</th>
<th data-sortable="true">Category</th>
<th data-sortable="true">Value</th>
<th data-sortable="true">Status</th>
</tr>
</thead>
<tbody>
{% for row in body_values %}
{% with props=row.fetch_properties %}
<tr>
<td>
{% with start_date=props.measure_date|to_datetime %}
<span style="display: none;">
{{ start_date.isoformat }}
</span>

{{ start_date }}
{% endwith %}
</td>
<td>{{ props.measure_type }}</td>
<td>{{ props.measure_category }}</td>
<td>{{ props.measure_value }}</td>
<td>{{ props.measure_status }}</td>
</tr>
{% endwith %}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>

<script>
window.showValues = function() {
$("#withings_tabs").show();
};
</script>
4 changes: 4 additions & 0 deletions templatetags/passive_data_kit.py
Expand Up @@ -400,3 +400,7 @@ def render(self, context):
pass

return generator

@register.filter("to_datetime")
def to_datetime(value):
return arrow.get(value).datetime

0 comments on commit c359ee1

Please sign in to comment.