Coverage for tasks/dad.py : 42%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
3"""
4camcops_server/tasks/dad.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
12 CamCOPS is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 CamCOPS is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
25===============================================================================
27"""
29from typing import Any, Dict, Iterable, List, Tuple, Type
31from sqlalchemy.ext.declarative import DeclarativeMeta
32from sqlalchemy.sql.sqltypes import Integer
34from camcops_server.cc_modules.cc_constants import (
35 CssClass,
36 DATA_COLLECTION_UNLESS_UPGRADED_DIV,
37)
38from camcops_server.cc_modules.cc_html import (
39 answer,
40 subheading_spanning_two_columns,
41 tr,
42)
43from camcops_server.cc_modules.cc_request import CamcopsRequest
44from camcops_server.cc_modules.cc_sqla_coltypes import (
45 CamcopsColumn,
46 PermittedValueChecker,
47)
48from camcops_server.cc_modules.cc_summaryelement import SummaryElement
49from camcops_server.cc_modules.cc_task import (
50 Task,
51 TaskHasClinicianMixin,
52 TaskHasPatientMixin,
53 TaskHasRespondentMixin,
54)
56YES = 1
57NO = 0
58NA = -99
59YN_NA_CHECKER = PermittedValueChecker(permitted_values=[YES, NO, NA])
62# =============================================================================
63# DAD
64# =============================================================================
66class DadMetaclass(DeclarativeMeta):
67 # noinspection PyInitNewSignature
68 def __init__(cls: Type['Dad'],
69 name: str,
70 bases: Tuple[Type, ...],
71 classdict: Dict[str, Any]) -> None:
72 explan = f" ({YES} yes, {NO} no, {NA} not applicable)"
73 for colname in cls.ITEMS:
74 setattr(
75 cls,
76 colname,
77 CamcopsColumn(colname, Integer,
78 permitted_value_checker=YN_NA_CHECKER,
79 comment=colname + explan)
80 )
81 super().__init__(name, bases, classdict)
84class Dad(TaskHasPatientMixin, TaskHasRespondentMixin, TaskHasClinicianMixin,
85 Task,
86 metaclass=DadMetaclass):
87 """
88 Server implementation of the DAD task.
89 """
90 __tablename__ = "dad"
91 shortname = "DAD"
93 GROUPS = [
94 "hygiene",
95 "dressing",
96 "continence",
97 "eating",
98 "mealprep",
99 "telephone",
100 "outing",
101 "finance",
102 "medications",
103 "leisure"
104 ]
105 ITEMS = [
106 "hygiene_init_wash",
107 "hygiene_init_teeth",
108 "hygiene_init_hair",
109 "hygiene_plan_wash",
110 "hygiene_exec_wash",
111 "hygiene_exec_hair",
112 "hygiene_exec_teeth",
114 "dressing_init_dress",
115 "dressing_plan_clothing",
116 "dressing_plan_order",
117 "dressing_exec_dress",
118 "dressing_exec_undress",
120 "continence_init_toilet",
121 "continence_exec_toilet",
123 "eating_init_eat",
124 "eating_plan_utensils",
125 "eating_exec_eat",
127 "mealprep_init_meal",
128 "mealprep_plan_meal",
129 "mealprep_exec_meal",
131 "telephone_init_phone",
132 "telephone_plan_dial",
133 "telephone_exec_conversation",
134 "telephone_exec_message",
136 "outing_init_outing",
137 "outing_plan_outing",
138 "outing_exec_reach_destination",
139 "outing_exec_mode_transportation",
140 "outing_exec_return_with_shopping",
142 "finance_init_interest",
143 "finance_plan_pay_bills",
144 "finance_plan_organise_correspondence",
145 "finance_exec_handle_money",
147 "medications_init_medication",
148 "medications_exec_take_medications",
150 "leisure_init_interest_leisure",
151 "leisure_init_interest_chores",
152 "leisure_plan_chores",
153 "leisure_exec_complete_chores",
154 "leisure_exec_safe_at_home"
155 ]
157 @staticmethod
158 def longname(req: "CamcopsRequest") -> str:
159 _ = req.gettext
160 return _("Disability Assessment for Dementia")
162 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
163 d = self.get_score_dict()
164 s = self.standard_task_summary_fields()
165 for item in d:
166 s.extend([
167 SummaryElement(name=item + "_n",
168 coltype=Integer(),
169 value=d[item][0],
170 comment=item + " (numerator)"),
171 SummaryElement(name=item + "_d",
172 coltype=Integer(),
173 value=d[item][1],
174 comment=item + " (denominator)"),
175 ])
176 return s
178 # noinspection PyMethodOverriding
179 @staticmethod
180 def is_complete() -> bool:
181 return True
183 @classmethod
184 def get_items_activity(cls, activity: str) -> List[str]:
185 return [item for item in cls.ITEMS if item.startswith(activity)]
187 @classmethod
188 def get_items_activities(cls, activities: Iterable[str]) -> List[str]:
189 return [item for item in cls.ITEMS
190 if any(item.startswith(activity) for activity in activities)]
192 @classmethod
193 def get_items_phase(cls, phase: str) -> List[str]:
194 return [item for item in cls.ITEMS if phase in item]
196 def get_score(self, fields: List[str]) -> Tuple[int, int]:
197 score = self.count_where(fields, [YES])
198 possible = self.count_wherenot(fields, [None, NA])
199 return score, possible
201 def get_score_dict(self) -> Dict:
202 total = self.get_score(self.ITEMS)
203 hygiene = self.get_score(self.get_items_activity('hygiene'))
204 dressing = self.get_score(self.get_items_activity('dressing'))
205 continence = self.get_score(self.get_items_activity('continence'))
206 eating = self.get_score(self.get_items_activity('eating'))
207 badl = self.get_score(self.get_items_activities(
208 ['hygiene', 'dressing', 'continence', 'eating']))
209 mealprep = self.get_score(self.get_items_activity('mealprep'))
210 telephone = self.get_score(self.get_items_activity('telephone'))
211 outing = self.get_score(self.get_items_activity('outing'))
212 finance = self.get_score(self.get_items_activity('finance'))
213 medications = self.get_score(self.get_items_activity('medications'))
214 leisure = self.get_score(self.get_items_activity('leisure'))
215 iadl = self.get_score(self.get_items_activities(
216 ['mealprep', 'telephone', 'outing', 'finance',
217 'medications', 'leisure']))
218 initiation = self.get_score(self.get_items_phase('init'))
219 planning = self.get_score(self.get_items_phase('plan'))
220 execution = self.get_score(self.get_items_phase('exec'))
221 # n for numerator, d for denominator
222 return dict(
223 total=total,
224 hygiene=hygiene,
225 dressing=dressing,
226 continence=continence,
227 eating=eating,
228 badl=badl,
229 mealprep=mealprep,
230 telephone=telephone,
231 outing=outing,
232 finance=finance,
233 medications=medications,
234 leisure=leisure,
235 iadl=iadl,
236 initiation=initiation,
237 planning=planning,
238 execution=execution,
239 )
241 @staticmethod
242 def report_score(score_tuple: Tuple[int, int]) -> str:
243 return f"{answer(score_tuple[0])} / {score_tuple[1]}"
245 def report_answer(self, field: str) -> str:
246 value = getattr(self, field)
247 if value == YES:
248 text = "Yes (1)"
249 elif value == NO:
250 text = "No (0)"
251 elif value == NA:
252 text = "N/A"
253 else:
254 text = None
255 return answer(text)
257 def get_task_html(self, req: CamcopsRequest) -> str:
258 d = self.get_score_dict()
259 h = f"""
260 <div class="{CssClass.SUMMARY}">
261 <table class="{CssClass.SUMMARY}">
262 {self.get_is_complete_tr(req)}
263 <tr>
264 <td>Total</td>
265 <td>{self.report_score(d['total'])}</td>
266 </tr>
267 <tr>
268 <td>Activity: hygiene</td>
269 <td>{self.report_score(d['hygiene'])}</td>
270 </tr>
271 <tr>
272 <td>Activity: dressing</td>
273 <td>{self.report_score(d['dressing'])}</td>
274 </tr>
275 <tr>
276 <td>Activity: continence</td>
277 <td>{self.report_score(d['continence'])}</td>
278 </tr>
279 <tr>
280 <td>Activity: eating</td>
281 <td>{self.report_score(d['eating'])}</td>
282 </tr>
283 <tr>
284 <td>Basic activities of daily living (BADLs) (hygiene,
285 dressing, continence, eating)</td>
286 <td>{self.report_score(d['badl'])}</td>
287 </tr>
288 <tr>
289 <td>Activity: meal preparation</td>
290 <td>{self.report_score(d['mealprep'])}</td>
291 </tr>
292 <tr>
293 <td>Activity: telephone</td>
294 <td>{self.report_score(d['telephone'])}</td>
295 </tr>
296 <tr>
297 <td>Activity: outings</td>
298 <td>{self.report_score(d['outing'])}</td>
299 </tr>
300 <tr>
301 <td>Activity: finance</td>
302 <td>{self.report_score(d['finance'])}</td>
303 </tr>
304 <tr>
305 <td>Activity: medications</td>
306 <td>{self.report_score(d['medications'])}</td>
307 </tr>
308 <tr>
309 <td>Activity: leisure</td>
310 <td>{self.report_score(d['leisure'])}</td>
311 </tr>
312 <tr>
313 <td>Instrumental activities of daily living (IADLs)
314 (meal prep., telephone, outings, finance, medications,
315 leisure)</td>
316 <td>{self.report_score(d['iadl'])}</td>
317 </tr>
318 <tr>
319 <td>Phase: initiation</td>
320 <td>{self.report_score(d['initiation'])}</td>
321 </tr>
322 <tr>
323 <td>Phase: planning/organisation</td>
324 <td>{self.report_score(d['planning'])}</td>
325 </tr>
326 <tr>
327 <td>Phase: execution/performance</td>
328 <td>{self.report_score(d['execution'])}</td>
329 </tr>
330 </table>
331 </div>
332 <table class="{CssClass.TASKDETAIL}">
333 <tr>
334 <th width="50%">Question (I = initiation, P = planning,
335 E = execution)</th>
336 <th width="50%">Answer</th>
337 </tr>
338 """
339 for group in self.GROUPS:
340 h += subheading_spanning_two_columns(self.wxstring(req, group))
341 for item in self.ITEMS:
342 if not item.startswith(group):
343 continue
344 q = self.wxstring(req, item)
345 if '_init_' in item:
346 q += " (I)"
347 elif '_plan_' in item:
348 q += " (P)"
349 elif '_exec_' in item:
350 q += " (E)"
351 else:
352 # Shouldn't happen
353 q += " (?)"
354 h += tr(q, self.report_answer(item))
355 h += f"""
356 </table>
357 {DATA_COLLECTION_UNLESS_UPGRADED_DIV}
358 """
359 return h