Coverage for tasks/icd10specpd.py: 44%

237 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/icd10specpd.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, List, Optional, Tuple, Type 

31 

32from cardinal_pythonlib.datetimefunc import format_datetime 

33import cardinal_pythonlib.rnc_web as ws 

34from cardinal_pythonlib.stringfunc import strseq 

35from cardinal_pythonlib.typetests import is_false 

36from sqlalchemy.ext.declarative import DeclarativeMeta 

37from sqlalchemy.sql.schema import Column 

38from sqlalchemy.sql.sqltypes import Boolean, Date, UnicodeText 

39 

40from camcops_server.cc_modules.cc_constants import ( 

41 CssClass, 

42 DateFormat, 

43 ICD10_COPYRIGHT_DIV, 

44 PV, 

45) 

46from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

47from camcops_server.cc_modules.cc_db import add_multiple_columns 

48from camcops_server.cc_modules.cc_html import ( 

49 answer, 

50 get_yes_no_none, 

51 get_yes_no_unknown, 

52 subheading_spanning_two_columns, 

53 tr_qa, 

54) 

55from camcops_server.cc_modules.cc_request import CamcopsRequest 

56from camcops_server.cc_modules.cc_sqla_coltypes import ( 

57 BIT_CHECKER, 

58 CamcopsColumn, 

59) 

60from camcops_server.cc_modules.cc_string import AS 

61from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

62from camcops_server.cc_modules.cc_task import ( 

63 Task, 

64 TaskHasClinicianMixin, 

65 TaskHasPatientMixin, 

66) 

67 

68 

69# ============================================================================= 

70# Icd10SpecPD 

71# ============================================================================= 

72 

73 

74def ctv_info_pd( 

75 req: CamcopsRequest, condition: str, has_it: Optional[bool] 

76) -> CtvInfo: 

77 return CtvInfo(content=condition + ": " + get_yes_no_unknown(req, has_it)) 

78 

79 

80class Icd10SpecPDMetaclass(DeclarativeMeta): 

81 # noinspection PyInitNewSignature 

82 def __init__( 

83 cls: Type["Icd10SpecPD"], 

84 name: str, 

85 bases: Tuple[Type, ...], 

86 classdict: Dict[str, Any], 

87 ) -> None: 

88 add_multiple_columns( 

89 cls, 

90 "g", 

91 1, 

92 cls.N_GENERAL, 

93 Boolean, 

94 pv=PV.BIT, 

95 comment_fmt="G{n}: {s}", 

96 comment_strings=[ 

97 "pathological 1", 

98 "pervasive", 

99 "pathological 2", 

100 "persistent", 

101 "primary 1", 

102 "primary 2", 

103 ], 

104 ) 

105 add_multiple_columns( 

106 cls, 

107 "g1_", 

108 1, 

109 cls.N_GENERAL_1, 

110 Boolean, 

111 pv=PV.BIT, 

112 comment_fmt="G1{n}: {s}", 

113 comment_strings=[ 

114 "cognition", 

115 "affectivity", 

116 "impulse control", 

117 "interpersonal", 

118 ], 

119 ) 

120 add_multiple_columns( 

121 cls, 

122 "paranoid", 

123 1, 

124 cls.N_PARANOID, 

125 Boolean, 

126 pv=PV.BIT, 

127 comment_fmt="Paranoid ({n}): {s}", 

128 comment_strings=[ 

129 "sensitive", 

130 "grudges", 

131 "suspicious", 

132 "personal rights", 

133 "sexual jealousy", 

134 "self-referential", 

135 "conspiratorial", 

136 ], 

137 ) 

138 add_multiple_columns( 

139 cls, 

140 "schizoid", 

141 1, 

142 cls.N_SCHIZOID, 

143 Boolean, 

144 pv=PV.BIT, 

145 comment_fmt="Schizoid ({n}): {s}", 

146 comment_strings=[ 

147 "little pleasure", 

148 "cold/detached", 

149 "limited capacity for warmth", 

150 "indifferent to praise/criticism", 

151 "little interest in sex", 

152 "solitary", 

153 "fantasy/introspection", 

154 "0/1 close friends/confidants", 

155 "insensitive to social norms", 

156 ], 

157 ) 

