Coverage for tasks/icd10depressive.py: 36%

213 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/icd10depressive.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 List, Optional 

31 

32from cardinal_pythonlib.datetimefunc import format_datetime 

33import cardinal_pythonlib.rnc_web as ws 

34from sqlalchemy.sql.schema import Column 

35from sqlalchemy.sql.sqltypes import Boolean, Date, Integer, UnicodeText 

36 

37from camcops_server.cc_modules.cc_constants import ( 

38 CssClass, 

39 DateFormat, 

40 ICD10_COPYRIGHT_DIV, 

41) 

42from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

43from camcops_server.cc_modules.cc_html import ( 

44 answer, 

45 get_present_absent_none, 

46 heading_spanning_two_columns, 

47 tr, 

48 tr_qa, 

49) 

50from camcops_server.cc_modules.cc_request import CamcopsRequest 

51from camcops_server.cc_modules.cc_sqla_coltypes import ( 

52 BIT_CHECKER, 

53 CamcopsColumn, 

54 SummaryCategoryColType, 

55) 

56from camcops_server.cc_modules.cc_string import AS 

57from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

58from camcops_server.cc_modules.cc_task import ( 

59 Task, 

60 TaskHasClinicianMixin, 

61 TaskHasPatientMixin, 

62) 

63from camcops_server.cc_modules.cc_text import SS 

64 

65 

66# ============================================================================= 

67# Icd10Depressive 

68# ============================================================================= 

69 

70 

71class Icd10Depressive(TaskHasClinicianMixin, TaskHasPatientMixin, Task): 

72 """ 

73 Server implementation of the ICD10-DEPR task. 

74 """ 

75 

76 __tablename__ = "icd10depressive" 

77 shortname = "ICD10-DEPR" 

78 info_filename_stem = "icd" 

79 

80 mood = CamcopsColumn( 

81 "mood", 

82 Boolean, 

83 permitted_value_checker=BIT_CHECKER, 

84 comment="Depressed mood to a degree that is definitely abnormal " 

85 "for the individual, present for most of the day and almost " 

86 "every day, largely uninfluenced by circumstances, and " 

87 "sustained for at least 2 weeks.", 

88 ) 

89 anhedonia = CamcopsColumn( 

90 "anhedonia", 

91 Boolean, 

92 permitted_value_checker=BIT_CHECKER, 

93 comment="Loss of interest or pleasure in activities that are " 

94 "normally pleasurable.", 

95 ) 

96 energy = CamcopsColumn( 

97 "energy", 

98 Boolean, 

99 permitted_value_checker=BIT_CHECKER, 

100 comment="Decreased energy or increased fatiguability.", 

101 ) 

102 

103 sleep = CamcopsColumn( 

104 "sleep", 

105 Boolean, 

106 permitted_value_checker=BIT_CHECKER, 

107 comment="Sleep disturbance of any type.", 

108 ) 

109 worth = CamcopsColumn( 

110 "worth", 

111 Boolean, 

112 permitted_value_checker=BIT_CHECKER, 

113 comment="Loss of confidence and self-esteem.", 

114 ) 

115 appetite = CamcopsColumn( 

116 "appetite", 

117 Boolean, 

118 permitted_value_checker=BIT_CHECKER, 

119 comment="Change in appetite (decrease or increase) with " 

120 "corresponding weight change.", 

121 ) 

122 guilt = CamcopsColumn( 

123 "guilt", 

124 Boolean, 

125 permitted_value_checker=BIT_CHECKER, 

126 comment="Unreasonable feelings of self-reproach or excessive and " 

127 "inappropriate guilt.", 

128 ) 

129 concentration = CamcopsColumn( 

130 "concentration", 

131 Boolean, 

132 permitted_value_checker=BIT_CHECKER, 

133 comment="Complaints or evidence of diminished ability to think " 

134 "or concentrate, such as indecisiveness or vacillation.", 

135 ) 

136 activity = CamcopsColumn( 

137 "activity", 

138 Boolean, 

139 permitted_value_checker=BIT_CHECKER, 

140 comment="Change in psychomotor activity, with agitation or " 

141 "retardation (either subjective or objective).", 

142 ) 

143 death = CamcopsColumn( 

144 "death", 

145 Boolean, 

146 permitted_value_checker=BIT_CHECKER, 

147 comment="Recurrent thoughts of death or suicide, or any " 

148 "suicidal behaviour.", 

149 ) 

