Coverage for tasks/icd10specpd.py: 43%

1"""

2 camcops_server/tasks/icd10specpd.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, List, Optional, Type

29

30 from cardinal_pythonlib.datetimefunc import format_datetime

31 import cardinal_pythonlib.rnc_web as ws

32 from cardinal_pythonlib.stringfunc import strseq

33 from cardinal_pythonlib.typetests import is_false

34 from sqlalchemy.orm import Mapped

35 from sqlalchemy.sql.schema import Column

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

37

38 from camcops_server.cc_modules.cc_constants import (

39 CssClass,

40 DateFormat,

41 ICD10_COPYRIGHT_DIV,

42 PV,

43)

44 from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo

45 from camcops_server.cc_modules.cc_db import add_multiple_columns

46 from camcops_server.cc_modules.cc_html import (

47 answer,

48 get_yes_no_none,

49 get_yes_no_unknown,

50 subheading_spanning_two_columns,

51 tr_qa,

52)

53 from camcops_server.cc_modules.cc_request import CamcopsRequest

54 from camcops_server.cc_modules.cc_sqla_coltypes import (

55 BIT_CHECKER,

56 mapped_camcops_column,

57)

58 from camcops_server.cc_modules.cc_string import AS

59 from camcops_server.cc_modules.cc_summaryelement import SummaryElement

60 from camcops_server.cc_modules.cc_task import (

61 Task,

62 TaskHasClinicianMixin,

63 TaskHasPatientMixin,

64)

65

66

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

68# Icd10SpecPD

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

70

71

72 def ctv_info_pd(

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

74) -> CtvInfo:

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

76

77

78 class Icd10SpecPD( # type: ignore[misc]

79 TaskHasClinicianMixin,

80 TaskHasPatientMixin,

81 Task,

82):

83 """

84 Server implementation of the ICD10-PD task.

