Skip to content

Commit

Permalink
Merge pull request #77 from GeriLife/monthly-grouped-activities-by-type
Browse files Browse the repository at this point in the history
Monthly grouped activities by type
  • Loading branch information
brylie authored Jan 2, 2024
2 parents 0ef65f5 + a957d46 commit 5a7abc8
Show file tree
Hide file tree
Showing 8 changed files with 495 additions and 167 deletions.
95 changes: 41 additions & 54 deletions common/management/commands/make_fake_data.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,62 @@
from django.core.management.base import BaseCommand
from django.db import transaction
import datetime
import random
from django.core.management.base import BaseCommand

from homes.factories import HomeFactory
from metrics.models import ResidentActivity
from residents.factories import ResidentFactory, ResidencyFactory


NUM_HOMES = 5
NUM_RESIDENTS_PER_HOME = range(5, 10)
NUM_ACTIVITIES_PER_RESIDENT = range(0, 14)
ACTIVITY_DAYS_AGO = range(0, 8)
NUM_ACTIVITIES_PER_RESIDENT = range(10, 120)
ACTIVITY_DAYS_AGO = range(0, 90)
ACTIVITY_MINUTES = range(30, 120)


class Command(BaseCommand):
help = "Creates fake homes, residents, residencies, and resident activities."

def handle(self, *args, **options):
"""Creates fake homes, residents, residencies, and resident
activities."""
homes = HomeFactory.create_batch(NUM_HOMES)
today = datetime.date.today()

homes_created = len(homes)
residents_created = 0
residencies_created = 0
activities_created = 0

for home in homes:
num_residents = random.choice(NUM_RESIDENTS_PER_HOME)
residents = ResidentFactory.create_batch(num_residents)
residents_created += len(residents)
with transaction.atomic():
homes = HomeFactory.create_batch(NUM_HOMES)
today = datetime.date.today()

# Create residencies
for resident in residents:
residency = ResidencyFactory.create(
resident=resident,
home=home,
)
residencies_created += 1
for home in homes:
num_residents = random.choice(NUM_RESIDENTS_PER_HOME)
residents = ResidentFactory.create_batch(num_residents)

# Create activities
num_activities = random.choice(NUM_ACTIVITIES_PER_RESIDENT)

for _ in range(num_activities):
# date within last N days
activity_date = today - datetime.timedelta(
days=random.choice(ACTIVITY_DAYS_AGO),
)
activity_type = random.choice(
ResidentActivity.ActivityTypeChoices.choices,
)[0]
activity_minutes = random.choice(ACTIVITY_MINUTES)
caregiver_role = random.choice(
ResidentActivity.CaregiverRoleChoices.choices,
)[0]

