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/wsas.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, Tuple, Type 

30 

31from cardinal_pythonlib.stringfunc import strseq 

32from sqlalchemy.ext.declarative import DeclarativeMeta 

33from sqlalchemy.sql.schema import Column 

34from sqlalchemy.sql.sqltypes import Boolean, Integer 

35 

36from camcops_server.cc_modules.cc_constants import ( 

37 CssClass, 

38 DATA_COLLECTION_UNLESS_UPGRADED_DIV, 

39) 

40from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

41from camcops_server.cc_modules.cc_db import add_multiple_columns 

42from camcops_server.cc_modules.cc_html import answer, get_true_false, tr, tr_qa 

43from camcops_server.cc_modules.cc_request import CamcopsRequest 

44from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

45from camcops_server.cc_modules.cc_string import AS 

46from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

47from camcops_server.cc_modules.cc_task import ( 

48 get_from_dict, 

49 Task, 

50 TaskHasPatientMixin, 

51) 

52from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

53 

54 

55# ============================================================================= 

56# WSAS 

57# ============================================================================= 

58 

59class WsasMetaclass(DeclarativeMeta): 

60 # noinspection PyInitNewSignature 

61 def __init__(cls: Type['Wsas'], 

62 name: str, 

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

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

65 add_multiple_columns( 

66 cls, "q", 1, cls.NQUESTIONS, 

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

68 comment_fmt="Q{n}, {s} (0-4, higher worse)", 

69 comment_strings=[ 

70 "work", 

71 "home management", 

72 "social leisure", 

73 "private leisure", 

74 "relationships", 

75 ] 

76 ) 

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

78 

79 

80class Wsas(TaskHasPatientMixin, Task, 

81 metaclass=WsasMetaclass): 

82 """ 

83 Server implementation of the WSAS task. 

84 """ 

85 __tablename__ = "wsas" 

86 shortname = "WSAS" 

87 provides_trackers = True 

88 

89 retired_etc = Column( 

90 "retired_etc", Boolean, 

91 comment="Retired or choose not to have job for reason unrelated " 

92 "to problem" 

93 ) 

94 

95 MIN_PER_Q = 0 

96 MAX_PER_Q = 8 

97 NQUESTIONS = 5 

98 QUESTION_FIELDS = strseq("q", 1, NQUESTIONS) 

99 Q2_TO_END = strseq("q", 2, NQUESTIONS) 

100 TASK_FIELDS = QUESTION_FIELDS + ["retired_etc"] 

101 MAX_IF_WORKING = MAX_PER_Q * NQUESTIONS 

102 MAX_IF_RETIRED = MAX_PER_Q * (NQUESTIONS - 1) 

103 

104 @staticmethod 

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

106 _ = req.gettext 

107 return _("Work and Social Adjustment Scale") 

108 

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

110 return [TrackerInfo( 

111 value=self.total_score(), 

112 plot_label="WSAS total score (lower is better)", 

113 axis_label=f"Total score (out of " 

114 f"{self.MAX_IF_RETIRED}–{self.MAX_IF_WORKING})", 

115 axis_min=-0.5, 

116 axis_max=self.MAX_IF_WORKING + 0.5 

117 )] 

118 

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

120 return self.standard_task_summary_fields() + [ 

121 SummaryElement( 

122 name="total_score", 

123 coltype=Integer(), 

124 value=self.total_score(), 

125 comment=f"Total score (/ {self.max_score()})"), 

126 ] 

127 

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

129 if not self.is_complete(): 

130 return CTV_INCOMPLETE 

131 return [CtvInfo( 

132 content=f"WSAS total score {self.total_score()}/{self.max_score()}" 

133 )] 

134 

135 def total_score(self) -> int: 

136 return self.sum_fields(self.Q2_TO_END if self.retired_etc 

137 else self.QUESTION_FIELDS) 

138 

139 def max_score(self) -> int: 

140 return self.MAX_IF_RETIRED if self.retired_etc else self.MAX_IF_WORKING 

141 

142 def is_complete(self) -> bool: 

143 return ( 

144 self.all_fields_not_none(self.Q2_TO_END if self.retired_etc 

145 else self.QUESTION_FIELDS) and 

146 self.field_contents_valid() 

147 ) 

148 

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

150 option_dict = {None: None} 

151 for a in range(self.MIN_PER_Q, self.MAX_PER_Q + 1): 

152 option_dict[a] = req.wappstring(AS.WSAS_A_PREFIX + str(a)) 

153 q_a = "" 

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

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

156 fa = get_from_dict(option_dict, a) if a is not None else None 

157 q_a += tr(self.wxstring(req, "q" + str(q)), answer(fa)) 

158 return f""" 

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

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

161 {self.get_is_complete_tr(req)} 

162 <tr> 

163 <td>Total score</td> 

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

165 </td> 

166 </table> 

167 </div> 

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

169 <tr> 

170 <th width="75%">Question</th> 

171 <th width="25%">Answer</th> 

172 </tr> 

173 {tr_qa(self.wxstring(req, "q_retired_etc"), 

174 get_true_false(req, self.retired_etc))} 

175 </table> 

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

177 <tr> 

178 <th width="75%">Question</th> 

179 <th width="25%">Answer (0–8)</th> 

180 </tr> 

181 {q_a} 

182 </table> 

183 {DATA_COLLECTION_UNLESS_UPGRADED_DIV} 

184 """ 

185 

186 # noinspection PyUnresolvedReferences 

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

188 codes = [SnomedExpression(req.snomed(SnomedLookup.WSAS_PROCEDURE_ASSESSMENT))] # noqa 

189 if self.is_complete(): 

190 d = { 

191 req.snomed(SnomedLookup.WSAS_SCORE): self.total_score(), 

192 req.snomed(SnomedLookup.WSAS_HOME_MANAGEMENT_SCORE): self.q2, 

193 req.snomed(SnomedLookup.WSAS_SOCIAL_LEISURE_SCORE): self.q3, 

194 req.snomed(SnomedLookup.WSAS_PRIVATE_LEISURE_SCORE): self.q4, 

195 req.snomed(SnomedLookup.WSAS_RELATIONSHIPS_SCORE): self.q5, 

196 } 

197 if not self.retired_etc: 

198 d[req.snomed(SnomedLookup.WSAS_WORK_SCORE)] = self.q1 

199 codes.append(SnomedExpression( 

200 req.snomed(SnomedLookup.WSAS_SCALE), 

201 d 

202 )) 

203 return codes