Skip to content

Commit 21af205

Browse files
authored
Merge pull request #100 from GeriLife/add-work-factories
Add caregiver role and work model factories, enhance data generation, and improve visualizations
2 parents af97d15 + c75c6d2 commit 21af205

File tree

11 files changed

+284
-32
lines changed

11 files changed

+284
-32
lines changed

caregivers/factories.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import factory
2+
from factory import Sequence
3+
4+
from .models import CaregiverRole
5+
6+
7+
class CaregiverRoleFactory(factory.django.DjangoModelFactory):
8+
class Meta:
9+
model = CaregiverRole
10+
django_get_or_create = ("name",)
11+
12+
name = Sequence(lambda n: f"Caregiver Role {n}")

common/management/commands/make_fake_data.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,53 @@
33
import datetime
44
import random
55

6+
from caregivers.factories import CaregiverRoleFactory
67
from homes.factories import HomeFactory
78
from metrics.models import ResidentActivity
89
from residents.factories import ResidentFactory, ResidencyFactory
10+
from work.factories import WorkTypeFactory, WorkFactory
911

1012
NUM_HOMES = 5
1113
NUM_RESIDENTS_PER_HOME = range(5, 10)
1214
NUM_ACTIVITIES_PER_RESIDENT = range(10, 120)
15+
NUM_WORK_ENTRIES = range(10, 30)
1316
ACTIVITY_DAYS_AGO = range(0, 90)
1417
ACTIVITY_MINUTES = range(30, 120)
18+
WORK_MINUTES = range(30, 480)
19+
20+
# Predefined caregiver roles
21+
CAREGIVER_ROLES = ["Nurse", "Practical Nurse", "Staff", "Volunteer"]
22+
23+
# Predefined work types
24+
WORK_TYPES = ["Medication", "Cooking", "Cleaning", "Recreation", "Hygiene", "Wellness"]
1525

1626

1727
class Command(BaseCommand):
18-
help = "Creates fake homes, residents, residencies, and resident activities."
28+
help = "Creates fake homes, residents, residencies, resident activities, caregiver roles, and work."
1929

2030
def handle(self, *args, **options):
2131
with transaction.atomic():
32+
self.stdout.write("Creating caregiver roles...")
33+
caregiver_roles = []
34+
for role_name in CAREGIVER_ROLES:
35+
role = CaregiverRoleFactory(name=role_name)
36+
caregiver_roles.append(role)
37+
38+
self.stdout.write("Creating work types...")
39+
work_types = []
40+
for work_type_name in WORK_TYPES:
41+
work_type = WorkTypeFactory(name=work_type_name)
42+
work_types.append(work_type)
43+
44+
self.stdout.write("Creating homes...")
2245
homes = HomeFactory.create_batch(NUM_HOMES)
2346
today = datetime.date.today()
2447

2548
for home in homes:
2649
num_residents = random.choice(NUM_RESIDENTS_PER_HOME)
50+
self.stdout.write(
51+
f"Creating {num_residents} residents for {home.name}...",
52+
)
2753
residents = ResidentFactory.create_batch(num_residents)
2854

2955
residencies = [
@@ -59,4 +85,17 @@ def handle(self, *args, **options):
5985

6086
ResidentActivity.objects.bulk_create(all_activities)
6187

88+
# Create work entries for this home
89+
self.stdout.write(f"Creating work entries for {home.name}...")
90+
num_work_entries = random.choice(NUM_WORK_ENTRIES)
91+
for _ in range(num_work_entries):
92+
WorkFactory.create(
93+
home=home,
94+
type=random.choice(work_types),
95+
caregiver_role=random.choice(caregiver_roles),
96+
date=today
97+
- datetime.timedelta(days=random.choice(ACTIVITY_DAYS_AGO)),
98+
duration_minutes=random.choice(WORK_MINUTES),
99+
)
100+
62101
self.stdout.write(self.style.SUCCESS("Successfully created fake data"))

homes/charts.py

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from django.db.models import Sum
1+
from django.db.models import Sum, ExpressionWrapper, FloatField
22
from django.utils.translation import gettext as _
33

44
import pandas as pd
55
import plotly.express as px
6-
from core.constants import DAY_MILLISECONDS
6+
from core.constants import DAY_MILLISECONDS, HOUR_MINUTES
77
from homes.models import Home
88

99
from homes.queries import (
@@ -76,7 +76,12 @@ def prepare_work_by_type_chart(home: Home) -> str:
7676
work_by_type = list(
7777
home.work_performed.values("type__name")
7878
.order_by("type__name")
79-
.annotate(total_hours=Sum("duration_hours")),
79+
.annotate(
80+
total_hours=ExpressionWrapper(
81+
Sum("duration_minutes") / HOUR_MINUTES,
82+
output_field=FloatField(),
83+
),
84+
),
8085
)
8186

8287
work_by_type_chart = px.bar(
@@ -88,16 +93,30 @@ def prepare_work_by_type_chart(home: Home) -> str:
8893
"type__name": _("Type of work"),
8994
"total_hours": _("Total hours"),
9095
},
91-
).to_html()
92-
return work_by_type_chart
96+
template="plotly_dark",
97+
)
98+
99+
# Set plot background/paper color to transparent
100+
work_by_type_chart.update_layout(
101+
plot_bgcolor="rgba(0, 0, 0, 0)",
102+
paper_bgcolor="rgba(0, 0, 0, 0)",
103+
font_color="#FFFFFF",
104+
)
105+
106+
return work_by_type_chart.to_html()
93107