158 add_multiple_columns( 

159 cls, 

160 "dissocial", 

161 1, 

162 cls.N_DISSOCIAL, 

163 Boolean, 

164 pv=PV.BIT, 

165 comment_fmt="Dissocial ({n}): {s}", 

166 comment_strings=[ 

167 "unconcern", 

168 "irresponsibility", 

169 "incapacity to maintain relationships", 

170 "low tolerance to frustration", 

171 "incapacity for guilt", 

172 "prone to blame others", 

173 ], 

174 ) 

175 add_multiple_columns( 

176 cls, 

177 "eu", 

178 1, 

179 cls.N_EU, 

180 Boolean, 

181 pv=PV.BIT, 

182 comment_fmt="Emotionally unstable ({n}): {s}", 

183 comment_strings=[ 

184 "act without considering consequences", 

185 "quarrelsome", 

186 "outbursts of anger", 

187 "can't maintain actions with immediate reward", 

188 "unstable/capricious mood", 

189 "uncertain self-image", 

190 "intense/unstable relationships", 

191 "avoids abandonment", 

192 "threats/acts of self-harm", 

193 "feelings of emptiness", 

194 ], 

195 ) 

196 add_multiple_columns( 

197 cls, 

198 "histrionic", 

199 1, 

200 cls.N_HISTRIONIC, 

201 Boolean, 

202 pv=PV.BIT, 

203 comment_fmt="Histrionic ({n}): {s}", 

204 comment_strings=[ 

205 "theatricality", 

206 "suggestibility", 

207 "shallow/labile affect", 

208 "centre of attention", 

209 "inappropriately seductive", 

210 "concerned with attractiveness", 

211 ], 

212 ) 

213 add_multiple_columns( 

214 cls, 

215 "anankastic", 

216 1, 

217 cls.N_ANANKASTIC, 

218 Boolean, 

219 pv=PV.BIT, 

220 comment_fmt="Anankastic ({n}): {s}", 

221 comment_strings=[ 

222 "doubt/caution", 

223 "preoccupation with details", 

224 "perfectionism", 

225 "excessively conscientious", 

226 "preoccupied with productivity", 

227 "excessive pedantry", 

228 "rigid/stubborn", 

229 "require others do things specific way", 

230 ], 

231 ) 

232 add_multiple_columns( 

233 cls, 

234 "anxious", 

235 1, 

236 cls.N_ANXIOUS, 

237 Boolean, 

238 pv=PV.BIT, 

239 comment_fmt="Anxious ({n}), {s}", 

240 comment_strings=[ 

241 "tension/apprehension", 

242 "preoccupied with criticism/rejection", 

243 "won't get involved unless certain liked", 

244 "need for security restricts lifestyle", 

245 "avoidance of interpersonal contact", 

246 ], 

247 ) 

248 add_multiple_columns( 

249 cls, 

250 "dependent", 

251 1, 

252 cls.N_DEPENDENT, 

253 Boolean, 

254 pv=PV.BIT, 

255 comment_fmt="Dependent ({n}): {s}", 

256 comment_strings=[ 

257 "others decide", 

258 "subordinate needs to those of others", 

259 "unwilling to make reasonable demands", 

260 "uncomfortable/helpless when alone", 

261 "fears of being left to oneself", 

262 "everyday decisions require advice/reassurance", 

263 ], 

264 ) 

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

266 

267 

268class Icd10SpecPD( 

269 TaskHasClinicianMixin, 

270 TaskHasPatientMixin, 

271 Task, 

272 metaclass=Icd10SpecPDMetaclass, 

273): 

274 """ 

275 Server implementation of the ICD10-PD task. 

276 """ 

277 

278 __tablename__ = "icd10specpd" 

279 shortname = "ICD10-PD" 

280 info_filename_stem = "icd" 

281 

282 date_pertains_to = Column( 

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

284 ) 

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

286 skip_paranoid = CamcopsColumn( 

287 "skip_paranoid", 

288 Boolean, 

289 permitted_value_checker=BIT_CHECKER, 

290 comment="Skip questions for paranoid PD?", 

291 ) 