150 

151 somatic_anhedonia = CamcopsColumn( 

152 "somatic_anhedonia", 

153 Boolean, 

154 permitted_value_checker=BIT_CHECKER, 

155 comment="Marked loss of interest or pleasure in activities that " 

156 "are normally pleasurable", 

157 ) 

158 somatic_emotional_unreactivity = CamcopsColumn( 

159 "somatic_emotional_unreactivity", 

160 Boolean, 

161 permitted_value_checker=BIT_CHECKER, 

162 comment="Lack of emotional reactions to events or " 

163 "activities that normally produce an emotional response", 

164 ) 

165 somatic_early_morning_waking = CamcopsColumn( 

166 "somatic_early_morning_waking", 

167 Boolean, 

168 permitted_value_checker=BIT_CHECKER, 

169 comment="Waking in the morning 2 hours or more before " 

170 "the usual time", 

171 ) 

172 somatic_mood_worse_morning = CamcopsColumn( 

173 "somatic_mood_worse_morning", 

174 Boolean, 

175 permitted_value_checker=BIT_CHECKER, 

176 comment="Depression worse in the morning", 

177 ) 

178 somatic_psychomotor = CamcopsColumn( 

179 "somatic_psychomotor", 

180 Boolean, 

181 permitted_value_checker=BIT_CHECKER, 

182 comment="Objective evidence of marked psychomotor retardation or " 

183 "agitation (remarked on or reported by other people)", 

184 ) 

185 somatic_appetite = CamcopsColumn( 

186 "somatic_appetite", 

187 Boolean, 

188 permitted_value_checker=BIT_CHECKER, 

189 comment="Marked loss of appetite", 

190 ) 

191 somatic_weight = CamcopsColumn( 

192 "somatic_weight", 

193 Boolean, 

194 permitted_value_checker=BIT_CHECKER, 

195 comment="Weight loss (5 percent or more of body weight in the past " 

196 "month)" 

197 # 2017-08-24: AVOID A PERCENT SYMBOL (%) FOR NOW; SEE THIS BUG: 

198 # https://bitbucket.org/zzzeek/sqlalchemy/issues/4052/comment-attribute-causes-crash-during # noqa 

199 ) 

200 somatic_libido = CamcopsColumn( 

201 "somatic_libido", 

202 Boolean, 

203 permitted_value_checker=BIT_CHECKER, 

204 comment="Marked loss of libido", 

205 ) 

206 

207 hallucinations_schizophrenic = CamcopsColumn( 

208 "hallucinations_schizophrenic", 

209 Boolean, 

210 permitted_value_checker=BIT_CHECKER, 

211 comment="Hallucinations that are 'typically schizophrenic' " 

212 "(hallucinatory voices giving a running commentary on the " 

213 "patient's behaviour, or discussing him between themselves, " 

214 "or other types of hallucinatory voices coming from some part " 

215 "of the body).", 

216 ) 

217 hallucinations_other = CamcopsColumn( 

218 "hallucinations_other", 

219 Boolean, 

220 permitted_value_checker=BIT_CHECKER, 

221 comment="Hallucinations (of any other kind).", 

222 ) 

223 delusions_schizophrenic = CamcopsColumn( 

224 "delusions_schizophrenic", 

225 Boolean, 

226 permitted_value_checker=BIT_CHECKER, 

227 comment="Delusions that are 'typically schizophrenic' (delusions " 

228 "of control, influence or passivity, clearly referred to body " 

229 "or limb movements or specific thoughts, actions, or " 

230 "sensations; delusional perception; persistent delusions of " 

231 "other kinds that are culturally inappropriate and completely " 

232 "impossible).", 

233 ) 

234 delusions_other = CamcopsColumn( 

235 "delusions_other", 

236 Boolean, 

237 permitted_value_checker=BIT_CHECKER, 

238 comment="Delusions (of any other kind).", 

239 ) 

240 stupor = CamcopsColumn( 

241 "stupor", 

242 Boolean, 

243 permitted_value_checker=BIT_CHECKER, 

244 comment="Depressive stupor.", 

245 ) 

246 

247 date_pertains_to = CamcopsColumn( 

248 "date_pertains_to", Date, comment="Date the assessment pertains to" 

249 ) 

