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/pswq.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.stringfunc import strseq 

32from sqlalchemy.ext.declarative import DeclarativeMeta 

33from sqlalchemy.sql.sqltypes import Integer 

34 

35from camcops_server.cc_modules.cc_constants import CssClass 

36from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

37from camcops_server.cc_modules.cc_db import add_multiple_columns 

38from camcops_server.cc_modules.cc_html import answer, tr 

39from camcops_server.cc_modules.cc_request import CamcopsRequest 

40from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

41from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

42from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin 

43from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

44 

45 

46# ============================================================================= 

47# PSWQ 

48# ============================================================================= 

49 

50class PswqMetaclass(DeclarativeMeta): 

51 # noinspection PyInitNewSignature 

52 def __init__(cls: Type['Pswq'], 

53 name: str, 

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

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

56 add_multiple_columns( 

57 cls, "q", 1, cls.NQUESTIONS, 

58 minimum=cls.MIN_PER_Q, maximum=cls.MAX_PER_Q, 

59 comment_fmt="Q{n}, {s} (1-5)", 

60 comment_strings=[ 

61 "OK if not enough time [REVERSE SCORE]", # 1 

62 "worries overwhelm", 

63 "do not tend to worry [REVERSE SCORE]", 

64 "many situations make me worry", 

65 "cannot help worrying", # 5 

66 "worry under pressure", 

67 "always worrying", 

68 "easily dismiss worries [REVERSE SCORE]", 

69 "finish then worry about next thing", 

70 "never worry [REVERSE SCORE]", # 10 

71 "if nothing more to do, I do not worry [REVERSE SCORE]", 

72 "lifelong worrier", 

73 "have been worrying", 

74 "when start worrying cannot stop", 

75 "worry all the time", # 15 

76 "worry about projects until done", 

77 ] 

78 ) 

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

80 

81 

82class Pswq(TaskHasPatientMixin, Task, 

83 metaclass=PswqMetaclass): 

84 """ 

85 Server implementation of the PSWQ task. 

86 """ 

87 __tablename__ = "pswq" 

88 shortname = "PSWQ" 

89 provides_trackers = True 

90 

91 MIN_PER_Q = 1 

92 MAX_PER_Q = 5 

93 NQUESTIONS = 16 

94 REVERSE_SCORE = [1, 3, 8, 10, 11] 

95 TASK_FIELDS = strseq("q", 1, NQUESTIONS) 

96 MIN_TOTAL = MIN_PER_Q * NQUESTIONS 

97 MAX_TOTAL = MAX_PER_Q * NQUESTIONS 

98 

99 @staticmethod 

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

101 _ = req.gettext 

102 return _("Penn State Worry Questionnaire") 

103 

104 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]: 

105 return [TrackerInfo( 

106 value=self.total_score(), 

107 plot_label="PSWQ total score (lower is better)", 

108 axis_label=f"Total score ({self.MIN_TOTAL}–{self.MAX_TOTAL})", 

109 axis_min=self.MIN_TOTAL - 0.5, 

110 axis_max=self.MAX_TOTAL + 0.5 

111 )] 

112 

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

114 return self.standard_task_summary_fields() + [ 

115 SummaryElement(name="total_score", coltype=Integer(), 

116 value=self.total_score(), 

117 comment="Total score (16-80)"), 

118 ] 

119 

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

121 if not self.is_complete(): 

122 return CTV_INCOMPLETE 

123 return [CtvInfo( 

124 content=f"PSWQ total score {self.total_score()} " 

125 f"(range {self.MIN_TOTAL}–{self.MAX_TOTAL})" 

126 )] 

127 

128 def score(self, q: int) -> Optional[int]: 

129 value = getattr(self, "q" + str(q)) 

130 if value is None: 

131 return None 

132 if q in self.REVERSE_SCORE: 

133 return self.MAX_PER_Q + 1 - value 

134 else: 

135 return value 

136 

137 def total_score(self) -> int: 

138 values = [self.score(q) for q in range(1, self.NQUESTIONS + 1)] 

139 return sum(v for v in values if v is not None) 

140 

141 def is_complete(self) -> bool: 

142 return ( 

143 self.all_fields_not_none(self.TASK_FIELDS) and 

144 self.field_contents_valid() 

145 ) 

146 

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

148 h = f""" 

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

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

151 {self.get_is_complete_tr(req)} 

152 <tr> 

153 <td>Total score (16–80)</td> 

154 <td>{answer(self.total_score())}</td> 

155 </td> 

156 </table> 

157 </div> 

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

159 Anchor points are 1 = {self.wxstring(req, "anchor1")}, 

160 5 = {self.wxstring(req, "anchor5")}. 

161 Questions {", ".join(str(x) for x in self.REVERSE_SCORE)} 

162 are reverse-scored. 

163 </div> 

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

165 <tr> 

166 <th width="70%">Question</th> 

167 <th width="15%">Answer (1–5)</th> 

168 <th width="15%">Score (1–5)</th> 

169 </tr> 

170 """ 

171 for q in range(1, self.NQUESTIONS + 1): 

172 a = getattr(self, "q" + str(q)) 

173 score = self.score(q) 

174 h += tr(self.wxstring(req, "q" + str(q)), answer(a), score) 

175 h += """ 

176 </table> 

177 """ 

178 return h 

179 

180 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]: 

181 codes = [SnomedExpression(req.snomed(SnomedLookup.PSWQ_PROCEDURE_ASSESSMENT))] # noqa 

182 if self.is_complete(): 

183 codes.append(SnomedExpression( 

184 req.snomed(SnomedLookup.PSWQ_SCALE), 

185 { 

186 req.snomed(SnomedLookup.PSWQ_SCORE): self.total_score(), 

187 } 

188 )) 

189 return codes