94108

95109
def prepare_work_by_caregiver_role_chart(home: Home) -> str:
96110
"""Prepare the work hours by caregiver role chart."""
97111
work_by_caregiver_role = list(
98112
home.work_performed.values("caregiver_role__name")
99113
.order_by("caregiver_role__name")
100-
.annotate(total_hours=Sum("duration_hours")),
114+
.annotate(
115+
total_hours=ExpressionWrapper(
116+
Sum("duration_minutes") / HOUR_MINUTES,
117+
output_field=FloatField(),
118+
),
119+
),
101120
)
102121

103122
work_by_caregiver_role_chart = px.bar(
@@ -109,9 +128,17 @@ def prepare_work_by_caregiver_role_chart(home: Home) -> str:
109128
"caregiver_role__name": _("Caregiver role"),
110129
"total_hours": _("Total hours"),
111130
},
112-
).to_html()
131+
template="plotly_dark",
132+
)
113133

114-
return work_by_caregiver_role_chart
134+
# Set plot background/paper color to transparent
135+
work_by_caregiver_role_chart.update_layout(
136+
plot_bgcolor="rgba(0, 0, 0, 0)",
137+
paper_bgcolor="rgba(0, 0, 0, 0)",
138+
font_color="#FFFFFF",
139+
)
140+
141+
return work_by_caregiver_role_chart.to_html()
115142

116143

117144
def prepare_daily_work_percent_by_caregiver_role_and_type_chart(home: Home) -> str:
@@ -134,6 +161,7 @@ def prepare_daily_work_percent_by_caregiver_role_and_type_chart(home: Home) -> s
134161
},
135162
# Add numeric text on bars
136163
text_auto=True,
164+
template="plotly_dark",
137165
)
138166

139167
# Format y-axis as percentages
@@ -149,6 +177,19 @@ def prepare_daily_work_percent_by_caregiver_role_and_type_chart(home: Home) -> s
149177
width=DAY_MILLISECONDS,
150178
)
151179

180+
# Set plot background/paper color to transparent
181+
daily_work_percent_by_caregiver_role_and_type_chart.update_layout(
182+
plot_bgcolor="rgba(0, 0, 0, 0)",
183+
paper_bgcolor="rgba(0, 0, 0, 0)",
184+
font_color="#FFFFFF",
185+
)
186+
187+
# Remove individual y-axis labels and add a single global one
188+
daily_work_percent_by_caregiver_role_and_type_chart.update_yaxes(title_text="")
189+
daily_work_percent_by_caregiver_role_and_type_chart.update_layout(
190+
yaxis_title=_("Work percent"),
191+
)
192+
152193
return daily_work_percent_by_caregiver_role_and_type_chart.to_html()
153194

154195

@@ -169,6 +210,7 @@ def prepare_home_work_percent_by_caregiver_role_chart(home: Home) -> str:
169210
"home_name": "",
170211
},
171212
text_auto=True,
213+
template="plotly_dark",
172214
)
173215

174216
home_work_percent_by_caregiver_role_chart.update_layout(
@@ -181,7 +223,9 @@ def prepare_home_work_percent_by_caregiver_role_chart(home: Home) -> str:
181223
"pad": 0,
182224
},
183225
plot_bgcolor="rgba(0, 0, 0, 0)",
226+
paper_bgcolor="rgba(0, 0, 0, 0)",
184227
showlegend=False,
228+
font_color="#FFFFFF",
185229
xaxis={
186230
"tickformat": ",.0%",
187231
},
@@ -213,9 +257,17 @@ def prepare_work_percent_by_caregiver_role_and_type_chart(
213257
"work_type": _("Type of work"),
214258
},
215259
text_auto=True,
260+
template="plotly_dark",
216261
)
217262
work_percent_by_caregiver_role_and_type_chart.layout.yaxis.tickformat = ",.0%"
218263