ResidentActivity.objects.create(
residencies = [
ResidencyFactory.create(
resident=resident,
activity_date=activity_date,
residency=residency,
home=home,
activity_type=activity_type,
activity_minutes=activity_minutes,
caregiver_role=caregiver_role,
# TODO: consider adding group_activity_id
)
activities_created += 1

self.stdout.write(f"Created {homes_created} fake homes.")
self.stdout.write(f"Created {residents_created} fake residents.")
self.stdout.write(f"Created {residencies_created} fake residencies.")
self.stdout.write(f"Created {activities_created} fake activities.")
for resident in residents
]

# Prepare and bulk create activities for all residents in this home
all_activities = []
for residency in residencies:
num_activities = random.choice(NUM_ACTIVITIES_PER_RESIDENT)
activities = [
ResidentActivity(
resident=residency.resident,
activity_date=today
- datetime.timedelta(days=random.choice(ACTIVITY_DAYS_AGO)),
home=home,
residency=residency, # Associate Residency with ResidentActivity
activity_type=random.choice(
ResidentActivity.ActivityTypeChoices.choices,
)[0],
activity_minutes=random.choice(ACTIVITY_MINUTES),
caregiver_role=random.choice(
ResidentActivity.CaregiverRoleChoices.choices,
)[0],
)
for _ in range(num_activities)
]
all_activities.extend(activities)

ResidentActivity.objects.bulk_create(all_activities)

self.stdout.write(self.style.SUCCESS("Successfully created fake data"))
60 changes: 58 additions & 2 deletions homes/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
from django.db.models import Sum
from django.utils.translation import gettext as _

import pandas as pd
import plotly.express as px

from metrics.models import ResidentActivity


def dictfetchall(cursor):
"""Return a list of dictionaries containing all rows from a database
Expand All @@ -25,21 +28,33 @@ def get_activity_counts_by_resident_and_activity_type(home_id):

result = dictfetchall(cursor)

return result
return pd.DataFrame(result)


def prepare_activity_counts_by_resident_and_activity_type_chart(home):
activity_counts_by_resident_and_activity_type = (
get_activity_counts_by_resident_and_activity_type(home.id)
)

# Create a mapping from the enum to localized labels
activity_type_mapping = {
choice.value: _(choice.label) for choice in ResidentActivity.ActivityTypeChoices
}

# Apply the mapping to localize the activity_type values
activity_counts_by_resident_and_activity_type[
"activity_type"
] = activity_counts_by_resident_and_activity_type["activity_type"].map(
activity_type_mapping,
)

activity_counts_by_resident_and_activity_type_chart = px.bar(
activity_counts_by_resident_and_activity_type,
x="activity_count",
y="resident_name",
color="activity_type",
orientation="h",
title=_("Resident Activity Count By Type"),
title=_("Resident activity count by type"),
labels={
"activity_count": _("Activity Count"),
"resident_name": _("Resident Name"),
Expand Down Expand Up @@ -335,3 +350,44 @@ def prepare_work_by_caregiver_role_and_type_charts(context):
)

return context


def prepare_monthly_activity_counts_by_type_chart(home):
monthly_activity_counts_by_type = home.monthly_activity_counts_by_type

# Create a mapping from the enum to localized labels
activity_type_mapping = {
choice.value: _(choice.label) for choice in ResidentActivity.ActivityTypeChoices
}

# Apply the mapping to localize the activity_type values
monthly_activity_counts_by_type["activity_type"] = monthly_activity_counts_by_type[
"activity_type"
].map(activity_type_mapping)

monthly_activity_counts_by_type_chart = px.bar(
monthly_activity_counts_by_type,
x="month",
y="count",
color="activity_type",
title=_("Monthly activity counts by type"),
labels={
"activity_type": _("Activity type"),
"activity_count": _("Activity count"),
},
)

# Set plot background/paper color to transparent
monthly_activity_counts_by_type_chart.update_layout(
plot_bgcolor="rgba(0, 0, 0, 0)",
paper_bgcolor="rgba(0, 0, 0, 0)",
# ensure text is visible on dark background
font_color="#FFFFFF",
# only display month on x-axis
xaxis={
"dtick": "M1",
"tickformat": "%b\n%Y",
},
)

return monthly_activity_counts_by_type_chart.to_html()
27 changes: 27 additions & 0 deletions homes/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
from typing import TYPE_CHECKING
from django.db.models import Count, Q, QuerySet
from django.db.models.functions import TruncMonth
from django.utils import timezone
from datetime import timedelta
from django.db import models
Expand Down Expand Up @@ -369,6 +370,32 @@ def resident_counts_by_activity_level_chart_data(self) -> list[dict]:

return chart_data

@property
def monthly_activity_counts_by_type(self) -> pd.DataFrame:
"""Returns a list of dictionaries of counts of activities grouped by
month and type."""

from metrics.models import ResidentActivity

today = timezone.now()
one_year_ago = today - timedelta(days=365)

activities = (
ResidentActivity.objects.filter(
activity_date__gte=one_year_ago,
home=self,
)
.annotate(
month=TruncMonth("activity_date"),
)
.values("month", "activity_type")
.order_by("month")
.annotate(count=Count("id"))
.distinct()
)

return pd.DataFrame(list(activities))


class HomeGroup(models.Model):
name = models.CharField(max_length=25)
Expand Down
4 changes: 4 additions & 0 deletions homes/templates/homes/home_detail_charts.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
{{ activity_counts_by_resident_and_activity_type_chart|safe }}
</div>

<div class="row">
{{ monthly_activity_counts_by_type_chart|safe }}
</div>

<div class="row">
{{ daily_work_percent_by_caregiver_role_and_type_chart|safe }}
</div>
Expand Down
7 changes: 5 additions & 2 deletions homes/templates/homes/home_residents_activity_percents.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{% load l10n %}
{% load custom_filters %}

<div class="bar-container">
{% comment %} round item value to nearest integer {% endcomment %}
{% for item in data %}
<span
class="bar bg-{{ item.activity_level_class}}"
style="width: {{ item.value|unlocalize }}%;"
aria-label="{{ item.activity_level_label }}: {{ item.value }}%"
title="{{ item.activity_level_label }}: {{ item.value }}%"></span>
aria-label="{{ item.activity_level_label }}: {{ item.value|floatformat:'0'}}%"
title="{{ item.activity_level_label }}: {{ item.value|floatformat:'0' }}%"></span>
{% endfor %}
</div>
11 changes: 9 additions & 2 deletions homes/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .charts import (
prepare_activity_counts_by_resident_and_activity_type_chart,
prepare_daily_work_percent_by_caregiver_role_and_type_chart,
prepare_monthly_activity_counts_by_type_chart,
prepare_work_by_caregiver_role_and_type_charts,
prepare_work_by_caregiver_role_chart,
prepare_work_by_type_chart,
Expand Down Expand Up @@ -57,6 +58,12 @@ def prepare_activity_charts(self, context):
"activity_counts_by_resident_and_activity_type_chart"
] = prepare_activity_counts_by_resident_and_activity_type_chart(home)

context[
"monthly_activity_counts_by_type_chart"
] = prepare_monthly_activity_counts_by_type_chart(home)

return context

def prepare_work_charts(self, context):
"""Prepare work charts and add them to the template context."""
home = context["home"]
Expand Down Expand Up @@ -87,7 +94,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:

# Only prepare charts if work has been recorded
if context["work_has_been_recorded"]:
self.prepare_work_charts(context)
context = self.prepare_work_charts(context)
if context["activity_has_been_recorded"]:
self.prepare_activity_charts(context)
context = self.prepare_activity_charts(context)
return context
Binary file modified locale/fi/LC_MESSAGES/django.mo
Binary file not shown.
Loading

0 comments on commit 5a7abc8

Please sign in to comment.