Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/icd10specpd.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

12 CamCOPS is free software: you can redistribute it and/or modify 

13 it under the terms of the GNU General Public License as published by 

14 the Free Software Foundation, either version 3 of the License, or 

15 (at your option) any later version. 

16 

17 CamCOPS is distributed in the hope that it will be useful, 

18 but WITHOUT ANY WARRANTY; without even the implied warranty of 

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

22 You should have received a copy of the GNU General Public License 

23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

24 

25=============================================================================== 

26 

27""" 

28 

29from typing import Any, Dict, List, Optional, Tuple, Type 

30 

31from cardinal_pythonlib.datetimefunc import format_datetime 

32import cardinal_pythonlib.rnc_web as ws 

33from cardinal_pythonlib.stringfunc import strseq 

34from cardinal_pythonlib.typetests import is_false 

35from sqlalchemy.ext.declarative import DeclarativeMeta 

36from sqlalchemy.sql.schema import Column 

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

38 

39from camcops_server.cc_modules.cc_constants import ( 

40 CssClass, 

41 DateFormat, 

42 ICD10_COPYRIGHT_DIV, 

43 PV, 

44) 

45from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

46from camcops_server.cc_modules.cc_db import add_multiple_columns 

47from camcops_server.cc_modules.cc_html import ( 

48 answer, 

49 get_yes_no_none, 

50 get_yes_no_unknown, 

51 subheading_spanning_two_columns, 

52 tr_qa, 

53) 

54from camcops_server.cc_modules.cc_request import CamcopsRequest 

55from camcops_server.cc_modules.cc_sqla_coltypes import ( 

56 BIT_CHECKER, 

57 CamcopsColumn, 

58) 

59from camcops_server.cc_modules.cc_string import AS 

60from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

61from camcops_server.cc_modules.cc_task import ( 

62 Task, 

63 TaskHasClinicianMixin, 

64 TaskHasPatientMixin, 

65) 

66 

67 

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

69# Icd10SpecPD 

70# ============================================================================= 

71 

72def ctv_info_pd(req: CamcopsRequest, 

73 condition: str, has_it: Optional[bool]) -> CtvInfo: 

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

75 

76 

77class Icd10SpecPDMetaclass(DeclarativeMeta): 

78 # noinspection PyInitNewSignature 

79 def __init__(cls: Type['Icd10SpecPD'], 

80 name: str, 

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

82 classdict: Dict[str, Any]) -> None: 

83 add_multiple_columns( 

84 cls, "g", 1, cls.N_GENERAL, Boolean, 

85 pv=PV.BIT, 

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

87 comment_strings=["pathological 1", "pervasive", 

88 "pathological 2", "persistent", 

89 "primary 1", "primary 2"] 

90 ) 

91 add_multiple_columns( 

92 cls, "g1_", 1, cls.N_GENERAL_1, Boolean, 

93 pv=PV.BIT, 

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

95 comment_strings=["cognition", "affectivity", 

96 "impulse control", "interpersonal"] 

97 ) 

98 add_multiple_columns( 

99 cls, "paranoid", 1, cls.N_PARANOID, Boolean, 

100 pv=PV.BIT, 

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

102 comment_strings=["sensitive", "grudges", "suspicious", 

103 "personal rights", "sexual jealousy", 

104 "self-referential", "conspiratorial"] 

105 ) 

106 add_multiple_columns( 

107 cls, "schizoid", 1, cls.N_SCHIZOID, 

108 Boolean, 

109 pv=PV.BIT, 

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

111 comment_strings=["little pleasure", 

112 "cold/detached", 

113 "limited capacity for warmth", 

114 "indifferent to praise/criticism", 

115 "little interest in sex", 

116 "solitary", 

117 "fantasy/introspection", 

118 "0/1 close friends/confidants", 

119 "insensitive to social norms"] 

120 ) 

121 add_multiple_columns( 

122 cls, "dissocial", 1, cls.N_DISSOCIAL, Boolean, 

123 pv=PV.BIT, 

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

125 comment_strings=["unconcern", "irresponsibility", 

126 "incapacity to maintain relationships", 

127 "low tolerance to frustration", 

128 "incapacity for guilt", 

129 "prone to blame others"] 

130 ) 

131 add_multiple_columns( 

132 cls, "eu", 1, cls.N_EU, Boolean, 

133 pv=PV.BIT, 

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

135 comment_strings=["act without considering consequences", 

136 "quarrelsome", "outbursts of anger", 

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

138 "unstable/capricious mood", 

139 "uncertain self-image", 

140 "intense/unstable relationships", 

141 "avoids abandonment", 

142 "threats/acts of self-harm", 

143 "feelings of emptiness"] 

144 ) 

145 add_multiple_columns( 

146 cls, "histrionic", 1, cls.N_HISTRIONIC, Boolean, 

147 pv=PV.BIT, 

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

149 comment_strings=["theatricality", 

150 "suggestibility", 

151 "shallow/labile affect", 

152 "centre of attention", 

153 "inappropriately seductive", 

154 "concerned with attractiveness"] 

155 ) 

156 add_multiple_columns( 

157 cls, "anankastic", 1, cls.N_ANANKASTIC, Boolean, 

158 pv=PV.BIT, 

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

160 comment_strings=["doubt/caution", 

161 "preoccupation with details", 

162 "perfectionism", 

163 "excessively conscientious", 

164 "preoccupied with productivity", 

165 "excessive pedantry", 

166 "rigid/stubborn", 

167 "require others do things specific way"] 

168 ) 

169 add_multiple_columns( 

170 cls, "anxious", 1, cls.N_ANXIOUS, Boolean, 

171 pv=PV.BIT, 

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

173 comment_strings=["tension/apprehension", 

174 "preoccupied with criticism/rejection", 

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

176 "need for security restricts lifestyle", 

177 "avoidance of interpersonal contact"] 

178 ) 

179 add_multiple_columns( 

180 cls, "dependent", 1, cls.N_DEPENDENT, Boolean, 

181 pv=PV.BIT, 

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

183 comment_strings=["others decide", 

184 "subordinate needs to those of others", 

185 "unwilling to make reasonable demands", 

186 "uncomfortable/helpless when alone", 

187 "fears of being left to oneself", 

188 "everyday decisions require advice/reassurance"] 

189 ) 

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

191 

192 

193class Icd10SpecPD(TaskHasClinicianMixin, TaskHasPatientMixin, Task, 

194 metaclass=Icd10SpecPDMetaclass): 

195 """ 