292 skip_schizoid = CamcopsColumn( 

293 "skip_schizoid", 

294 Boolean, 

295 permitted_value_checker=BIT_CHECKER, 

296 comment="Skip questions for schizoid PD?", 

297 ) 

298 skip_dissocial = CamcopsColumn( 

299 "skip_dissocial", 

300 Boolean, 

301 permitted_value_checker=BIT_CHECKER, 

302 comment="Skip questions for dissocial PD?", 

303 ) 

304 skip_eu = CamcopsColumn( 

305 "skip_eu", 

306 Boolean, 

307 permitted_value_checker=BIT_CHECKER, 

308 comment="Skip questions for emotionally unstable PD?", 

309 ) 

310 skip_histrionic = CamcopsColumn( 

311 "skip_histrionic", 

312 Boolean, 

313 permitted_value_checker=BIT_CHECKER, 

314 comment="Skip questions for histrionic PD?", 

315 ) 

316 skip_anankastic = CamcopsColumn( 

317 "skip_anankastic", 

318 Boolean, 

319 permitted_value_checker=BIT_CHECKER, 

320 comment="Skip questions for anankastic PD?", 

321 ) 

322 skip_anxious = CamcopsColumn( 

323 "skip_anxious", 

324 Boolean, 

325 permitted_value_checker=BIT_CHECKER, 

326 comment="Skip questions for anxious PD?", 

327 ) 

328 skip_dependent = CamcopsColumn( 

329 "skip_dependent", 

330 Boolean, 

331 permitted_value_checker=BIT_CHECKER, 

332 comment="Skip questions for dependent PD?", 

333 ) 

334 other_pd_present = CamcopsColumn( 

335 "other_pd_present", 

336 Boolean, 

337 permitted_value_checker=BIT_CHECKER, 

338 comment="Is another personality disorder present?", 

339 ) 

340 vignette = Column("vignette", UnicodeText, comment="Vignette") 

341 

342 N_GENERAL = 6 

343 N_GENERAL_1 = 4 

344 N_PARANOID = 7 

345 N_SCHIZOID = 9 

346 N_DISSOCIAL = 6 

347 N_EU = 10 

348 N_EUPD_I = 5 

349 N_HISTRIONIC = 6 

350 N_ANANKASTIC = 8 

351 N_ANXIOUS = 5 

352 N_DEPENDENT = 6 

353 

354 GENERAL_FIELDS = strseq("g", 1, N_GENERAL) 

355 GENERAL_1_FIELDS = strseq("g1_", 1, N_GENERAL_1) 

356 PARANOID_FIELDS = strseq("paranoid", 1, N_PARANOID) 

357 SCHIZOID_FIELDS = strseq("schizoid", 1, N_SCHIZOID) 

358 DISSOCIAL_FIELDS = strseq("dissocial", 1, N_DISSOCIAL) 

359 EU_FIELDS = strseq("eu", 1, N_EU) 

360 EUPD_I_FIELDS = strseq("eu", 1, N_EUPD_I) # impulsive 

361 EUPD_B_FIELDS = strseq("eu", N_EUPD_I + 1, N_EU) # borderline 

362 HISTRIONIC_FIELDS = strseq("histrionic", 1, N_HISTRIONIC) 

363 ANANKASTIC_FIELDS = strseq("anankastic", 1, N_ANANKASTIC) 

364 ANXIOUS_FIELDS = strseq("anxious", 1, N_ANXIOUS) 

365 DEPENDENT_FIELDS = strseq("dependent", 1, N_DEPENDENT) 

366 

367 @staticmethod 

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

369 _ = req.gettext 

370 return _("ICD-10 criteria for specific personality disorders (F60)") 

371 

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

373 if not self.is_complete(): 

374 return CTV_INCOMPLETE 

