Coverage for tasks/icd10depressive.py: 36%

213 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/tasks/icd10depressive.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 

28import datetime 

29from typing import List, Optional 

30 

31from cardinal_pythonlib.datetimefunc import format_datetime 

32import cardinal_pythonlib.rnc_web as ws 

33from sqlalchemy.orm import Mapped, mapped_column 

34from sqlalchemy.sql.sqltypes import Boolean, Integer, UnicodeText 

35 

36from camcops_server.cc_modules.cc_constants import ( 

37 CssClass, 

38 DateFormat, 

39 ICD10_COPYRIGHT_DIV, 

40) 

41from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

42from camcops_server.cc_modules.cc_html import ( 

43 answer, 

44 get_present_absent_none, 

45 heading_spanning_two_columns, 

46 tr, 

47 tr_qa, 

48) 

49from camcops_server.cc_modules.cc_request import CamcopsRequest 

50from camcops_server.cc_modules.cc_sqla_coltypes import ( 

51 BIT_CHECKER, 

52 mapped_camcops_column, 

53 SummaryCategoryColType, 

54) 

55from camcops_server.cc_modules.cc_string import AS 

56from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

57from camcops_server.cc_modules.cc_task import ( 

58 Task, 

59 TaskHasClinicianMixin, 

60 TaskHasPatientMixin, 

61) 

62from camcops_server.cc_modules.cc_text import SS 

63 

64 

65# ============================================================================= 

66# Icd10Depressive 

67# ============================================================================= 

68 

69 

70class Icd10Depressive(TaskHasClinicianMixin, TaskHasPatientMixin, Task): # type: ignore[misc] # noqa: E501 

71 """ 

72 Server implementation of the ICD10-DEPR task. 

73 """ 

74 

75 __tablename__ = "icd10depressive" 

76 shortname = "ICD10-DEPR" 

77 info_filename_stem = "icd" 

78 

79 mood: Mapped[Optional[bool]] = mapped_camcops_column( 

80 permitted_value_checker=BIT_CHECKER, 

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

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

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

84 "sustained for at least 2 weeks.", 

85 ) 

86 anhedonia: Mapped[Optional[bool]] = mapped_camcops_column( 

87 permitted_value_checker=BIT_CHECKER, 

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

89 "normally pleasurable.", 

90 ) 

91 energy: Mapped[Optional[bool]] = mapped_camcops_column( 

92 permitted_value_checker=BIT_CHECKER, 

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

94 ) 

95 

96 sleep: Mapped[Optional[bool]] = mapped_camcops_column( 

97 permitted_value_checker=BIT_CHECKER, 

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

99 ) 

100 worth: Mapped[Optional[bool]] = mapped_camcops_column( 

101 permitted_value_checker=BIT_CHECKER, 

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

103 ) 

104 appetite: Mapped[Optional[bool]] = mapped_camcops_column( 

105 permitted_value_checker=BIT_CHECKER, 

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

107 "corresponding weight change.", 

108 ) 

109 guilt: Mapped[Optional[bool]] = mapped_camcops_column( 

110 permitted_value_checker=BIT_CHECKER, 

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

112 "inappropriate guilt.", 

113 ) 

114 concentration: Mapped[Optional[bool]] = mapped_camcops_column( 

115 permitted_value_checker=BIT_CHECKER, 

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

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

118 ) 

119 activity: Mapped[Optional[bool]] = mapped_camcops_column( 

120 permitted_value_checker=BIT_CHECKER, 

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

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

123 ) 

124 death: Mapped[Optional[bool]] = mapped_camcops_column( 

125 permitted_value_checker=BIT_CHECKER, 

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

127 "suicidal behaviour.", 

128 ) 

129 

130 somatic_anhedonia: Mapped[Optional[bool]] = mapped_camcops_column( 

131 permitted_value_checker=BIT_CHECKER, 

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

133 "are normally pleasurable", 

134 ) 

135 somatic_emotional_unreactivity: Mapped[Optional[bool]] = ( 

136 mapped_camcops_column( 

137 permitted_value_checker=BIT_CHECKER, 

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

139 "activities that normally produce an emotional response", 

140 ) 

141 ) 

142 somatic_early_morning_waking: Mapped[Optional[bool]] = ( 

143 mapped_camcops_column( 

144 permitted_value_checker=BIT_CHECKER, 

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

146 "the usual time", 

147 ) 

148 ) 