196 Server implementation of the ICD10-PD task. 

197 """ 

198 __tablename__ = "icd10specpd" 

199 shortname = "ICD10-PD" 

200 

201 date_pertains_to = Column( 

202 "date_pertains_to", Date, 

203 comment="Date the assessment pertains to" 

204 ) 

205 comments = Column( 

206 "comments", UnicodeText, 

207 comment="Clinician's comments" 

208 ) 

209 skip_paranoid = CamcopsColumn( 

210 "skip_paranoid", Boolean, 

211 permitted_value_checker=BIT_CHECKER, 

212 comment="Skip questions for paranoid PD?" 

213 ) 

214 skip_schizoid = CamcopsColumn( 

215 "skip_schizoid", Boolean, 

216 permitted_value_checker=BIT_CHECKER, 

217 comment="Skip questions for schizoid PD?" 

218 ) 

219 skip_dissocial = CamcopsColumn( 

220 "skip_dissocial", Boolean, 

221 permitted_value_checker=BIT_CHECKER, 

222 comment="Skip questions for dissocial PD?" 

223 ) 

224 skip_eu = CamcopsColumn( 

225 "skip_eu", Boolean, 

226 permitted_value_checker=BIT_CHECKER, 

227 comment="Skip questions for emotionally unstable PD?" 

228 ) 

229 skip_histrionic = CamcopsColumn( 

230 "skip_histrionic", Boolean, 

231 permitted_value_checker=BIT_CHECKER, 

232 comment="Skip questions for histrionic PD?" 

233 ) 

234 skip_anankastic = CamcopsColumn( 

235 "skip_anankastic", Boolean, 

236 permitted_value_checker=BIT_CHECKER, 

237 comment="Skip questions for anankastic PD?" 

238 ) 

239 skip_anxious = CamcopsColumn( 

240 "skip_anxious", Boolean, 

241 permitted_value_checker=BIT_CHECKER, 

242 comment="Skip questions for anxious PD?" 

243 ) 

244 skip_dependent = CamcopsColumn( 

245 "skip_dependent", Boolean, 

246 permitted_value_checker=BIT_CHECKER, 

247 comment="Skip questions for dependent PD?" 

248 ) 

249 other_pd_present = CamcopsColumn( 

250 "other_pd_present", Boolean, 

251 permitted_value_checker=BIT_CHECKER, 

252 comment="Is another personality disorder present?" 

253 ) 

254 vignette = Column( 

255 "vignette", UnicodeText, 

256 comment="Vignette" 

257 ) 

258 

259 N_GENERAL = 6 

260 N_GENERAL_1 = 4 

261 N_PARANOID = 7 

262 N_SCHIZOID = 9 

263 N_DISSOCIAL = 6 

264 N_EU = 10 

265 N_EUPD_I = 5 

266 N_HISTRIONIC = 6 

267 N_ANANKASTIC = 8 

268 N_ANXIOUS = 5 

269 N_DEPENDENT = 6 

270 

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

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

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

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

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

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

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

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

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

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

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

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

283 

284 @staticmethod 

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

286 _ = req.gettext 

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

288 

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

290 if not self.is_complete(): 

291 return CTV_INCOMPLETE 

292 infolist = [ctv_info_pd(req, 

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

294 self.has_pd()), 

295 ctv_info_pd(req, 

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

297 self.has_paranoid_pd()), 

298 ctv_info_pd(req, 

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

300 self.has_schizoid_pd()), 

301 ctv_info_pd(req, 

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

303 self.has_dissocial_pd()), 

304 ctv_info_pd(req, 

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

306 self.has_eupd_i()), 

307 ctv_info_pd(req, 

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

309 self.has_eupd_b()), 

310 ctv_info_pd(req, 

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

312 self.has_histrionic_pd()), 

313 ctv_info_pd(req, 

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

315 self.has_anankastic_pd()), 

316 ctv_info_pd(req, 

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

318 self.has_anxious_pd()), 

319 ctv_info_pd(req, 

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

321 self.has_dependent_pd())] 

322 return infolist 

323 

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

325 return self.standard_task_summary_fields() + [ 

326 SummaryElement( 

327 name="meets_general_criteria", coltype=Boolean(), 

328 value=self.has_pd(), 

329 comment="Meets general criteria for personality disorder?"), 

330 SummaryElement( 

331 name="paranoid_pd", coltype=Boolean(), 

332 value=self.has_paranoid_pd(), 

333 comment="Meets criteria for paranoid PD?"), 

334 SummaryElement( 

335 name="schizoid_pd", coltype=Boolean(), 

336 value=self.has_schizoid_pd(), 

337 comment="Meets criteria for schizoid PD?"), 

338 SummaryElement( 

339 name="dissocial_pd", coltype=Boolean(), 

340 value=self.has_dissocial_pd(), 

341 comment="Meets criteria for dissocial PD?"), 

342 SummaryElement( 

343 name="eupd_i", coltype=Boolean(), 

344 value=self.has_eupd_i(), 

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

346 SummaryElement( 

347 name="eupd_b", coltype=Boolean(), 

348 value=self.has_eupd_b(), 

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

350 SummaryElement( 

351 name="histrionic_pd", coltype=Boolean(), 

352 value=self.has_histrionic_pd(), 

353 comment="Meets criteria for histrionic PD?"), 

354 SummaryElement( 

355 name="anankastic_pd", coltype=Boolean(), 

356 value=self.has_anankastic_pd(), 

357 comment="Meets criteria for anankastic PD?"), 

358 SummaryElement( 

359 name="anxious_pd", coltype=Boolean(), 

360 value=self.has_anxious_pd(), 

361 comment="Meets criteria for anxious PD?"), 

362 SummaryElement( 

363 name="dependent_pd", coltype=Boolean(), 

364 value=self.has_dependent_pd(), 

365 comment="Meets criteria for dependent PD?"), 

366 ] 

367 

368 # noinspection PyUnresolvedReferences 

369 def is_pd_excluded(self) -> bool: 

370 return ( 

371 is_false(self.g1) or 

372 is_false(self.g2) or 

373 is_false(self.g3) or 

374 is_false(self.g4) or 

375 is_false(self.g5) or 

376 is_false(self.g6) or 

377 ( 

378 self.all_fields_not_none(self.GENERAL_1_FIELDS) and 

379 self.count_booleans(self.GENERAL_1_FIELDS) <= 1 

380 ) 

381 ) 

382 

383 def is_complete_general(self) -> bool: 

384 return ( 

385 self.all_fields_not_none(self.GENERAL_FIELDS) and 

386 self.all_fields_not_none(self.GENERAL_1_FIELDS) 

387 ) 

388 

389 def is_complete_paranoid(self) -> bool: 

390 return self.all_fields_not_none(self.PARANOID_FIELDS) 

391 

392 def is_complete_schizoid(self) -> bool: 

393 return self.all_fields_not_none(self.SCHIZOID_FIELDS) 

394 

395 def is_complete_dissocial(self) -> bool: 

396 return self.all_fields_not_none(self.DISSOCIAL_FIELDS) 

397 

398 def is_complete_eu(self) -> bool: 

399 return self.all_fields_not_none(self.EU_FIELDS) 

400 

401 def is_complete_histrionic(self) -> bool: 

402 return self.all_fields_not_none(self.HISTRIONIC_FIELDS) 

403 

404 def is_complete_anankastic(self) -> bool: 

405 return self.all_fields_not_none(self.ANANKASTIC_FIELDS) 

406 

407 def is_complete_anxious(self) -> bool: 

408 return self.all_fields_not_none(self.ANXIOUS_FIELDS) 

409 

410 def is_complete_dependent(self) -> bool: 

411 return self.all_fields_not_none(self.DEPENDENT_FIELDS) 

412 

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

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

415 if self.is_pd_excluded(): 

416 return False 

417 if not self.is_complete_general(): 

418 return None 

419 return ( 

420 self.all_truthy(self.GENERAL_FIELDS) and 

421 self.count_booleans(self.GENERAL_1_FIELDS) > 1 

422 ) 

423 

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

425 hpd = self.has_pd() 

426 if not hpd: 

427 return hpd 

428 if not self.is_complete_paranoid(): 

429 return None 

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

431 

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

433 hpd = self.has_pd() 

434 if not hpd: 

435 return hpd 

436 if not self.is_complete_schizoid(): 

437 return None 

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

439 

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

441 hpd = self.has_pd() 

442 if not hpd: 

443 return hpd 

444 if not self.is_complete_dissocial(): 

445 return None 

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

447 

448 # noinspection PyUnresolvedReferences 

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

450 hpd = self.has_pd() 

451 if not hpd: 

452 return hpd 

453 if not self.is_complete_eu(): 

454 return None 

455 return ( 

456 self.count_booleans(self.EUPD_I_FIELDS) >= 3 and 

457 self.eu2 

458 ) 

459 

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

461 hpd = self.has_pd() 

462 if not hpd: 

463 return hpd 

464 if not self.is_complete_eu(): 

465 return None 

466 return ( 

467 self.count_booleans(self.EUPD_I_FIELDS) >= 3 and 

468 self.count_booleans(self.EUPD_B_FIELDS) >= 2 

469 ) 

470 

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

472 hpd = self.has_pd() 

473 if not hpd: 

474 return hpd 

475 if not self.is_complete_histrionic(): 

476 return None 

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

478 

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

480 hpd = self.has_pd() 

481 if not hpd: 

482 return hpd 

483 if not self.is_complete_anankastic(): 

484 return None 

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

486 

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

488 hpd = self.has_pd() 

489 if not hpd: 

490 return hpd 

491 if not self.is_complete_anxious(): 

492 return None 

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

494 

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

496 hpd = self.has_pd() 

497 if not hpd: 

498 return hpd 

499 if not self.is_complete_dependent(): 

500 return None 

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

502 

503 def is_complete(self) -> bool: 

504 return ( 

505 self.date_pertains_to is not None and ( 

506 self.is_pd_excluded() or ( 

507 self.is_complete_general() and 

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

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

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

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

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

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

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

515 (self.skip_dependent or self.is_complete_dependent()) 

516 ) 

517 ) and 

518 self.field_contents_valid() 

519 ) 

520 

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

522 return f""" 

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

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