375 infolist = [ 

376 ctv_info_pd( 

377 req, 

378 self.wxstring(req, "meets_general_criteria"), 

379 self.has_pd(), 

380 ), 

381 ctv_info_pd( 

382 req, 

383 self.wxstring(req, "paranoid_pd_title"), 

384 self.has_paranoid_pd(), 

385 ), 

386 ctv_info_pd( 

387 req, 

388 self.wxstring(req, "schizoid_pd_title"), 

389 self.has_schizoid_pd(), 

390 ), 

391 ctv_info_pd( 

392 req, 

393 self.wxstring(req, "dissocial_pd_title"), 

394 self.has_dissocial_pd(), 

395 ), 

396 ctv_info_pd( 

397 req, self.wxstring(req, "eu_pd_i_title"), self.has_eupd_i() 

398 ), 

399 ctv_info_pd( 

400 req, self.wxstring(req, "eu_pd_b_title"), self.has_eupd_b() 

401 ), 

402 ctv_info_pd( 

403 req, 

404 self.wxstring(req, "histrionic_pd_title"), 

405 self.has_histrionic_pd(), 

406 ), 

407 ctv_info_pd( 

408 req, 

409 self.wxstring(req, "anankastic_pd_title"), 

410 self.has_anankastic_pd(), 

411 ), 

412 ctv_info_pd( 

413 req, 

414 self.wxstring(req, "anxious_pd_title"), 

415 self.has_anxious_pd(), 

416 ), 

417 ctv_info_pd( 

418 req, 

419 self.wxstring(req, "dependent_pd_title"), 

420 self.has_dependent_pd(), 

421 ), 

422 ] 

423 return infolist 

424 

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

426 return self.standard_task_summary_fields() + [ 

427 SummaryElement( 

428 name="meets_general_criteria", 

429 coltype=Boolean(), 

430 value=self.has_pd(), 

431 comment="Meets general criteria for personality disorder?", 

432 ), 

433 SummaryElement( 

434 name="paranoid_pd", 

435 coltype=Boolean(), 

436 value=self.has_paranoid_pd(), 

437 comment="Meets criteria for paranoid PD?", 

438 ), 

439 SummaryElement( 

440 name="schizoid_pd", 

441 coltype=Boolean(), 

442 value=self.has_schizoid_pd(), 

443 comment="Meets criteria for schizoid PD?", 

444 ), 

445 SummaryElement( 

446 name="dissocial_pd", 

447 coltype=Boolean(), 

448 value=self.has_dissocial_pd(), 

449 comment="Meets criteria for dissocial PD?", 

450 ), 

451 SummaryElement( 

452 name="eupd_i", 

453 coltype=Boolean(), 

454 value=self.has_eupd_i(), 

455 comment="Meets criteria for EUPD (impulsive type)?", 

456 ), 

457 SummaryElement( 

458 name="eupd_b", 

459 coltype=Boolean(), 

460 value=self.has_eupd_b(), 

461 comment="Meets criteria for EUPD (borderline type)?", 

462 ), 

463 SummaryElement( 

464 name="histrionic_pd", 

465 coltype=Boolean(), 

466 value=self.has_histrionic_pd(), 

467 comment="Meets criteria for histrionic PD?", 

468 ), 

469 SummaryElement( 

470 name="anankastic_pd", 

471 coltype=Boolean(), 

472 value=self.has_anankastic_pd(), 

473 comment="Meets criteria for anankastic PD?", 

474 ), 

475 SummaryElement( 

476 name="anxious_pd", 

477 coltype=Boolean(), 

478 value=self.has_anxious_pd(), 

479 comment="Meets criteria for anxious PD?", 

480 ), 

481 SummaryElement( 

482 name="dependent_pd", 

483 coltype=Boolean(), 

484 value=self.has_dependent_pd(), 

485 comment="Meets criteria for dependent PD?", 

486 ), 

487 ] 

488 

489 # noinspection PyUnresolvedReferences 

490 def is_pd_excluded(self) -> bool: 

491 return ( 

492 is_false(self.g1) 

493 or is_false(self.g2) 

494 or is_false(self.g3) 

495 or is_false(self.g4) 

496 or is_false(self.g5) 

497 or is_false(self.g6) 

498 or ( 

499 self.all_fields_not_none(self.GENERAL_1_FIELDS) 

500 and self.count_booleans(self.GENERAL_1_FIELDS) <= 1 

501 ) 

502 ) 

503 

504 def is_complete_general(self) -> bool: 

505 return self.all_fields_not_none( 

506 self.GENERAL_FIELDS 

507 ) and self.all_fields_not_none(self.GENERAL_1_FIELDS) 