149 somatic_mood_worse_morning: Mapped[Optional[bool]] = mapped_camcops_column( 

150 permitted_value_checker=BIT_CHECKER, 

151 comment="Depression worse in the morning", 

152 ) 

153 somatic_psychomotor: Mapped[Optional[bool]] = mapped_camcops_column( 

154 permitted_value_checker=BIT_CHECKER, 

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

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

157 ) 

158 somatic_appetite: Mapped[Optional[bool]] = mapped_camcops_column( 

159 permitted_value_checker=BIT_CHECKER, 

160 comment="Marked loss of appetite", 

161 ) 

162 somatic_weight: Mapped[Optional[bool]] = mapped_camcops_column( 

163 permitted_value_checker=BIT_CHECKER, 

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

165 "month)", 

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

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

168 ) 

169 somatic_libido: Mapped[Optional[bool]] = mapped_camcops_column( 

170 permitted_value_checker=BIT_CHECKER, 

171 comment="Marked loss of libido", 

172 ) 

173 

174 hallucinations_schizophrenic: Mapped[Optional[bool]] = ( 

175 mapped_camcops_column( 

176 permitted_value_checker=BIT_CHECKER, 

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

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

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

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

181 "of the body).", 

182 ) 

183 ) 

184 hallucinations_other: Mapped[Optional[bool]] = mapped_camcops_column( 

185 permitted_value_checker=BIT_CHECKER, 

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

187 ) 

188 delusions_schizophrenic: Mapped[Optional[bool]] = mapped_camcops_column( 

189 permitted_value_checker=BIT_CHECKER, 

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

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

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

193 "sensations; delusional perception; persistent delusions of " 

194 "other kinds that are culturally inappropriate and completely " 

195 "impossible).", 

196 ) 

197 delusions_other: Mapped[Optional[bool]] = mapped_camcops_column( 

198 permitted_value_checker=BIT_CHECKER, 

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

200 ) 

201 stupor: Mapped[Optional[bool]] = mapped_camcops_column( 

202 permitted_value_checker=BIT_CHECKER, 

203 comment="Depressive stupor.", 

204 ) 

205 

206 date_pertains_to: Mapped[Optional[datetime.date]] = mapped_camcops_column( 

207 comment="Date the assessment pertains to" 

208 ) 

209 comments: Mapped[Optional[str]] = mapped_column( 

210 UnicodeText, comment="Clinician's comments" 

211 ) 

212 duration_at_least_2_weeks: Mapped[Optional[bool]] = mapped_camcops_column( 

213 permitted_value_checker=BIT_CHECKER, 

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

215 ) 

216 severe_clinically: Mapped[Optional[bool]] = mapped_camcops_column( 

217 permitted_value_checker=BIT_CHECKER, 

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

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

220 "detail", 

221 ) 

222 

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

224 ADDITIONAL_NAMES = [ 

225 "sleep", 

226 "worth", 

227 "appetite", 

228 "guilt", 

229 "concentration", 

230 "activity", 

231 "death", 

232 ] 

233 SOMATIC_NAMES = [ 

234 "somatic_anhedonia", 

235 "somatic_emotional_unreactivity", 

236 "somatic_early_morning_waking", 

237 "somatic_mood_worse_morning", 

238 "somatic_psychomotor", 

239 "somatic_appetite", 

240 "somatic_weight", 

241 "somatic_libido", 

242 ] 

243 PSYCHOSIS_NAMES = [ 

244 "hallucinations_schizophrenic", 

245 "hallucinations_other", 

246 "delusions_schizophrenic", 

247 "delusions_other", 

248 "stupor", 

249 ] 

250 

251 @staticmethod 

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

253 _ = req.gettext 

254 return _( 

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

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

257 ) 

258 

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

260 if not self.is_complete(): 

261 return CTV_INCOMPLETE 

262 infolist = [ 

263 CtvInfo( 

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

265 format_datetime( 

266 self.date_pertains_to, DateFormat.LONG_DATE 

267 ), 

268 self.get_full_description(req), 

269 ) 

270 ) 

271 ] 

272 if self.comments: 

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

274 return infolist 

275 

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