85 """

86

87 __tablename__ = "icd10specpd"

88 shortname = "ICD10-PD"

89 info_filename_stem = "icd"

90

91 @classmethod

92 def extend_columns(cls: Type["Icd10SpecPD"], **kwargs: Any) -> None:

93 add_multiple_columns(

94 cls,

95 "g",

96 1,

97 cls.N_GENERAL,

98 Boolean,

99 pv=PV.BIT,

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

101 comment_strings=[

102 "pathological 1",

103 "pervasive",

104 "pathological 2",

105 "persistent",

106 "primary 1",

107 "primary 2",

108 ],

109 )

110 add_multiple_columns(

111 cls,

112 "g1_",

113 1,

114 cls.N_GENERAL_1,

115 Boolean,

116 pv=PV.BIT,

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

118 comment_strings=[

119 "cognition",

120 "affectivity",

121 "impulse control",

122 "interpersonal",

123 ],

124 )

125 add_multiple_columns(

126 cls,

127 "paranoid",

128 1,

129 cls.N_PARANOID,

130 Boolean,

131 pv=PV.BIT,

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

133 comment_strings=[

134 "sensitive",

135 "grudges",

136 "suspicious",

137 "personal rights",

138 "sexual jealousy",

139 "self-referential",

140 "conspiratorial",

141 ],

142 )

143 add_multiple_columns(

144 cls,

145 "schizoid",

146 1,

147 cls.N_SCHIZOID,

148 Boolean,

149 pv=PV.BIT,

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

151 comment_strings=[

152 "little pleasure",

153 "cold/detached",

154 "limited capacity for warmth",

155 "indifferent to praise/criticism",

156 "little interest in sex",

157 "solitary",

158 "fantasy/introspection",

159 "0/1 close friends/confidants",

160 "insensitive to social norms",

161 ],

162 )

163 add_multiple_columns(

164 cls,

165 "dissocial",

166 1,

167 cls.N_DISSOCIAL,

168 Boolean,

169 pv=PV.BIT,

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

171 comment_strings=[

172 "unconcern",

173 "irresponsibility",

174 "incapacity to maintain relationships",

175 "low tolerance to frustration",

176 "incapacity for guilt",

177 "prone to blame others",

178 ],

179 )

180 add_multiple_columns(

181 cls,

182 "eu",

183 1,

184 cls.N_EU,

185 Boolean,

186 pv=PV.BIT,

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

188 comment_strings=[

189 "act without considering consequences",

190 "quarrelsome",

191 "outbursts of anger",

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

193 "unstable/capricious mood",

194 "uncertain self-image",

195 "intense/unstable relationships",

196 "avoids abandonment",

197 "threats/acts of self-harm",

198 "feelings of emptiness",

199 ],

200 )

201 add_multiple_columns(

202 cls,

203 "histrionic",

204 1,

205 cls.N_HISTRIONIC,

206 Boolean,

207 pv=PV.BIT,

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

209 comment_strings=[

210 "theatricality",

211 "suggestibility",

212 "shallow/labile affect",

213 "centre of attention",

214 "inappropriately seductive",

215 "concerned with attractiveness",

216 ],

217 )

218 add_multiple_columns(

219 cls,

220 "anankastic",

221 1,

222 cls.N_ANANKASTIC,

223 Boolean,

224 pv=PV.BIT,

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

226 comment_strings=[

227 "doubt/caution",

228 "preoccupation with details",

229 "perfectionism",

230 "excessively conscientious",

231 "preoccupied with productivity",

232 "excessive pedantry",

233 "rigid/stubborn",

234 "require others do things specific way",

235 ],

236 )

237 add_multiple_columns(

238 cls,

239 "anxious",

240 1,

241 cls.N_ANXIOUS,

242 Boolean,

243 pv=PV.BIT,

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

245 comment_strings=[

246 "tension/apprehension",

247 "preoccupied with criticism/rejection",

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

249 "need for security restricts lifestyle",

250 "avoidance of interpersonal contact",

251 ],

252 )

253 add_multiple_columns(

254 cls,

255 "dependent",

256 1,

257 cls.N_DEPENDENT,

258 Boolean,

259 pv=PV.BIT,

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

261 comment_strings=[

262 "others decide",

263 "subordinate needs to those of others",

264 "unwilling to make reasonable demands",

265 "uncomfortable/helpless when alone",

266 "fears of being left to oneself",

267 "everyday decisions require advice/reassurance",

268 ],

269 )

270

271 date_pertains_to = Column(

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

273 )

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

275 skip_paranoid: Mapped[Optional[bool]] = mapped_camcops_column(

276 permitted_value_checker=BIT_CHECKER,

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

278 )

279 skip_schizoid: Mapped[Optional[bool]] = mapped_camcops_column(

280 permitted_value_checker=BIT_CHECKER,

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

282 )

283 skip_dissocial: Mapped[Optional[bool]] = mapped_camcops_column(

284 permitted_value_checker=BIT_CHECKER,

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

286 )

287 skip_eu: Mapped[Optional[bool]] = mapped_camcops_column(

288 permitted_value_checker=BIT_CHECKER,

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

290 )

291 skip_histrionic: Mapped[Optional[bool]] = mapped_camcops_column(

292 permitted_value_checker=BIT_CHECKER,

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

294 )

295 skip_anankastic: Mapped[Optional[bool]] = mapped_camcops_column(

296 permitted_value_checker=BIT_CHECKER,

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

298 )

299 skip_anxious: Mapped[Optional[bool]] = mapped_camcops_column(

300 permitted_value_checker=BIT_CHECKER,

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

302 )

303 skip_dependent: Mapped[Optional[bool]] = mapped_camcops_column(

304 permitted_value_checker=BIT_CHECKER,

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

306 )

307 other_pd_present: Mapped[Optional[bool]] = mapped_camcops_column(

308 permitted_value_checker=BIT_CHECKER,

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

310 )

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

312

313 N_GENERAL = 6

314 N_GENERAL_1 = 4

315 N_PARANOID = 7

316 N_SCHIZOID = 9

317 N_DISSOCIAL = 6

318 N_EU = 10

319 N_EUPD_I = 5

320 N_HISTRIONIC = 6

321 N_ANANKASTIC = 8

322 N_ANXIOUS = 5

323 N_DEPENDENT = 6

324

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

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

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

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

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

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

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

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

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

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

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

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

337

338 @staticmethod

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

340 _ = req.gettext

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

342

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

344 if not self.is_complete():

345 return CTV_INCOMPLETE

346 infolist = [

347 ctv_info_pd(

348 req,

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

350 self.has_pd(),

351 ),

352 ctv_info_pd(

353 req,

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

355 self.has_paranoid_pd(),

356 ),

357 ctv_info_pd(

358 req,

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

360 self.has_schizoid_pd(),

361 ),

362 ctv_info_pd(

363 req,

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

365 self.has_dissocial_pd(),

366 ),

367 ctv_info_pd(

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

369 ),

370 ctv_info_pd(

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

372 ),

373 ctv_info_pd(

374 req,

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

376 self.has_histrionic_pd(),

377 ),

378 ctv_info_pd(

379 req,

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

381 self.has_anankastic_pd(),

382 ),

383 ctv_info_pd(

384 req,

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

386 self.has_anxious_pd(),

387 ),

388 ctv_info_pd(

389 req,

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

391 self.has_dependent_pd(),

392 ),

393 ]

394 return infolist

395

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

397 return self.standard_task_summary_fields() + [

398 SummaryElement(

399 name="meets_general_criteria",

400 coltype=Boolean(),

401 value=self.has_pd(),

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

403 ),

404 SummaryElement(

405 name="paranoid_pd",

406 coltype=Boolean(),

407 value=self.has_paranoid_pd(),

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

409 ),

410 SummaryElement(

411 name="schizoid_pd",

412 coltype=Boolean(),

413 value=self.has_schizoid_pd(),

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

415 ),

416 SummaryElement(

417 name="dissocial_pd",

418 coltype=Boolean(),

419 value=self.has_dissocial_pd(),

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

421 ),

422 SummaryElement(

423 name="eupd_i",

424 coltype=Boolean(),

425 value=self.has_eupd_i(),

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

427 ),

428 SummaryElement(

429 name="eupd_b",

430 coltype=Boolean(),

431 value=self.has_eupd_b(),

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

433 ),

434 SummaryElement(

435 name="histrionic_pd",

436 coltype=Boolean(),

437 value=self.has_histrionic_pd(),

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

439 ),

440 SummaryElement(

441 name="anankastic_pd",

442 coltype=Boolean(),

443 value=self.has_anankastic_pd(),

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

445 ),

446 SummaryElement(

447 name="anxious_pd",

448 coltype=Boolean(),

449 value=self.has_anxious_pd(),

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

451 ),

452 SummaryElement(

453 name="dependent_pd",

454 coltype=Boolean(),

455 value=self.has_dependent_pd(),

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

457 ),

458 ]

459

460 # noinspection PyUnresolvedReferences

461 def is_pd_excluded(self) -> bool:

462 return (

463 is_false(self.g1) # type: ignore[attr-defined]

464 or is_false(self.g2) # type: ignore[attr-defined]

465 or is_false(self.g3) # type: ignore[attr-defined]

466 or is_false(self.g4) # type: ignore[attr-defined]

467 or is_false(self.g5) # type: ignore[attr-defined]

468 or is_false(self.g6) # type: ignore[attr-defined]

469 or (

470 self.all_fields_not_none(self.GENERAL_1_FIELDS)

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

472 )

473 )

474

475 def is_complete_general(self) -> bool:

476 return self.all_fields_not_none(

477 self.GENERAL_FIELDS

478 ) and self.all_fields_not_none(self.GENERAL_1_FIELDS)

479

480 def is_complete_paranoid(self) -> bool:

481 return self.all_fields_not_none(self.PARANOID_FIELDS)

482

483 def is_complete_schizoid(self) -> bool:

484 return self.all_fields_not_none(self.SCHIZOID_FIELDS)

485

486 def is_complete_dissocial(self) -> bool:

487 return self.all_fields_not_none(self.DISSOCIAL_FIELDS)

488

489 def is_complete_eu(self) -> bool:

490 return self.all_fields_not_none(self.EU_FIELDS)

491

492 def is_complete_histrionic(self) -> bool:

493 return self.all_fields_not_none(self.HISTRIONIC_FIELDS)

494

495 def is_complete_anankastic(self) -> bool:

496 return self.all_fields_not_none(self.ANANKASTIC_FIELDS)

497

498 def is_complete_anxious(self) -> bool:

499 return self.all_fields_not_none(self.ANXIOUS_FIELDS)

500

501 def is_complete_dependent(self) -> bool:

502 return self.all_fields_not_none(self.DEPENDENT_FIELDS)

503

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

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

506 if self.is_pd_excluded():

507 return False

508 if not self.is_complete_general():

509 return None

510 return (

511 self.all_truthy(self.GENERAL_FIELDS)

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

513 )

514

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

516 hpd = self.has_pd()

517 if not hpd:

518 return hpd

519 if not self.is_complete_paranoid():

520 return None

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

522

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

524 hpd = self.has_pd()

525 if not hpd:

526 return hpd

527 if not self.is_complete_schizoid():

528 return None

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

530

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

532 hpd = self.has_pd()

533 if not hpd:

534 return hpd

535 if not self.is_complete_dissocial():

536 return None

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

538

539 # noinspection PyUnresolvedReferences

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

541 hpd = self.has_pd()

542 if not hpd:

543 return hpd

544 if not self.is_complete_eu():

545 return None

546 return self.count_booleans(self.EUPD_I_FIELDS) >= 3 and self.eu2 # type: ignore[attr-defined] # noqa: E501

547

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

549 hpd = self.has_pd()

550 if not hpd:

551 return hpd

552 if not self.is_complete_eu():

553 return None

554 return (

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

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

557 )

558

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

560 hpd = self.has_pd()

561 if not hpd:

562 return hpd

563 if not self.is_complete_histrionic():

564 return None

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

566

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

568 hpd = self.has_pd()

569 if not hpd:

570 return hpd

571 if not self.is_complete_anankastic():

572 return None

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

574

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

576 hpd = self.has_pd()

577 if not hpd:

578 return hpd

579 if not self.is_complete_anxious():

580 return None

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

582

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

584 hpd = self.has_pd()

585 if not hpd:

586 return hpd

587 if not self.is_complete_dependent():

588 return None

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

590

591 def is_complete(self) -> bool:

592 return (

593 self.date_pertains_to is not None

594 and (

595 self.is_pd_excluded()

596 or (

597 self.is_complete_general()

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

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

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

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

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

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

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

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

606 )

607 )

608 and self.field_contents_valid()

609 )

610

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

612 return f"""

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

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