508 

509 def is_complete_paranoid(self) -> bool: 

510 return self.all_fields_not_none(self.PARANOID_FIELDS) 

511 

512 def is_complete_schizoid(self) -> bool: 

513 return self.all_fields_not_none(self.SCHIZOID_FIELDS) 

514 

515 def is_complete_dissocial(self) -> bool: 

516 return self.all_fields_not_none(self.DISSOCIAL_FIELDS) 

517 

518 def is_complete_eu(self) -> bool: 

519 return self.all_fields_not_none(self.EU_FIELDS) 

520 

521 def is_complete_histrionic(self) -> bool: 

522 return self.all_fields_not_none(self.HISTRIONIC_FIELDS) 

523 

524 def is_complete_anankastic(self) -> bool: 

525 return self.all_fields_not_none(self.ANANKASTIC_FIELDS) 

526 

527 def is_complete_anxious(self) -> bool: 

528 return self.all_fields_not_none(self.ANXIOUS_FIELDS) 

529 

530 def is_complete_dependent(self) -> bool: 

531 return self.all_fields_not_none(self.DEPENDENT_FIELDS) 

532 

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

534 def has_pd(self) -> Optional[bool]: 

535 if self.is_pd_excluded(): 

536 return False 

537 if not self.is_complete_general(): 

538 return None 

539 return ( 

540 self.all_truthy(self.GENERAL_FIELDS) 

541 and self.count_booleans(self.GENERAL_1_FIELDS) > 1 

542 ) 

543 

544 def has_paranoid_pd(self) -> Optional[bool]: 

545 hpd = self.has_pd() 

546 if not hpd: 

547 return hpd 

548 if not self.is_complete_paranoid(): 

549 return None 

550 return self.count_booleans(self.PARANOID_FIELDS) >= 4 

551 

552 def has_schizoid_pd(self) -> Optional[bool]: 

553 hpd = self.has_pd() 

554 if not hpd: 

555 return hpd 

556 if not self.is_complete_schizoid(): 

557 return None 

558 return self.count_booleans(self.SCHIZOID_FIELDS) >= 4 

559 

560 def has_dissocial_pd(self) -> Optional[bool]: 

561 hpd = self.has_pd() 

562 if not hpd: 

563 return hpd 

564 if not self.is_complete_dissocial(): 

565 return None 

566 return self.count_booleans(self.DISSOCIAL_FIELDS) >= 3 

567 

568 # noinspection PyUnresolvedReferences 

569 def has_eupd_i(self) -> Optional[bool]: 

570 hpd = self.has_pd() 

571 if not hpd: 

572 return hpd 

573 if not self.is_complete_eu(): 

574 return None 

575 return self.count_booleans(self.EUPD_I_FIELDS) >= 3 and self.eu2 

576 

577 def has_eupd_b(self) -> Optional[bool]: 

578 hpd = self.has_pd() 

579 if not hpd: 

580 return hpd 

581 if not self.is_complete_eu(): 

582 return None 

583 return ( 

584 self.count_booleans(self.EUPD_I_FIELDS) >= 3 

585 and self.count_booleans(self.EUPD_B_FIELDS) >= 2 

586 ) 

587 

588 def has_histrionic_pd(self) -> Optional[bool]: 

589 hpd = self.has_pd() 

590 if not hpd: 

591 return hpd 

592 if not self.is_complete_histrionic(): 

593 return None 

594 return self.count_booleans(self.HISTRIONIC_FIELDS) >= 4 

595 

596 def has_anankastic_pd(self) -> Optional[bool]: 

597 hpd = self.has_pd() 

598 if not hpd: 

599 return hpd 

600 if not self.is_complete_anankastic(): 

601 return None 

602 return self.count_booleans(self.ANANKASTIC_FIELDS) >= 4 

603 

604 def has_anxious_pd(self) -> Optional[bool]: 

605 hpd = self.has_pd() 

606 if not hpd: 

607 return hpd 

608 if not self.is_complete_anxious(): 

609 return None 

610 return self.count_booleans(self.ANXIOUS_FIELDS) >= 4 

611 

612 def has_dependent_pd(self) -> Optional[bool]: 