277 return self.standard_task_summary_fields() + [ 

278 SummaryElement( 

279 name="n_core", 

280 coltype=Integer(), 

281 value=self.n_core(), 

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

283 ), 

284 SummaryElement( 

285 name="n_additional", 

286 coltype=Integer(), 

287 value=self.n_additional(), 

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

289 ), 

290 SummaryElement( 

291 name="n_total", 

292 coltype=Integer(), 

293 value=self.n_total(), 

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

295 ), 

296 SummaryElement( 

297 name="n_somatic", 

298 coltype=Integer(), 

299 value=self.n_somatic(), 

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

301 ), 

302 SummaryElement( 

303 name="category", 

304 coltype=SummaryCategoryColType, 

305 value=self.get_full_description(req), 

306 comment="Diagnostic category", 

307 ), 

308 SummaryElement( 

309 name="psychosis_or_stupor", 

310 coltype=Boolean(), 

311 value=self.is_psychotic_or_stupor(), 

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

313 ), 

314 ] 

315 

316 # Scoring 

317 def n_core(self) -> int: 

318 return self.count_booleans(self.CORE_NAMES) 

319 

320 def n_additional(self) -> int: 

321 return self.count_booleans(self.ADDITIONAL_NAMES) 

322 

323 def n_total(self) -> int: 

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

325 

326 def n_somatic(self) -> int: 

327 return self.count_booleans(self.SOMATIC_NAMES) 

328 

329 def main_complete(self) -> bool: 

330 return ( 

331 self.duration_at_least_2_weeks is not None 

332 and self.all_fields_not_none(self.CORE_NAMES) 

333 and self.all_fields_not_none(self.ADDITIONAL_NAMES) 

334 ) or bool(self.severe_clinically) 

335 

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

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

338 x = self.meets_criteria_severe_ignoring_psychosis() 

339 if not x: 

340 return x 

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

342 return False # that counts as F32.3 

343 if ( 

344 self.stupor is None 

345 or self.hallucinations_other is None 

346 or self.delusions_other is None 

347 ): 

348 return None # might be F32.3 

349 if self.hallucinations_schizophrenic or self.delusions_schizophrenic: 

350 return True 

351 if ( 

352 self.hallucinations_schizophrenic is None 

353 or self.delusions_schizophrenic is None 

354 ): 

355 return None 

356 return False 

357 

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

359 x = self.meets_criteria_severe_ignoring_psychosis() 

360 if not x: 

361 return x 

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

363 return True 

364 if ( 

365 self.stupor is None 

366 or self.hallucinations_other is None 

367 or self.delusions_other is None 

368 ): 

369 return None 

370 return False 

371 

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

373 x = self.meets_criteria_severe_ignoring_psychosis() 

374 if not x: 

375 return x 

376 if self.any_fields_none(self.PSYCHOSIS_NAMES): 

377 return None 

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

379 

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

381 if self.severe_clinically: 

382 return True 

383 if ( 

384 self.duration_at_least_2_weeks is not None 

385 and not self.duration_at_least_2_weeks 

386 ): 

387 return False # too short 

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

389 return True 

390 if not self.main_complete(): 

391 return None # addition of more information might increase severity 

392 return False 

393 

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

395 if self.severe_clinically: 

396 return False # too severe 

397 if ( 

398 self.duration_at_least_2_weeks is not None 

399 and not self.duration_at_least_2_weeks 

400 ): 

401 return False # too short 

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

403 return False # too severe; that's severe 

404 if not self.main_complete(): 

405 return None # addition of more information might increase severity 

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

407 return True 

408 return False 

409 

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

411 if self.severe_clinically: 

412 return False # too severe 

413 if ( 

414 self.duration_at_least_2_weeks is not None 

415 and not self.duration_at_least_2_weeks 

416 ): 

417 return False # too short 

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

419 return False # too severe; that's moderate 

420 if not self.main_complete(): 

421 return None # addition of more information might increase severity 

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

423 return True 

424 return False 

425 

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

427 if self.severe_clinically: 

428 return False # too severe 

429 if ( 

430 self.duration_at_least_2_weeks is not None 

431 and not self.duration_at_least_2_weeks 

432 ): 

433 return True # too short for depression 

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

435 return False # too severe 

436 if not self.main_complete(): 

437 return None # addition of more information might increase severity 

438 return True 

439 

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