615 </tr>

616 """

617

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

619 return self.get_twocol_bool_row(

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

621 )

622

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

624 return f"""

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

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

627 </tr>

628 """

629

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

631 return f"""

632 <tr>

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

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

635 </tr>

636 """

637

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

639 return f"""

640 <tr>

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

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

643 </tr>

644 """

645

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

647 return self.get_twocol_bool_row_true_false(

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

649 )

650

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

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

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

654 html += self.pd_general_criteria_bits(req)

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

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

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

658 return html

659

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

661 h = self.get_standard_clinician_comments_block(req, self.comments) # type: ignore[arg-type] # noqa: E501

662 h += f"""

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

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

665 """

666 h += self.get_is_complete_tr(req)

667 h += tr_qa(

668 req.wappstring(AS.DATE_PERTAINS_TO),

669 format_datetime(

670 self.date_pertains_to, DateFormat.LONG_DATE, default=None # type: ignore[arg-type] # noqa: E501

671 ),

672 )

673 h += tr_qa(

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

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

676 )

677 h += tr_qa(

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

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

680 )

681 h += tr_qa(

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

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

684 )

685 h += tr_qa(

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

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

688 )

689 h += tr_qa(

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

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

692 )

693 h += tr_qa(

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

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

696 )

697 h += tr_qa(

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

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

700 )

701 h += tr_qa(

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

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

704 )

705 h += tr_qa(

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

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

708 )

709 h += tr_qa(

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

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

712 )

713

714 h += f"""

715 </table>

716 </div>

717 <div>

718 <p><i>Vignette:</i></p>

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

720 default_for_blank_strings=True)}</p>

721 </div>

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

723 <tr>

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

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

726 </tr>

727 """

728

729 # General

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

731 h += self.get_twocol_bool_row_true_false(

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

733 )

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

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

736 h += self.get_twocol_bool_row_true_false(

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

738 )

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

740 h += self.get_twocol_bool_row_true_false(

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

742 )

743

744 # Paranoid, etc.

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

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

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

748

749 # EUPD is special

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

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

752 h += self.pd_general_criteria_bits(req)

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

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

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

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

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

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

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

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

761

762 # Back to plain ones

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

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

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

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

767

768 # Done

769 h += (

770 """

771 </table>

772 """

773 + ICD10_COPYRIGHT_DIV

774 )

775 return h