613 hpd = self.has_pd() 

614 if not hpd: 

615 return hpd 

616 if not self.is_complete_dependent(): 

617 return None 

618 return self.count_booleans(self.DEPENDENT_FIELDS) >= 4 

619 

620 def is_complete(self) -> bool: 

621 return ( 

622 self.date_pertains_to is not None 

623 and ( 

624 self.is_pd_excluded() 

625 or ( 

626 self.is_complete_general() 

627 and (self.skip_paranoid or self.is_complete_paranoid()) 

628 and (self.skip_schizoid or self.is_complete_schizoid()) 

629 and (self.skip_dissocial or self.is_complete_dissocial()) 

630 and (self.skip_eu or self.is_complete_eu()) 

631 and (self.skip_histrionic or self.is_complete_histrionic()) 

632 and (self.skip_anankastic or self.is_complete_anankastic()) 

633 and (self.skip_anxious or self.is_complete_anxious()) 

634 and (self.skip_dependent or self.is_complete_dependent()) 

635 ) 

636 ) 

637 and self.field_contents_valid() 

638 ) 

639 

640 def pd_heading(self, req: CamcopsRequest, wstringname: str) -> str: 

641 return f""" 

642 <tr class="{CssClass.HEADING}"> 

643 <td colspan="2">{self.wxstring(req, wstringname)}</td> 

644 </tr> 

645 """ 

646 

647 def pd_skiprow(self, req: CamcopsRequest, stem: str) -> str: 

648 return self.get_twocol_bool_row( 

649 req, "skip_" + stem, label=self.wxstring(req, "skip_this_pd") 

650 ) 

651 

652 def pd_subheading(self, req: CamcopsRequest, wstringname: str) -> str: 

653 return f""" 

654 <tr class="{CssClass.SUBHEADING}"> 

655 <td colspan="2">{self.wxstring(req, wstringname)}</td> 

656 </tr> 

657 """ 

658 

659 def pd_general_criteria_bits(self, req: CamcopsRequest) -> str: 

660 return f""" 

661 <tr> 

662 <td>{self.wxstring(req, "general_criteria_must_be_met")}</td> 

663 <td><i><b>{get_yes_no_unknown(req, self.has_pd())}</b></i></td> 

664 </tr> 

665 """ 

666 

667 def pd_b_text(self, req: CamcopsRequest, wstringname: str) -> str: 

668 return f""" 

669 <tr> 

670 <td>{self.wxstring(req, wstringname)}</td> 

671 <td class="{CssClass.SUBHEADING}"></td> 

672 </tr> 

673 """ 

674 

675 def pd_basic_row(self, req: CamcopsRequest, stem: str, i: int) -> str: 

676 return self.get_twocol_bool_row_true_false( 

677 req, stem + str(i), self.wxstring(req, stem + str(i)) 

678 ) 

679 

680 def standard_pd_html(self, req: CamcopsRequest, stem: str, n: int) -> str: 

681 html = self.pd_heading(req, stem + "_pd_title") 

682 html += self.pd_skiprow(req, stem) 

683 html += self.pd_general_criteria_bits(req) 

684 html += self.pd_b_text(req, stem + "_pd_B") 

685 for i in range(1, n + 1): 

686 html += self.pd_basic_row(req, stem, i) 

687 return html 

688 

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

690 h = self.get_standard_clinician_comments_block(req, self.comments) 

691 h += f""" 

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

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

694 """ 

695 h += self.get_is_complete_tr(req) 

696 h += tr_qa( 

697 req.wappstring(AS.DATE_PERTAINS_TO), 

698 format_datetime( 

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

700 ), 

701 ) 

702 h += tr_qa( 

703 self.wxstring(req, "meets_general_criteria"), 

704 get_yes_no_none(req, self.has_pd()), 

705 ) 

706 h += tr_qa( 

707 self.wxstring(req, "paranoid_pd_title"), 

708 get_yes_no_none(req, self.has_paranoid_pd()), 

709 ) 

710 h += tr_qa( 

711 self.wxstring(req, "schizoid_pd_title"), 

712 get_yes_no_none(req, self.has_schizoid_pd()), 

713 ) 