250 comments = Column("comments", UnicodeText, comment="Clinician's comments") 

251 duration_at_least_2_weeks = CamcopsColumn( 

252 "duration_at_least_2_weeks", 

253 Boolean, 

254 permitted_value_checker=BIT_CHECKER, 

255 comment="Depressive episode lasts at least 2 weeks?", 

256 ) 

257 severe_clinically = CamcopsColumn( 

258 "severe_clinically", 

259 Boolean, 

260 permitted_value_checker=BIT_CHECKER, 

261 comment="Clinical impression of severe depression, in a " 

262 "patient unwilling or unable to describe many symptoms in " 

263 "detail", 

264 ) 

265 

266 CORE_NAMES = ["mood", "anhedonia", "energy"] 

267 ADDITIONAL_NAMES = [ 

268 "sleep", 

269 "worth", 

270 "appetite", 

271 "guilt", 

272 "concentration", 

273 "activity", 

274 "death", 

275 ] 

276 SOMATIC_NAMES = [ 

277 "somatic_anhedonia", 

278 "somatic_emotional_unreactivity", 

279 "somatic_early_morning_waking", 

280 "somatic_mood_worse_morning", 

281 "somatic_psychomotor", 

282 "somatic_appetite", 

283 "somatic_weight", 

284 "somatic_libido", 

285 ] 

286 PSYCHOSIS_NAMES = [ 

287 "hallucinations_schizophrenic", 

288 "hallucinations_other", 

289 "delusions_schizophrenic", 

290 "delusions_other", 

291 "stupor", 

292 ] 

293 

294 @staticmethod 

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

296 _ = req.gettext 

297 return _( 

298 "ICD-10 symptomatic criteria for a depressive episode " 

299 "(as in e.g. F06.3, F25, F31, F32, F33)" 

300 ) 

301 

302 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: 

303 if not self.is_complete(): 

304 return CTV_INCOMPLETE 

305 infolist = [ 

306 CtvInfo( 

307 content="Pertains to: {}. Category: {}.".format( 

308 format_datetime( 

309 self.date_pertains_to, DateFormat.LONG_DATE 

310 ), 

311 self.get_full_description(req), 

312 ) 

313 ) 

314 ] 

315 if self.comments: 

316 infolist.append(CtvInfo(content=ws.webify(self.comments))) 

317 return infolist 

318 

319 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: 

320 return self.standard_task_summary_fields() + [ 

321 SummaryElement( 

322 name="n_core", 

323 coltype=Integer(), 

324 value=self.n_core(), 

325 comment="Number of core diagnostic symptoms (/3)", 

326 ), 

327 SummaryElement( 

328 name="n_additional", 

329 coltype=Integer(), 

330 value=self.n_additional(), 

331 comment="Number of additional diagnostic symptoms (/7)", 

332 ), 

333 SummaryElement( 

334 name="n_total", 

335 coltype=Integer(), 

336 value=self.n_total(), 

337 comment="Total number of diagnostic symptoms (/10)", 

338 ), 

339 SummaryElement( 

340 name="n_somatic", 

341 coltype=Integer(), 

342 value=self.n_somatic(), 

343 comment="Number of somatic syndrome symptoms (/8)", 

344 ), 

345 SummaryElement( 

346 name="category", 

347 coltype=SummaryCategoryColType, 

348 value=self.get_full_description(req), 

349 comment="Diagnostic category", 

350 ), 

351 SummaryElement( 

352 name="psychosis_or_stupor", 

353 coltype=Boolean(), 

354 value=self.is_psychotic_or_stupor(), 

355 comment="Psychotic symptoms or stupor present?", 

356 ), 

357 ] 

358 

359 # Scoring 

360 def n_core(self) -> int: 

361 return self.count_booleans(self.CORE_NAMES) 

362 

363 def n_additional(self) -> int: 

364 return self.count_booleans(self.ADDITIONAL_NAMES) 

365 

366 def n_total(self) -> int: 

367 return self.n_core() + self.n_additional() 

368 

369 def n_somatic(self) -> int: 

370 return self.count_booleans(self.SOMATIC_NAMES) 

371 

372 def main_complete(self) -> bool: 

373 return ( 

374 self.duration_at_least_2_weeks is not None 

375 and self.all_fields_not_none(self.CORE_NAMES) 

376 and self.all_fields_not_none(self.ADDITIONAL_NAMES) 

377 ) or bool(self.severe_clinically) 

