Coverage for tasks/khandaker_mojo_medical.py: 69%

1"""

2 camcops_server/tasks/khandaker_mojo_medical.py

3

4===============================================================================

5

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

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

8

9 This file is part of CamCOPS.

10

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.

15

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.

20

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

23

24===============================================================================

25

26"""

27

28 from typing import Any, Type

29

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

31

32 from camcops_server.cc_modules.cc_constants import CssClass

33 from camcops_server.cc_modules.cc_html import tr_qa

34 from camcops_server.cc_modules.cc_request import CamcopsRequest

35 from camcops_server.cc_modules.cc_sqla_coltypes import (

36 bool_column,

37 camcops_column,

38 ZERO_TO_TWO_CHECKER,

39)

40 from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin

41

42

43 class KhandakerMojoMedical( # type: ignore[misc]

44 TaskHasPatientMixin,

45 Task,

46):

47 """

48 Server implementation of the KhandakerMojoMedical task

49 """

50

51 __tablename__ = "khandaker_mojo_medical"

52 shortname = "Khandaker_MOJO_Medical"

53 info_filename_stem = "khandaker_mojo"

54 provides_trackers = False

55

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"

63

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 )

433

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"

440

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"

454

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 )

468

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 ]

480

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 ]

493

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 ]

505

506 MANDATORY_FIELD_NAMES = (

507 MANDATORY_FIELD_NAMES_1

508 + MANDATORY_FIELD_NAMES_2

509 + MANDATORY_FIELD_NAMES_3

510 )

511

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 }

518

519 MULTI_CHOICE_FIELD_NAMES = [FN_DIAGNOSIS, FN_SMOKING_STATUS]

520

521 @staticmethod

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

523 _ = req.gettext

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

525

526 def is_complete(self) -> bool:

527 if self.any_fields_none(self.MANDATORY_FIELD_NAMES):

528 return False

529

530 if not self.field_contents_valid():

531 return False

532

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

537

538 return True

539

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

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

542

543 rows_1 = ""

544 for field_name in self.MANDATORY_FIELD_NAMES_1:

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

546

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

548

549 rows_2 = ""

550 for field_name in self.MANDATORY_FIELD_NAMES_2:

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

552

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

554

555 rows_3 = ""

556 for field_name in self.MANDATORY_FIELD_NAMES_3:

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

558

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

590

591 return html

592

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

594 rows = ""

595

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

597 answer = getattr(self, field_name)

598

599 answer_text = answer

600

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

605

606 rows += tr_qa(question_text, answer_text)

607

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)

614

615 rows += tr_qa(details_question_text, details_answer)

616

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 )

622

623 return rows