441 t = self.n_somatic() 

442 u = self.n_fields_none(self.SOMATIC_NAMES) 

443 if t >= 4: 

444 return True 

445 elif t + u < 4: 

446 return False 

447 else: 

448 return None 

449 

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

451 s = self.meets_criteria_somatic() 

452 if s is None: 

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

454 elif s: 

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

456 else: 

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

458 

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

460 if self.meets_criteria_severe_psychotic_schizophrenic(): 

461 return self.wxstring( 

462 req, "category_severe_psychotic_schizophrenic" 

463 ) 

464 

465 elif self.meets_criteria_severe_psychotic_icd(): 

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

467 

468 elif self.meets_criteria_severe_nonpsychotic(): 

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

470 

471 elif self.meets_criteria_moderate(): 

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

473 

474 elif self.meets_criteria_mild(): 

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

476 

477 elif self.meets_criteria_none(): 

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

479 

480 else: 

481 return req.sstring(SS.UNKNOWN) 

482 

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

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

485 return self.get_main_description(req) + ( 

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

487 ) 

488 

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

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

491 return True 

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

493 return False 

494 else: 

495 return None 

496 

497 def is_complete(self) -> bool: 

498 return ( 

499 self.date_pertains_to is not None 

500 and self.main_complete() 

501 and self.field_contents_valid() 

502 ) 

503 

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

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

506 

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

508 return self.get_twocol_bool_row_true_false( 

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

510 ) 

511 

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

513 return self.get_twocol_bool_row_present_absent( 

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

515 ) 

516 

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

518 h = """ 

519 {clinician_comments} 

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

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

522 {tr_is_complete} 

523 {date_pertains_to} 

524 {category} 

525 {n_core} 

526 {n_total} 

527 {n_somatic} 

528 {psychotic_symptoms_or_stupor} 

529 </table> 

530 </div> 

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

532 {icd10_symptomatic_disclaimer} 

533 </div> 

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

535 <tr> 

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

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

538 </tr> 

539 """.format( 

540 clinician_comments=self.get_standard_clinician_comments_block( 

541 req, self.comments 

542 ), 

543 CssClass=CssClass, 

544 tr_is_complete=self.get_is_complete_tr(req), 

545 date_pertains_to=tr_qa( 

546 req.wappstring(AS.DATE_PERTAINS_TO), 

547 format_datetime( 

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

549 ), 

550 ), 

551 category=tr_qa( 

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

553 self.get_full_description(req), 

554 ), 

555 n_core=tr( 

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

557 ), 

558 n_total=tr( 

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

560 ), 

561 n_somatic=tr( 

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

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

564 ), 

565 psychotic_symptoms_or_stupor=tr( 

566 self.wxstring(req, "psychotic_symptoms_or_stupor") 

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

568 answer( 

569 get_present_absent_none(req, self.is_psychotic_or_stupor()) 

570 ), 

571 ), 

572 icd10_symptomatic_disclaimer=req.wappstring( 

573 AS.ICD10_SYMPTOMATIC_DISCLAIMER 

574 ), 

575 ) 

576 

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

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

579 

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

581 for x in self.CORE_NAMES: 

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

583 

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

585 for x in self.ADDITIONAL_NAMES: 

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

587 

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

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

590 

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

592 for x in self.SOMATIC_NAMES: 

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

594 

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

596 for x in self.PSYCHOSIS_NAMES: 

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

598 

599 extradetail = [ 

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

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

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

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

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

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

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

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

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

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

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

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

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

613 ] 

614 

615 h += f""" 

616 </table> 

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

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

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

620 </div> 

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

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

623 diagnostic symptoms. 

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

625 Severe depression requires 3 core and ≥8 total. 

626 All three require a duration of ≥2 weeks. 

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

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

629 to describe symptoms in detail. 

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

631 depression without hallucinations/delusions/depressive stupor. 

632 ICD-10 psychotic depression requires severe depression plus 

633 hallucinations/delusions other than those that are “typically 

634 schizophrenic”, or stupor. 

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

636 schizophreniform psychotic symptoms; 

637 however, such symptoms can occur in severe depression with 

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

639 Moreover, psychotic symptoms can occur in mild/moderate 

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

641 </div> 

642 {ICD10_COPYRIGHT_DIV} 

643 """ # noqa 

644 return h