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

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/khandaker_mojo_medical.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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/>. 

25 

26=============================================================================== 

27 

28""" 

29 

30from typing import Any, Dict, Tuple, Type 

31 

32 

33from sqlalchemy.ext.declarative import DeclarativeMeta 

34from sqlalchemy.sql.sqltypes import Date, Float, Integer, UnicodeText 

35 

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 

45 

46 

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 ) 

420 

421 super().__init__(name, bases, classdict) 

422 

423 

424class KhandakerMojoMedical( 

425 TaskHasPatientMixin, Task, metaclass=KhandakerMojoMedicalMetaclass 

426): 

427 """ 

428 Server implementation of the KhandakerMojoMedical task 

429 """ 

430 

431 __tablename__ = "khandaker_mojo_medical" 

432 shortname = "Khandaker_MOJO_Medical" 

433 info_filename_stem = "khandaker_mojo" 

434 provides_trackers = False 

435 

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" 

449 

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" 

463 

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 ) 

477 

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 ] 

489 

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 ] 

502 

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 ] 

514 

515 MANDATORY_FIELD_NAMES = ( 

516 MANDATORY_FIELD_NAMES_1 

517 + MANDATORY_FIELD_NAMES_2 

518 + MANDATORY_FIELD_NAMES_3 

519 ) 

520 

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 } 

527 

528 MULTI_CHOICE_FIELD_NAMES = [FN_DIAGNOSIS, FN_SMOKING_STATUS] 

529 

530 @staticmethod 

531 def longname(req: "CamcopsRequest") -> str: 

532 _ = req.gettext 

533 return _("Khandaker GM — MOJO — Medical questionnaire") 

534 

535 def is_complete(self) -> bool: 

536 if self.any_fields_none(self.MANDATORY_FIELD_NAMES): 

537 return False 

538 

539 if not self.field_contents_valid(): 

540 return False 

541 

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 

546 

547 return True 

548 

549 def get_task_html(self, req: CamcopsRequest) -> str: 

550 heading_1 = self.xstring(req, "general_information_title") 

551 

552 rows_1 = "" 

553 for field_name in self.MANDATORY_FIELD_NAMES_1: 

554 rows_1 += self.get_rows(req, field_name) 

555 

556 heading_2 = self.xstring(req, "medical_history_title") 

557 

558 rows_2 = "" 

559 for field_name in self.MANDATORY_FIELD_NAMES_2: 

560 rows_2 += self.get_rows(req, field_name) 

561 

562 heading_3 = self.xstring(req, "family_history_title") 

563 

564 rows_3 = "" 

565 for field_name in self.MANDATORY_FIELD_NAMES_3: 

566 rows_3 += self.get_rows(req, field_name) 

567 

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 """ 

599 

600 return html 

601 

602 def get_rows(self, req: CamcopsRequest, field_name: str) -> str: 

603 rows = "" 

604 

605 question_text = self.xstring(req, f"q_{field_name}") 

606 answer = getattr(self, field_name) 

607 

608 answer_text = answer 

609 

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}") 

614 

615 rows += tr_qa(question_text, answer_text) 

616 

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) 

623 

624 rows += tr_qa(details_question_text, details_answer) 

625 

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 ) 

631 

632 return rows