264+
# Set plot background/paper color to transparent
265+
work_percent_by_caregiver_role_and_type_chart.update_layout(
266+
plot_bgcolor="rgba(0, 0, 0, 0)",
267+
paper_bgcolor="rgba(0, 0, 0, 0)",
268+
font_color="#FFFFFF",
269+
)
270+
219271
return work_percent_by_caregiver_role_and_type_chart.to_html()
220272

221273

@@ -234,6 +286,14 @@ def prepare_work_by_caregiver_role_and_type_chart(
234286
"total_hours": _("Total hours"),
235287
"work_type": _("Type of work"),
236288
},
289+
template="plotly_dark",
290+
)
291+
292+
# Set plot background/paper color to transparent
293+
work_by_caregiver_role_and_type_chart.update_layout(
294+
plot_bgcolor="rgba(0, 0, 0, 0)",
295+
paper_bgcolor="rgba(0, 0, 0, 0)",
296+
font_color="#FFFFFF",
237297
)
238298

239299
return work_by_caregiver_role_and_type_chart.to_html()

homes/queries.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def get_daily_total_hours_by_role_and_work_type_with_percent(home_id):
2222
date,
2323
caregiver_role.name as role_name,
2424
work_type.name as work_type,
25-
sum(duration_hours) as daily_total_hours
25+
sum(duration_minutes) / 60.0 as daily_total_hours
2626
from work
2727
left join work_type on type_id = work_type.id
2828
left join caregiver_role on caregiver_role_id = caregiver_role.id
@@ -56,7 +56,7 @@ def get_total_hours_by_role_and_work_type_with_percent(home_id):
5656
select
5757
caregiver_role.name as role_name,
5858
work_type.name as work_type,
59-
sum(duration_hours) as total_hours
59+
sum(duration_minutes) / 60.0 as total_hours
6060
from work
6161
left join work_type on type_id = work_type.id
6262
left join caregiver_role on caregiver_role_id = caregiver_role.id
@@ -90,7 +90,7 @@ def get_home_total_hours_by_role_with_percent(home_id):
9090
select
9191
home.name as home_name,
9292
caregiver_role.name as role_name,
93-
CAST(sum(duration_hours) as FLOAT) as total_hours
93+
CAST(sum(duration_minutes) / 60.0 as FLOAT) as total_hours
9494
from work
9595
left join home on home_id = home.id
9696
left join caregiver_role on caregiver_role_id = caregiver_role.id

homes/templates/homes/home_detail.html

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,16 @@ <h2 class="mt-3">{% translate "Current Residents" %}</h2>
6767
</table>
6868
{% endif %}
6969

70-
{% include "homes/home_detail_charts.html" %}
71-
7270
<div class="row">
73-
<!-- only load analytics charts if work has been recorded -->
71+
<!-- only load analytics charts if activities have been recorded -->
7472
<h2>
75-
{% translate "Work" %}
73+
{% translate "Activities" %}
7674
</h2>
7775
{% if work_has_been_recorded %}
76+
<p>{% translate "Activities recorded in the past 30 days" %}</p>
7877
{% include "homes/home_detail_charts.html" %}
7978
{% else %}
80-
<p>{% translate "No work has been recorded yet." %}</p>
79+
<p>{% translate "No activities have been recorded yet." %}</p>
8180
{% endif %}
8281
</div>
8382
</div>

homes/templates/homes/home_detail_charts.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{% load i18n %}
2+
13
<div class="row">
24
{{ activity_counts_by_resident_and_activity_type_chart|safe }}
35
</div>
@@ -17,6 +19,10 @@
1719
</div>
1820
</div>
1921

22+
<h2>{% translate "Work" %}</h2>
23+
24+
<p>{% translate "Work recorded in the past 30 days." %}</p>
25+
2026
<div class="row">
2127
{{ daily_work_percent_by_caregiver_role_and_type_chart|safe }}
2228
</div>

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dependencies = [
1818
"plotly>=5.18.0,<6.0",
1919
"psycopg2-binary>=2.9.9,<3.0",
2020
"shortuuid>=1.0.11,<2.0",
21+
"statsmodels>=0.14.4",
2122
"whitenoise>=6.6.0,<7.0",
2223
]
2324

0 commit comments

Comments
 (0)