378 

379 # Meets criteria? These also return null for unknown. 

380 def meets_criteria_severe_psychotic_schizophrenic(self) -> Optional[bool]: 

381 x = self.meets_criteria_severe_ignoring_psychosis() 

382 if not x: 

383 return x 

384 if self.stupor or self.hallucinations_other or self.delusions_other: 

385 return False # that counts as F32.3 

386 if ( 

387 self.stupor is None 

388 or self.hallucinations_other is None 

389 or self.delusions_other is None 

390 ): 

391 return None # might be F32.3 

392 if self.hallucinations_schizophrenic or self.delusions_schizophrenic: 

393 return True 

394 if ( 

395 self.hallucinations_schizophrenic is None 

396 or self.delusions_schizophrenic is None 

397 ): 

398 return None 

399 return False 

400 

401 def meets_criteria_severe_psychotic_icd(self) -> Optional[bool]: 

402 x = self.meets_criteria_severe_ignoring_psychosis() 

403 if not x: 

404 return x 

405 if self.stupor or self.hallucinations_other or self.delusions_other: 

406 return True 

407 if ( 

408 self.stupor is None 

409 or self.hallucinations_other is None 

410 or self.delusions_other is None 

411 ): 

412 return None 

413 return False 

414 

415 def meets_criteria_severe_nonpsychotic(self) -> Optional[bool]: 

416 x = self.meets_criteria_severe_ignoring_psychosis() 

417 if not x: 

418 return x 

419 if self.any_fields_none(self.PSYCHOSIS_NAMES): 

420 return None 

421 return self.count_booleans(self.PSYCHOSIS_NAMES) == 0 

422 

423 def meets_criteria_severe_ignoring_psychosis(self) -> Optional[bool]: 

424 if self.severe_clinically: 

425 return True 

426 if ( 

427 self.duration_at_least_2_weeks is not None 

428 and not self.duration_at_least_2_weeks 

429 ): 

430 return False # too short 

431 if self.n_core() >= 3 and self.n_total() >= 8: 

432 return True 

433 if not self.main_complete(): 

434 return None # addition of more information might increase severity 

435 return False 

436 

437 def meets_criteria_moderate(self) -> Optional[bool]: 

438 if self.severe_clinically: 

439 return False # too severe 

440 if ( 

441 self.duration_at_least_2_weeks is not None 

442 and not self.duration_at_least_2_weeks 

443 ): 

444 return False # too short 

445 if self.n_core() >= 3 and self.n_total() >= 8: 

446 return False # too severe; that's severe 

447 if not self.main_complete(): 

448 return None # addition of more information might increase severity 

449 if self.n_core() >= 2 and self.n_total() >= 6: 

450 return True 

451 return False 

452 

453 def meets_criteria_mild(self) -> Optional[bool]: 

454 if self.severe_clinically: 

455 return False # too severe 

456 if ( 

457 self.duration_at_least_2_weeks is not None 

458 and not self.duration_at_least_2_weeks 

459 ): 

460 return False # too short 

461 if self.n_core() >= 2 and self.n_total() >= 6: 

462 return False # too severe; that's moderate 

463 if not self.main_complete(): 

464 return None # addition of more information might increase severity 

465 if self.n_core() >= 2 and self.n_total() >= 4: 

466 return True 

467 return False 

468 

469 def meets_criteria_none(self) -> Optional[bool]: 

470 if self.severe_clinically: 

471 return False # too severe 

472 if ( 

473 self.duration_at_least_2_weeks is not None 

474 and not self.duration_at_least_2_weeks 

475 ): 

476 return True # too short for depression 

477 if self.n_core() >= 2 and self.n_total() >= 4: 

478 return False # too severe 

479 if not self.main_complete(): 

480 return None # addition of more information might increase severity 

481 return True 

482 

483 def meets_criteria_somatic(self) -> Optional[bool]: 

484 t = self.n_somatic() 

485 u = self.n_fields_none(self.SOMATIC_NAMES) 

486 if t >= 4: 

487 return True 

488 elif t + u < 4: 

489 return False 

490 else: 

491 return None 

492 

493 def get_somatic_description(self, req: CamcopsRequest) -> str: 