525 </tr> 

526 """ 

527 

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

529 return self.get_twocol_bool_row( 

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

531 

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

533 return f""" 

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

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

536 </tr> 

537 """ 

538 

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

540 return f""" 

541 <tr> 

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

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

544 </tr> 

545 """ 

546 

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

548 return f""" 

549 <tr> 

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

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

552 </tr> 

553 """ 

554 

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

556 return self.get_twocol_bool_row_true_false( 

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

558 

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

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

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

562 html += self.pd_general_criteria_bits(req) 

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

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

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

566 return html 

567 

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

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

570 h += f""" 

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

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

573 """ 

574 h += self.get_is_complete_tr(req) 

575 h += tr_qa(req.wappstring(AS.DATE_PERTAINS_TO), 

576 format_datetime(self.date_pertains_to, 

577 DateFormat.LONG_DATE, default=None)) 

578 h += tr_qa(self.wxstring(req, "meets_general_criteria"), 

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

580 h += tr_qa(self.wxstring(req, "paranoid_pd_title"), 

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

582 h += tr_qa(self.wxstring(req, "schizoid_pd_title"), 

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

584 h += tr_qa(self.wxstring(req, "dissocial_pd_title"), 

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

586 h += tr_qa(self.wxstring(req, "eu_pd_i_title"), 

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

588 h += tr_qa(self.wxstring(req, "eu_pd_b_title"), 

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

590 h += tr_qa(self.wxstring(req, "histrionic_pd_title"), 

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

592 h += tr_qa(self.wxstring(req, "anankastic_pd_title"), 

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

594 h += tr_qa(self.wxstring(req, "anxious_pd_title"), 

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

596 h += tr_qa(self.wxstring(req, "dependent_pd_title"), 

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

598 

599 h += f""" 

600 </table> 

601 </div> 

602 <div> 

603 <p><i>Vignette:</i></p> 

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

605 default_for_blank_strings=True)}</p> 

606 </div> 

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

608 <tr> 

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

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

611 </tr> 

612 """ 

613 

614 # General 

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

616 h += self.get_twocol_bool_row_true_false( 

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

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

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

620 h += self.get_twocol_bool_row_true_false( 

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

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

623 h += self.get_twocol_bool_row_true_false( 

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

625 

626 # Paranoid, etc. 

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

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

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

630 

631 # EUPD is special 

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

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

634 h += self.pd_general_criteria_bits(req) 

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

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

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

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

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

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

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

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

643 

644 # Back to plain ones 

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

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

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

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

649 

650 # Done 

651 h += """ 

652 </table> 

653 """ + ICD10_COPYRIGHT_DIV 

654 return h