Coverage for tasks/khandaker_mojo_medical.py: 70%
132 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/khandaker_mojo_medical.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, Tuple, Type
33from sqlalchemy.ext.declarative import DeclarativeMeta
34from sqlalchemy.sql.sqltypes import Date, Float, Integer, UnicodeText
36from camcops_server.cc_modules.cc_constants import CssClass
37from camcops_server.cc_modules.cc_html import tr_qa
38from camcops_server.cc_modules.cc_request import CamcopsRequest
39from camcops_server.cc_modules.cc_sqla_coltypes import (
40 BoolColumn,
41 CamcopsColumn,
42 ZERO_TO_TWO_CHECKER,
43)
44from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin
47class KhandakerMojoMedicalMetaclass(DeclarativeMeta):
48 # noinspection PyInitNewSignature
49 def __init__(
50 cls: Type["KhandakerMojoMedical"],
51 name: str,
52 bases: Tuple[Type, ...],
53 classdict: Dict[str, Any],
54 ) -> None:
55 setattr(
56 cls,
57 cls.FN_DIAGNOSIS,
58 CamcopsColumn(
59 cls.FN_DIAGNOSIS,
60 Integer,
61 permitted_value_checker=ZERO_TO_TWO_CHECKER,
62 comment=(
63 "Diagnosis (0 Rheumatoid Arthritis, "
64 "1 Ankylosing Spondylitis, 2 Sjögren’s Syndrome)"
65 ),
66 ),
67 )
68 setattr(
69 cls,
70 cls.FN_DIAGNOSIS_DATE,
71 CamcopsColumn(
72 cls.FN_DIAGNOSIS_DATE,
73 Date,
74 comment=(
75 "Date of first diagnosis (may be approx from "
76 "'duration of illness (years))'"
77 ),
78 ),
79 )
80 setattr(
81 cls,
82 cls.FN_DIAGNOSIS_DATE_APPROXIMATE,
83 BoolColumn(
84 cls.FN_DIAGNOSIS_DATE_APPROXIMATE,
85 comment="True if diagnosis date was derived from duration",
86 ),
87 )
88 setattr(
89 cls,
90 cls.FN_HAS_FIBROMYALGIA,
91 BoolColumn(
92 cls.FN_HAS_FIBROMYALGIA,
93 comment="Do you have a diagnosis of fibromyalgia?",
94 ),
95 )
96 setattr(
97 cls,
98 cls.FN_IS_PREGNANT,
99 BoolColumn(
100 cls.FN_IS_PREGNANT,
101 comment=(
102 "Are you, or is there any possibility that you might "
103 "be pregnant?"
104 ),
105 ),
106 )
107 setattr(
108 cls,
109 cls.FN_HAS_INFECTION_PAST_MONTH,
110 BoolColumn(
111 cls.FN_HAS_INFECTION_PAST_MONTH,
112 comment=(
113 "Do you currently have an infection, or had "
114 "treatment for an infection (e.g antibiotics) "
115 "in the past month?"
116 ),
117 ),
118 )
119 setattr(
120 cls,
121 cls.FN_HAD_INFECTION_TWO_MONTHS_PRECEDING,
122 BoolColumn(
123 cls.FN_HAD_INFECTION_TWO_MONTHS_PRECEDING,
124 comment=(
125 "Have you had an infection, or had treatment for "
126 "an infection (e.g antibiotics) in the 2 months "
127 "preceding last month?"
128 ),
129 constraint_name="ck_kh2mm_had_infection",
130 ),
131 )
132 setattr(
133 cls,
134 cls.FN_HAS_ALCOHOL_SUBSTANCE_DEPENDENCE,
135 BoolColumn(
136 cls.FN_HAS_ALCOHOL_SUBSTANCE_DEPENDENCE,
137 comment=(
138 "Do you have a current diagnosis of alcohol or "
139 "substance dependence?"
140 ),
141 constraint_name="ck_kh2mm_has_alcohol",
142 ),
143 )
144 setattr(
145 cls,
146 cls.FN_SMOKING_STATUS,
147 CamcopsColumn(
148 cls.FN_SMOKING_STATUS,
149 Integer,
150 permitted_value_checker=ZERO_TO_TWO_CHECKER,
151 comment=(
152 "What is your smoking status? (0 Never smoked, "
153 "1 Ex-smoker, 2 Current smoker)"
154 ),
155 ),
156 )
157 setattr(
158 cls,
159 cls.FN_ALCOHOL_UNITS_PER_WEEK,
160 CamcopsColumn(
161 cls.FN_ALCOHOL_UNITS_PER_WEEK,
162 Float,
163 comment=(
164 "How much alcohol do you drink per week? (medium "
165 "glass of wine = 2 units, pint of beer at 4.5% = "
166 "2.5 units, 25ml of spirits at 40% = 1 unit)"
167 ),
168 ),
169 )
170 setattr(
171 cls,
172 cls.FN_DEPRESSION,
173 BoolColumn(
174 cls.FN_DEPRESSION,
175 comment=(
176 "Have you had any of the following conditions "
177 "diagnosed by a doctor?"
178 ),
179 ),
180 )
181 setattr(
182 cls,
183 cls.FN_BIPOLAR_DISORDER,
184 BoolColumn(
185 cls.FN_BIPOLAR_DISORDER,
186 comment=(
187 "Have you had any of the following conditions "
188 "diagnosed by a doctor?"
189 ),
190 ),
191 )
192 setattr(
193 cls,
194 cls.FN_SCHIZOPHRENIA,
195 BoolColumn(
196 cls.FN_SCHIZOPHRENIA,
197 comment=(
198 "Have you had any of the following conditions "
199 "diagnosed by a doctor?"
200 ),
201 ),
202 )
203 setattr(
204 cls,
205 cls.FN_AUTISM,
206 BoolColumn(
207 cls.FN_AUTISM,
208 comment=(
209 "Have you had any of the following conditions "
210 "diagnosed by a doctor?"
211 ),
212 ),
213 )
214 setattr(
215 cls,
216 cls.FN_PTSD,
217 BoolColumn(
218 cls.FN_PTSD,
219 comment=(
220 "Have you had any of the following conditions "
221 "diagnosed by a doctor?"
222 ),
223 ),
224 )
225 setattr(
226 cls,
227 cls.FN_ANXIETY,
228 BoolColumn(
229 cls.FN_ANXIETY,
230 comment=(
231 "Have you had any of the following conditions "
232 "diagnosed by a doctor?"
233 ),
234 ),
235 )
236 setattr(
237 cls,
238 cls.FN_PERSONALITY_DISORDER,
239 BoolColumn(
240 cls.FN_PERSONALITY_DISORDER,
241 comment=(
242 "Have you had any of the following conditions "
243 "diagnosed by a doctor?"
244 ),
245 ),
246 )
247 setattr(
248 cls,
249 cls.FN_INTELLECTUAL_DISABILITY,
250 BoolColumn(
251 cls.FN_INTELLECTUAL_DISABILITY,
252 comment=(
253 "Have you had any of the following conditions "
254 "diagnosed by a doctor?"
255 ),
256 ),
257 )
258 setattr(
259 cls,
260 cls.FN_OTHER_MENTAL_ILLNESS,
261 BoolColumn(
262 cls.FN_OTHER_MENTAL_ILLNESS,
263 comment=(
264 "Have you had any of the following conditions "
265 "diagnosed by a doctor?"
266 ),
267 ),
268 )
269 setattr(
270 cls,
271 cls.FN_OTHER_MENTAL_ILLNESS_DETAILS,
272 CamcopsColumn(
273 cls.FN_OTHER_MENTAL_ILLNESS_DETAILS,
274 UnicodeText,
275 comment="If other, please list here",
276 ),
277 )
278 setattr(
279 cls,
280 cls.FN_HOSPITALISED_IN_LAST_YEAR,
281 BoolColumn(
282 cls.FN_HOSPITALISED_IN_LAST_YEAR,
283 comment=(
284 "Have you had a physical or mental illness "
285 "requiring hospitalisation in the previous 12 "
286 "months?"
287 ),
288 ),
289 )
290 setattr(
291 cls,
292 cls.FN_HOSPITALISATION_DETAILS,
293 CamcopsColumn(
294 cls.FN_HOSPITALISATION_DETAILS,
295 UnicodeText,
296 comment=(
297 "If yes, please list here (name of illness, number "
298 "of hospitilisations and duration):"
299 ),
300 ),
301 )
302 setattr(
303 cls,
304 cls.FN_FAMILY_DEPRESSION,
305 BoolColumn(
306 cls.FN_FAMILY_DEPRESSION,
307 comment=(
308 "Has anyone in your immediate family "
309 "(parents, siblings or children) had any of the "
310 "following conditions diagnosed by a doctor?"
311 ),
312 ),
313 )
314 setattr(
315 cls,
316 cls.FN_FAMILY_BIPOLAR_DISORDER,
317 BoolColumn(
318 cls.FN_FAMILY_BIPOLAR_DISORDER,
319 comment=(
320 "Has anyone in your immediate family "
321 "(parents, siblings or children) had any of the "
322 "following conditions diagnosed by a doctor?"
323 ),
324 ),
325 )
326 setattr(
327 cls,
328 cls.FN_FAMILY_SCHIZOPHRENIA,
329 BoolColumn(
330 cls.FN_FAMILY_SCHIZOPHRENIA,
331 comment=(
332 "Has anyone in your immediate family "
333 "(parents, siblings or children) had any of the "
334 "following conditions diagnosed by a doctor?"
335 ),
336 ),
337 )
338 setattr(
339 cls,
340 cls.FN_FAMILY_AUTISM,
341 BoolColumn(
342 cls.FN_FAMILY_AUTISM,
343 comment=(
344 "Has anyone in your immediate family "
345 "(parents, siblings or children) had any of the "
346 "following conditions diagnosed by a doctor?"
347 ),
348 ),
349 )
350 setattr(
351 cls,
352 cls.FN_FAMILY_PTSD,
353 BoolColumn(
354 cls.FN_FAMILY_PTSD,
355 comment=(
356 "Has anyone in your immediate family "
357 "(parents, siblings or children) had any of the "
358 "following conditions diagnosed by a doctor?"
359 ),
360 ),
361 )
362 setattr(
363 cls,
364 cls.FN_FAMILY_ANXIETY,
365 BoolColumn(
366 cls.FN_FAMILY_ANXIETY,
367 comment=(
368 "Has anyone in your immediate family "
369 "(parents, siblings or children) had any of the "
370 "following conditions diagnosed by a doctor?"
371 ),
372 ),
373 )
374 setattr(
375 cls,
376 cls.FN_FAMILY_PERSONALITY_DISORDER,
377 BoolColumn(
378 cls.FN_FAMILY_PERSONALITY_DISORDER,
379 comment=(
380 "Has anyone in your immediate family "
381 "(parents, siblings or children) had any of the "
382 "following conditions diagnosed by a doctor?"
383 ),
384 ),
385 )
386 setattr(
387 cls,
388 cls.FN_FAMILY_INTELLECTUAL_DISABILITY,
389 BoolColumn(
390 cls.FN_FAMILY_INTELLECTUAL_DISABILITY,
391 comment=(
392 "Has anyone in your immediate family "
393 "(parents, siblings or children) had any of the "
394 "following conditions diagnosed by a doctor?"
395 ),
396 constraint_name="ck_kh2mm_fam_int_dis",
397 ),
398 )
399 setattr(
400 cls,
401 cls.FN_FAMILY_OTHER_MENTAL_ILLNESS,
402 BoolColumn(
403 cls.FN_FAMILY_OTHER_MENTAL_ILLNESS,
404 comment=(
405 "Has anyone in your immediate family "
406 "(parents, siblings or children) had any of the "
407 "following conditions diagnosed by a doctor?"
408 ),
409 ),
410 )
411 setattr(
412 cls,
413 cls.FN_FAMILY_OTHER_MENTAL_ILLNESS_DETAILS,
414 CamcopsColumn(
415 cls.FN_FAMILY_OTHER_MENTAL_ILLNESS_DETAILS,
416 UnicodeText,
417 comment="If other, please list here",
418 ),
419 )
421 super().__init__(name, bases, classdict)
424class KhandakerMojoMedical(
425 TaskHasPatientMixin, Task, metaclass=KhandakerMojoMedicalMetaclass
426):
427 """
428 Server implementation of the KhandakerMojoMedical task
429 """
431 __tablename__ = "khandaker_mojo_medical"
432 shortname = "Khandaker_MOJO_Medical"
433 info_filename_stem = "khandaker_mojo"
434 provides_trackers = False
436 # Section 1: General Information
437 FN_DIAGNOSIS = "diagnosis"
438 FN_DIAGNOSIS_DATE = "diagnosis_date"
439 FN_DIAGNOSIS_DATE_APPROXIMATE = "diagnosis_date_approximate"
440 FN_HAS_FIBROMYALGIA = "has_fibromyalgia"
441 FN_IS_PREGNANT = "is_pregnant"
442 FN_HAS_INFECTION_PAST_MONTH = "has_infection_past_month"
443 FN_HAD_INFECTION_TWO_MONTHS_PRECEDING = (
444 "had_infection_two_months_preceding"
445 )
446 FN_HAS_ALCOHOL_SUBSTANCE_DEPENDENCE = "has_alcohol_substance_dependence"
447 FN_SMOKING_STATUS = "smoking_status"
448 FN_ALCOHOL_UNITS_PER_WEEK = "alcohol_units_per_week"
450 # Section 2: Medical History
451 FN_DEPRESSION = "depression"
452 FN_BIPOLAR_DISORDER = "bipolar_disorder"
453 FN_SCHIZOPHRENIA = "schizophrenia"
454 FN_AUTISM = "autism"
455 FN_PTSD = "ptsd"
456 FN_ANXIETY = "anxiety"
457 FN_PERSONALITY_DISORDER = "personality_disorder"
458 FN_INTELLECTUAL_DISABILITY = "intellectual_disability"
459 FN_OTHER_MENTAL_ILLNESS = "other_mental_illness"
460 FN_OTHER_MENTAL_ILLNESS_DETAILS = "other_mental_illness_details"
461 FN_HOSPITALISED_IN_LAST_YEAR = "hospitalised_in_last_year"
462 FN_HOSPITALISATION_DETAILS = "hospitalisation_details"
464 # Section 3: Family history
465 FN_FAMILY_DEPRESSION = "family_depression"
466 FN_FAMILY_BIPOLAR_DISORDER = "family_bipolar_disorder"
467 FN_FAMILY_SCHIZOPHRENIA = "family_schizophrenia"
468 FN_FAMILY_AUTISM = "family_autism"
469 FN_FAMILY_PTSD = "family_ptsd"
470 FN_FAMILY_ANXIETY = "family_anxiety"
471 FN_FAMILY_PERSONALITY_DISORDER = "family_personality_disorder"
472 FN_FAMILY_INTELLECTUAL_DISABILITY = "family_intellectual_disability"
473 FN_FAMILY_OTHER_MENTAL_ILLNESS = "family_other_mental_illness"
474 FN_FAMILY_OTHER_MENTAL_ILLNESS_DETAILS = (
475 "family_other_mental_illness_details"
476 )
478 MANDATORY_FIELD_NAMES_1 = [
479 FN_DIAGNOSIS,
480 FN_DIAGNOSIS_DATE,
481 FN_HAS_FIBROMYALGIA,
482 FN_IS_PREGNANT,
483 FN_HAS_INFECTION_PAST_MONTH,
484 FN_HAD_INFECTION_TWO_MONTHS_PRECEDING,
485 FN_HAS_ALCOHOL_SUBSTANCE_DEPENDENCE,
486 FN_SMOKING_STATUS,
487 FN_ALCOHOL_UNITS_PER_WEEK,
488 ]
490 MANDATORY_FIELD_NAMES_2 = [
491 FN_DEPRESSION,
492 FN_BIPOLAR_DISORDER,
493 FN_SCHIZOPHRENIA,
494 FN_AUTISM,
495 FN_PTSD,
496 FN_ANXIETY,
497 FN_PERSONALITY_DISORDER,
498 FN_INTELLECTUAL_DISABILITY,
499 FN_OTHER_MENTAL_ILLNESS,
500 FN_HOSPITALISED_IN_LAST_YEAR,
501 ]
503 MANDATORY_FIELD_NAMES_3 = [
504 FN_FAMILY_DEPRESSION,
505 FN_FAMILY_BIPOLAR_DISORDER,
506 FN_FAMILY_SCHIZOPHRENIA,
507 FN_FAMILY_AUTISM,
508 FN_FAMILY_PTSD,
509 FN_FAMILY_ANXIETY,
510 FN_FAMILY_PERSONALITY_DISORDER,
511 FN_FAMILY_INTELLECTUAL_DISABILITY,
512 FN_FAMILY_OTHER_MENTAL_ILLNESS,
513 ]
515 MANDATORY_FIELD_NAMES = (
516 MANDATORY_FIELD_NAMES_1
517 + MANDATORY_FIELD_NAMES_2
518 + MANDATORY_FIELD_NAMES_3
519 )
521 # If the answer is yes to any of these, we need to have the details
522 DETAILS_FIELDS = {
523 FN_OTHER_MENTAL_ILLNESS: FN_OTHER_MENTAL_ILLNESS_DETAILS,
524 FN_HOSPITALISED_IN_LAST_YEAR: FN_HOSPITALISATION_DETAILS,
525 FN_FAMILY_OTHER_MENTAL_ILLNESS: FN_FAMILY_OTHER_MENTAL_ILLNESS_DETAILS,
526 }
528 MULTI_CHOICE_FIELD_NAMES = [FN_DIAGNOSIS, FN_SMOKING_STATUS]
530 @staticmethod
531 def longname(req: "CamcopsRequest") -> str:
532 _ = req.gettext
533 return _("Khandaker GM — MOJO — Medical questionnaire")
535 def is_complete(self) -> bool:
536 if self.any_fields_none(self.MANDATORY_FIELD_NAMES):
537 return False
539 if not self.field_contents_valid():
540 return False
542 for field_name, details_field_name in self.DETAILS_FIELDS.items():
543 if getattr(self, field_name):
544 if getattr(self, details_field_name) is None:
545 return False
547 return True
549 def get_task_html(self, req: CamcopsRequest) -> str:
550 heading_1 = self.xstring(req, "general_information_title")
552 rows_1 = ""
553 for field_name in self.MANDATORY_FIELD_NAMES_1:
554 rows_1 += self.get_rows(req, field_name)
556 heading_2 = self.xstring(req, "medical_history_title")
558 rows_2 = ""
559 for field_name in self.MANDATORY_FIELD_NAMES_2:
560 rows_2 += self.get_rows(req, field_name)
562 heading_3 = self.xstring(req, "family_history_title")
564 rows_3 = ""
565 for field_name in self.MANDATORY_FIELD_NAMES_3:
566 rows_3 += self.get_rows(req, field_name)
568 html = f"""
569 <div class="{CssClass.SUMMARY}">
570 <table class="{CssClass.SUMMARY}">
571 {self.get_is_complete_tr(req)}
572 </table>
573 </div>
574 <h3>{heading_1}</h3>
575 <table class="{CssClass.TASKDETAIL}">
576 <tr>
577 <th width="60%">Question</th>
578 <th width="40%">Answer</th>
579 </tr>
580 {rows_1}
581 </table>
582 <h3>{heading_2}</h3>
583 <table class="{CssClass.TASKDETAIL}">
584 <tr>
585 <th width="60%">Question</th>
586 <th width="40%">Answer</th>
587 </tr>
588 {rows_2}
589 </table>
590 <h3>{heading_3}</h3>
591 <table class="{CssClass.TASKDETAIL}">
592 <tr>
593 <th width="60%">Question</th>
594 <th width="40%">Answer</th>
595 </tr>
596 {rows_3}
597 </table>
598 """
600 return html
602 def get_rows(self, req: CamcopsRequest, field_name: str) -> str:
603 rows = ""
605 question_text = self.xstring(req, f"q_{field_name}")
606 answer = getattr(self, field_name)
608 answer_text = answer
610 if answer is not None and (
611 field_name in self.MULTI_CHOICE_FIELD_NAMES
612 ):
613 answer_text = self.xstring(req, f"{field_name}_{answer}")
615 rows += tr_qa(question_text, answer_text)
617 if answer and field_name in self.DETAILS_FIELDS:
618 details_field_name = self.DETAILS_FIELDS[field_name]
619 details_question_text = self.xstring(
620 req, f"q_{details_field_name}"
621 )
622 details_answer = getattr(self, details_field_name)
624 rows += tr_qa(details_question_text, details_answer)
626 if field_name == self.FN_DIAGNOSIS_DATE:
627 rows += tr_qa(
628 self.xstring(req, f"q_{self.FN_DIAGNOSIS_DATE_APPROXIMATE}"),
629 getattr(self, self.FN_DIAGNOSIS_DATE_APPROXIMATE),
630 )
632 return rows