494 s = self.meets_criteria_somatic() 

495 if s is None: 

496 return self.wxstring(req, "category_somatic_unknown") 

497 elif s: 

498 return self.wxstring(req, "category_with_somatic") 

499 else: 

500 return self.wxstring(req, "category_without_somatic") 

501 

502 def get_main_description(self, req: CamcopsRequest) -> str: 

503 if self.meets_criteria_severe_psychotic_schizophrenic(): 

504 return self.wxstring( 

505 req, "category_severe_psychotic_schizophrenic" 

506 ) 

507 

508 elif self.meets_criteria_severe_psychotic_icd(): 

509 return self.wxstring(req, "category_severe_psychotic") 

510 

511 elif self.meets_criteria_severe_nonpsychotic(): 

512 return self.wxstring(req, "category_severe_nonpsychotic") 

513 

514 elif self.meets_criteria_moderate(): 

515 return self.wxstring(req, "category_moderate") 

516 

517 elif self.meets_criteria_mild(): 

518 return self.wxstring(req, "category_mild") 

519 

520 elif self.meets_criteria_none(): 

521 return self.wxstring(req, "category_none") 

522 

523 else: 

524 return req.sstring(SS.UNKNOWN) 

525 

526 def get_full_description(self, req: CamcopsRequest) -> str: 

527 skip_somatic = self.main_complete() and self.meets_criteria_none() 

528 return self.get_main_description(req) + ( 

529 "" if skip_somatic else " " + self.get_somatic_description(req) 

530 ) 

531 

532 def is_psychotic_or_stupor(self) -> Optional[bool]: 

533 if self.count_booleans(self.PSYCHOSIS_NAMES) > 0: 

534 return True 

535 elif self.all_fields_not_none(self.PSYCHOSIS_NAMES) > 0: 

536 return False 

537 else: 

538 return None 

539 

540 def is_complete(self) -> bool: 

541 return ( 

542 self.date_pertains_to is not None 

543 and self.main_complete() 

544 and self.field_contents_valid() 

545 ) 

546 

547 def text_row(self, req: CamcopsRequest, wstringname: str) -> str: 

548 return heading_spanning_two_columns(self.wxstring(req, wstringname)) 

549 

550 def row_true_false(self, req: CamcopsRequest, fieldname: str) -> str: 

551 return self.get_twocol_bool_row_true_false( 

552 req, fieldname, self.wxstring(req, "" + fieldname) 

553 ) 

554 

555 def row_present_absent(self, req: CamcopsRequest, fieldname: str) -> str: 

556 return self.get_twocol_bool_row_present_absent( 

557 req, fieldname, self.wxstring(req, "" + fieldname) 

558 ) 

559 

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

561 h = """ 

562 {clinician_comments} 

563 <div class="{CssClass.SUMMARY}"> 

564 <table class="{CssClass.SUMMARY}"> 

565 {tr_is_complete} 

566 {date_pertains_to} 

567 {category} 

568 {n_core} 

569 {n_total} 

570 {n_somatic} 

571 {psychotic_symptoms_or_stupor} 

572 </table> 

573 </div> 

574 <div class="{CssClass.EXPLANATION}"> 

575 {icd10_symptomatic_disclaimer} 

576 </div> 

577 <table class="{CssClass.TASKDETAIL}"> 

578 <tr> 

579 <th width="80%">Question</th> 

580 <th width="20%">Answer</th> 

581 </tr> 

582 """.format( 

583 clinician_comments=self.get_standard_clinician_comments_block( 

584 req, self.comments 

585 ), 

586 CssClass=CssClass, 

587 tr_is_complete=self.get_is_complete_tr(req), 

588 date_pertains_to=tr_qa( 

589 req.wappstring(AS.DATE_PERTAINS_TO), 

590 format_datetime( 

591 self.date_pertains_to, DateFormat.LONG_DATE, default=None 

592 ), 

593 ), 

594 category=tr_qa( 

595 req.sstring(SS.CATEGORY) + " <sup>[1,2]</sup>", 

596 self.get_full_description(req), 

597 ), 

598 n_core=tr( 

599 self.wxstring(req, "n_core"), answer(self.n_core()) + " / 3" 

600 ), 

601 n_total=tr( 

602 self.wxstring(req, "n_total"), answer(self.n_total()) + " / 10" 

603 ), 

604 n_somatic=tr( 

605 self.wxstring(req, "n_somatic"), 

606 answer(self.n_somatic()) + " / 8", 

607 ), 

608 psychotic_symptoms_or_stupor=tr( 

609 self.wxstring(req, "psychotic_symptoms_or_stupor") 

610 + " <sup>[2]</sup>", 

611 answer( 

612 get_present_absent_none(req, self.is_psychotic_or_stupor()) 

613 ), 

614 ), 

615 icd10_symptomatic_disclaimer=req.wappstring( 

616 AS.ICD10_SYMPTOMATIC_DISCLAIMER 

617 ), 

618 ) 

