Coverage for tasks/paradise24.py: 50%

58 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/paradise24.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**PARADISE 24 task.** 

29 

30""" 

31 

32from typing import Any, Dict, Optional, Type, Tuple 

33 

34from cardinal_pythonlib.stringfunc import strseq 

35from sqlalchemy.ext.declarative import DeclarativeMeta 

36from sqlalchemy.sql.sqltypes import Integer 

37 

38from camcops_server.cc_modules.cc_constants import CssClass 

39from camcops_server.cc_modules.cc_db import add_multiple_columns 

40from camcops_server.cc_modules.cc_html import tr_qa, tr, answer 

41from camcops_server.cc_modules.cc_request import CamcopsRequest 

42from camcops_server.cc_modules.cc_task import TaskHasPatientMixin, Task 

43 

44 

45class Paradise24Metaclass(DeclarativeMeta): 

46 def __init__( 

47 cls: Type["Paradise24"], 

48 name: str, 

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

50 classdict: Dict[str, Any], 

51 ) -> None: 

52 

53 add_multiple_columns( 

54 cls, 

55 cls.Q_PREFIX, 

56 cls.FIRST_Q, 

57 cls.LAST_Q, 

58 coltype=Integer, 

59 minimum=0, 

60 maximum=2, 

61 comment_fmt="Q{n} - {s}", 

62 comment_strings=[ 

63 "rested 0-2 (none - a lot)", 

64 "loss interest 0-2 (none - a lot)", 

65 "appetite 0-2 (none - a lot)", 

66 "sleeping 0-2 (none - a lot)", 

67 "irritable 0-2 (none - a lot)", 

68 "slowed down 0-2 (none - a lot)", 

69 "sad 0-2 (none - a lot)", 

70 "worry 0-2 (none - a lot)", 

71 "cope 0-2 (none - a lot)", 

72 "pain 0-2 (none - a lot)", 

73 "concentrating 0-2 (none - a lot)", 

74 "remembering 0-2 (none - a lot)", 

75 "decisions 0-2 (none - a lot)", 

76 "conversation 0-2 (none - a lot)", 

77 "walking 0-2 (none - a lot)", 

78 "grooming 0-2 (none - a lot)", 

79 "sexual 0-2 (none - a lot)", 

80 "staying by yourself 0-2 (none - a lot)", 

81 "health 0-2 (none - a lot)", 

82 "friendship 0-2 (none - a lot)", 

83 "getting along 0-2 (none - a lot)", 

84 "work or school 0-2 (none - a lot)", 

85 "money 0-2 (none - a lot)", 

86 "community 0-2 (none - a lot)", 

87 ], 

88 ) 

89 

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

91 

92 

93class Paradise24(TaskHasPatientMixin, Task, metaclass=Paradise24Metaclass): 

94 __tablename__ = "paradise24" 

95 shortname = "PARADISE 24" 

96 

97 Q_PREFIX = "q" 

98 FIRST_Q = 1 

99 LAST_Q = 24 

100 

101 ALL_FIELD_NAMES = strseq(Q_PREFIX, FIRST_Q, LAST_Q) 

102 

103 @staticmethod 

104 def longname(req: CamcopsRequest) -> str: 

105 _ = req.gettext 

106 return _( 

107 "Psychosocial fActors Relevant to BrAin DISorders in Europe–24" 

108 ) 

109 

110 def is_complete(self) -> bool: 

111 if self.any_fields_none(self.ALL_FIELD_NAMES): 

112 return False 

113 

114 return True 

115 

116 def total_score(self) -> Optional[int]: 

117 if not self.is_complete(): 

118 return None 

119 

120 return self.sum_fields(self.ALL_FIELD_NAMES) 

121 

122 def metric_score(self) -> Optional[int]: 

123 total_score = self.total_score() 

124 

125 if total_score is None: 

126 return None 

127 

128 # Table 3 of Cieza et al. (2015); see help. 

129 # - doi:10.1371/journal.pone.0132410.t003 

130 # - Indexes are raw scores. 

131 # - Values are transformed scores. 

132 score_lookup = [ 

133 0, # 0 

134 10, 

135 19, 

136 25, 

137 29, 

138 33, 

139 36, 

140 38, 

141 41, 

142 43, 

143 45, # 10 

144 46, 

145 48, 

146 50, 

147 51, 

148 53, 

149 54, 

150 55, 

151 57, 

152 58, 

153 59, # 20 

154 60, 

155 61, 

156 63, 

157 64, 

158 65, 

159 66, 

160 67, 

161 68, 

162 69, 

163 71, # 30 

164 72, 

165 73, 

166 74, 

167 76, 

168 77, 

169 78, 

170 80, 

171 81, 

172 83, 

173 85, # 40 

174 87, 

175 89, 

176 91, 

177 92, 

178 94, 

179 96, 

180 98, 

181 100, # 48 

182 ] 

183 

184 try: 

185 return score_lookup[total_score] 

186 except (IndexError, TypeError): 

187 return None 

188 

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

190 rows = "" 

191 for q_num in range(self.FIRST_Q, self.LAST_Q + 1): 

192 field = self.Q_PREFIX + str(q_num) 

193 question_cell = f"{q_num}. {self.xstring(req, field)}" 

194 

195 rows += tr_qa(question_cell, self.get_answer_cell(req, q_num)) 

196 

197 html = """ 

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

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

200 {tr_is_complete} 

201 {total_score} 

202 {metric_score} 

203 </table> 

204 </div> 

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

206 <tr> 

207 <th width="60%">Question</th> 

208 <th width="40%">Score</th> 

209 </tr> 

210 {rows} 

211 </table> 

212 <div class="{CssClass.FOOTNOTES}"> 

213 [1] Sum of all questions, range 0–48. 

214 [2] Transformed metric scale, range 0–100. 

215 </div> 

216 """.format( 

217 CssClass=CssClass, 

218 tr_is_complete=self.get_is_complete_tr(req), 

219 total_score=tr( 

220 self.wxstring(req, "raw_score") + " <sup>[1]</sup>", 

221 f"{answer(self.total_score())}", 

222 ), 

223 metric_score=tr( 

224 self.wxstring(req, "metric_score") + " <sup>[2]</sup>", 

225 f"{answer(self.metric_score())}", 

226 ), 

227 rows=rows, 

228 ) 

229 return html 

230 

231 def get_answer_cell(self, req: CamcopsRequest, q_num: int) -> str: 

232 q_field = self.Q_PREFIX + str(q_num) 

233 

234 score = getattr(self, q_field) 

235 meaning = self.get_score_meaning(req, score) 

236 answer_cell = f"{score} [{meaning}]" 

237 

238 return answer_cell 

239 

240 def get_score_meaning(self, req: CamcopsRequest, score: int) -> str: 

241 return self.wxstring(req, f"option_{score}")