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