619 

620 h += self.text_row(req, "duration_text") 

621 h += self.row_true_false(req, "duration_at_least_2_weeks") 

622 

623 h += self.text_row(req, "core") 

624 for x in self.CORE_NAMES: 

625 h += self.row_present_absent(req, x) 

626 

627 h += self.text_row(req, "additional") 

628 for x in self.ADDITIONAL_NAMES: 

629 h += self.row_present_absent(req, x) 

630 

631 h += self.text_row(req, "clinical_text") 

632 h += self.row_true_false(req, "severe_clinically") 

633 

634 h += self.text_row(req, "somatic") 

635 for x in self.SOMATIC_NAMES: 

636 h += self.row_present_absent(req, x) 

637 

638 h += self.text_row(req, "psychotic") 

639 for x in self.PSYCHOSIS_NAMES: 

640 h += self.row_present_absent(req, x) 

641 

642 extradetail = [ 

643 f"n_core() = {self.n_core()}", 

644 f"n_additional() = {self.n_additional()}", 

645 f"n_total() = {self.n_total()}", 

646 f"n_somatic() = {self.n_somatic()}", 

647 f"main_complete() = {self.main_complete()}", 

648 f"meets_criteria_severe_psychotic_schizophrenic() = {self.meets_criteria_severe_psychotic_schizophrenic()}", # noqa 

649 f"meets_criteria_severe_psychotic_icd() = {self.meets_criteria_severe_psychotic_icd()}", # noqa 

650 f"meets_criteria_severe_nonpsychotic() = {self.meets_criteria_severe_nonpsychotic()}", # noqa 

651 f"meets_criteria_severe_ignoring_psychosis() = {self.meets_criteria_severe_ignoring_psychosis()}", # noqa 

652 f"meets_criteria_moderate() = {self.meets_criteria_moderate()}", 

653 f"meets_criteria_mild() = {self.meets_criteria_mild()}", 

654 f"meets_criteria_none() = {self.meets_criteria_none()}", 

655 f"meets_criteria_somatic() = {self.meets_criteria_somatic()}", 

656 ] 

657 

658 h += f""" 

659 </table> 

660 <div class="{CssClass.HEADING}">Working</div> 

661 <div class="{CssClass.EXTRADETAIL2}"> 

662 <pre>{"<br>".join(ws.webify(f"‣ {x}") for x in extradetail)}</pre> 

663 </div> 

664 <div class="{CssClass.FOOTNOTES}"> 

665 [1] Mild depression requires ≥2 core symptoms and ≥4 total 

666 diagnostic symptoms. 

667 Moderate depression requires ≥2 core and ≥6 total. 

668 Severe depression requires 3 core and ≥8 total. 

669 All three require a duration of ≥2 weeks. 

670 In addition, the diagnosis of severe depression is allowed with 

671 a clinical impression of “severe” in a patient unable/unwilling 

672 to describe symptoms in detail. 

673 [2] ICD-10 nonpsychotic severe depression requires severe 

674 depression without hallucinations/delusions/depressive stupor. 

675 ICD-10 psychotic depression requires severe depression plus 

676 hallucinations/delusions other than those that are “typically 

677 schizophrenic”, or stupor. 

678 ICD-10 does not clearly categorize severe depression with only 

679 schizophreniform psychotic symptoms; 

680 however, such symptoms can occur in severe depression with 

681 psychosis (e.g. Tandon R &amp; Greden JF, 1987, PMID 2884810). 

682 Moreover, psychotic symptoms can occur in mild/moderate 

683 depression (Maj M et al., 2007, PMID 17915981). 

684 </div> 

685 {ICD10_COPYRIGHT_DIV} 

686 """ # noqa 

687 return h