714 h += tr_qa( 

715 self.wxstring(req, "dissocial_pd_title"), 

716 get_yes_no_none(req, self.has_dissocial_pd()), 

717 ) 

718 h += tr_qa( 

719 self.wxstring(req, "eu_pd_i_title"), 

720 get_yes_no_none(req, self.has_eupd_i()), 

721 ) 

722 h += tr_qa( 

723 self.wxstring(req, "eu_pd_b_title"), 

724 get_yes_no_none(req, self.has_eupd_b()), 

725 ) 

726 h += tr_qa( 

727 self.wxstring(req, "histrionic_pd_title"), 

728 get_yes_no_none(req, self.has_histrionic_pd()), 

729 ) 

730 h += tr_qa( 

731 self.wxstring(req, "anankastic_pd_title"), 

732 get_yes_no_none(req, self.has_anankastic_pd()), 

733 ) 

734 h += tr_qa( 

735 self.wxstring(req, "anxious_pd_title"), 

736 get_yes_no_none(req, self.has_anxious_pd()), 

737 ) 

738 h += tr_qa( 

739 self.wxstring(req, "dependent_pd_title"), 

740 get_yes_no_none(req, self.has_dependent_pd()), 

741 ) 

742 

743 h += f""" 

744 </table> 

745 </div> 

746 <div> 

747 <p><i>Vignette:</i></p> 

748 <p>{answer(ws.webify(self.vignette), 

749 default_for_blank_strings=True)}</p> 

750 </div> 

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

752 <tr> 

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

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

755 </tr> 

756 """ 

757 

758 # General 

759 h += subheading_spanning_two_columns(self.wxstring(req, "general")) 

760 h += self.get_twocol_bool_row_true_false( 

761 req, "g1", self.wxstring(req, "G1") 

762 ) 

763 h += self.pd_b_text(req, "G1b") 

764 for i in range(1, Icd10SpecPD.N_GENERAL_1 + 1): 

765 h += self.get_twocol_bool_row_true_false( 

766 req, "g1_" + str(i), self.wxstring(req, "G1_" + str(i)) 

767 ) 

768 for i in range(2, Icd10SpecPD.N_GENERAL + 1): 

769 h += self.get_twocol_bool_row_true_false( 

770 req, "g" + str(i), self.wxstring(req, "G" + str(i)) 

771 ) 

772 

773 # Paranoid, etc. 

774 h += self.standard_pd_html(req, "paranoid", Icd10SpecPD.N_PARANOID) 

775 h += self.standard_pd_html(req, "schizoid", Icd10SpecPD.N_SCHIZOID) 

776 h += self.standard_pd_html(req, "dissocial", Icd10SpecPD.N_DISSOCIAL) 

777 

778 # EUPD is special 

779 h += self.pd_heading(req, "eu_pd_title") 

780 h += self.pd_skiprow(req, "eu") 

781 h += self.pd_general_criteria_bits(req) 

782 h += self.pd_subheading(req, "eu_pd_i_title") 

783 h += self.pd_b_text(req, "eu_pd_i_B") 

784 for i in range(1, Icd10SpecPD.N_EUPD_I + 1): 

785 h += self.pd_basic_row(req, "eu", i) 

786 h += self.pd_subheading(req, "eu_pd_b_title") 

787 h += self.pd_b_text(req, "eu_pd_b_B") 

788 for i in range(Icd10SpecPD.N_EUPD_I + 1, Icd10SpecPD.N_EU + 1): 

789 h += self.pd_basic_row(req, "eu", i) 

790 

791 # Back to plain ones 

792 h += self.standard_pd_html(req, "histrionic", Icd10SpecPD.N_HISTRIONIC) 

793 h += self.standard_pd_html(req, "anankastic", Icd10SpecPD.N_ANANKASTIC) 

794 h += self.standard_pd_html(req, "anxious", Icd10SpecPD.N_ANXIOUS) 

795 h += self.standard_pd_html(req, "dependent", Icd10SpecPD.N_DEPENDENT) 

796 

797 # Done 

798 h += ( 

799 """ 

800 </table> 

801 """ 

802 + ICD10_COPYRIGHT_DIV 

803 ) 

804 return h