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