Coverage for tasks/icd10specpd.py: 44%
237 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/tasks/icd10specpd.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
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.
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.
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/>.
26===============================================================================
28"""
30from typing import Any, Dict, List, Optional, Tuple, Type
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
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)
69# =============================================================================
70# Icd10SpecPD
71# =============================================================================
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))
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)
268class Icd10SpecPD(
269 TaskHasClinicianMixin,
270 TaskHasPatientMixin,
271 Task,
272 metaclass=Icd10SpecPDMetaclass,
273):
274 """
275 Server implementation of the ICD10-PD task.
276 """
278 __tablename__ = "icd10specpd"
279 shortname = "ICD10-PD"
280 info_filename_stem = "icd"
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")
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
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)
367 @staticmethod
368 def longname(req: "CamcopsRequest") -> str:
369 _ = req.gettext
370 return _("ICD-10 criteria for specific personality disorders (F60)")
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
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 ]
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 )
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)
509 def is_complete_paranoid(self) -> bool:
510 return self.all_fields_not_none(self.PARANOID_FIELDS)
512 def is_complete_schizoid(self) -> bool:
513 return self.all_fields_not_none(self.SCHIZOID_FIELDS)
515 def is_complete_dissocial(self) -> bool:
516 return self.all_fields_not_none(self.DISSOCIAL_FIELDS)
518 def is_complete_eu(self) -> bool:
519 return self.all_fields_not_none(self.EU_FIELDS)
521 def is_complete_histrionic(self) -> bool:
522 return self.all_fields_not_none(self.HISTRIONIC_FIELDS)
524 def is_complete_anankastic(self) -> bool:
525 return self.all_fields_not_none(self.ANANKASTIC_FIELDS)
527 def is_complete_anxious(self) -> bool:
528 return self.all_fields_not_none(self.ANXIOUS_FIELDS)
530 def is_complete_dependent(self) -> bool:
531 return self.all_fields_not_none(self.DEPENDENT_FIELDS)
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 )
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
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
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
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
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 )
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
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
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
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
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 )
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 """
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 )
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 """
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 """
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 """
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 )
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
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 )
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 """
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 )
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)
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)
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)
797 # Done
798 h += (
799 """
800 </table>
801 """
802 + ICD10_COPYRIGHT_DIV
803 )
804 return h