Coverage for tasks/wsas.py: 62%

69 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/wsas.py 

5 

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

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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/>. 

25 

26=============================================================================== 

27 

28""" 

29 

30from typing import Any, Dict, List, Tuple, Type 

31 

32from cardinal_pythonlib.stringfunc import strseq 

33from sqlalchemy.ext.declarative import DeclarativeMeta 

34from sqlalchemy.sql.schema import Column 

35from sqlalchemy.sql.sqltypes import Boolean, Integer 

36 

37from camcops_server.cc_modules.cc_constants import ( 

38 CssClass, 

39 DATA_COLLECTION_UNLESS_UPGRADED_DIV, 

40) 

41from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

42from camcops_server.cc_modules.cc_db import add_multiple_columns 

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

44from camcops_server.cc_modules.cc_request import CamcopsRequest 

45from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

46from camcops_server.cc_modules.cc_string import AS 

47from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

48from camcops_server.cc_modules.cc_task import ( 

49 get_from_dict, 

50 Task, 

51 TaskHasPatientMixin, 

52) 

53from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

54 

55 

56# ============================================================================= 

57# WSAS 

58# ============================================================================= 

59 

60 

61class WsasMetaclass(DeclarativeMeta): 

62 # noinspection PyInitNewSignature 

63 def __init__( 

64 cls: Type["Wsas"], 

65 name: str, 

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

67 classdict: Dict[str, Any], 

68 ) -> None: 

69 add_multiple_columns( 

70 cls, 

71 "q", 

72 1, 

73 cls.NQUESTIONS, 

74 minimum=cls.MIN_PER_Q, 

75 maximum=cls.MAX_PER_Q, 

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

77 comment_strings=[ 

78 "work", 

79 "home management", 

80 "social leisure", 

81 "private leisure", 

82 "relationships", 

83 ], 

84 ) 

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

86 

87 

88class Wsas(TaskHasPatientMixin, Task, metaclass=WsasMetaclass): 

89 """ 

90 Server implementation of the WSAS task. 

91 """ 

92 

93 __tablename__ = "wsas" 

94 shortname = "WSAS" 

95 provides_trackers = True 

96 

97 retired_etc = Column( 

98 "retired_etc", 

99 Boolean, 

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

101 "to problem", 

102 ) 

103 

104 MIN_PER_Q = 0 

105 MAX_PER_Q = 8 

106 NQUESTIONS = 5 

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

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

109 TASK_FIELDS = QUESTION_FIELDS + ["retired_etc"] 

110 MAX_IF_WORKING = MAX_PER_Q * NQUESTIONS 

111 MAX_IF_RETIRED = MAX_PER_Q * (NQUESTIONS - 1) 

112 

113 @staticmethod 

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

115 _ = req.gettext 

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

117 

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

119 return [ 

120 TrackerInfo( 

121 value=self.total_score(), 

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

123 axis_label=f"Total score (out of " 

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

125 axis_min=-0.5, 

126 axis_max=self.MAX_IF_WORKING + 0.5, 

127 ) 

128 ] 

129 

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

131 return self.standard_task_summary_fields() + [ 

132 SummaryElement( 

133 name="total_score", 

134 coltype=Integer(), 

135 value=self.total_score(), 

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

137 ) 

138 ] 

139 

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

141 if not self.is_complete(): 

142 return CTV_INCOMPLETE 

143 return [ 

144 CtvInfo( 

145 content=f"WSAS total score " 

146 f"{self.total_score()}/{self.max_score()}" 

147 ) 

148 ] 

149 

150 def total_score(self) -> int: 

151 return self.sum_fields( 

152 self.Q2_TO_END if self.retired_etc else self.QUESTION_FIELDS 

153 ) 

154 

155 def max_score(self) -> int: 

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

157 

158 def is_complete(self) -> bool: 

159 return ( 

160 self.all_fields_not_none( 

161 self.Q2_TO_END if self.retired_etc else self.QUESTION_FIELDS 

162 ) 

163 and self.field_contents_valid() 

164 ) 

165 

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

167 option_dict = {None: None} 

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

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

170 q_a = "" 

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

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

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

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

175 return f""" 

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

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

178 {self.get_is_complete_tr(req)} 

179 <tr> 

180 <td>Total score</td> 

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

182 </td> 

183 </table> 

184 </div> 

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

186 <tr> 

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

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

189 </tr> 

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

191 get_true_false(req, self.retired_etc))} 

192 </table> 

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

194 <tr> 

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

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

197 </tr> 

198 {q_a} 

199 </table> 

200 {DATA_COLLECTION_UNLESS_UPGRADED_DIV} 

201 """ 

202 

203 # noinspection PyUnresolvedReferences 

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

205 codes = [ 

206 SnomedExpression( 

207 req.snomed(SnomedLookup.WSAS_PROCEDURE_ASSESSMENT) 

208 ) 

209 ] 

210 if self.is_complete(): 

211 d = { 

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

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

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

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

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

217 } 

218 if not self.retired_etc: 

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

220 codes.append( 

221 SnomedExpression(req.snomed(SnomedLookup.WSAS_SCALE), d) 

222 ) 

223 return codes