Coverage for tasks/paradise24.py: 47%

1"""

2 camcops_server/tasks/paradise24.py

3

4===============================================================================

5

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

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

8

9 This file is part of CamCOPS.

10

11 CamCOPS is free software: you can redistribute it and/or modify

12 it under the terms of the GNU General Public License as published by

13 the Free Software Foundation, either version 3 of the License, or

14 (at your option) any later version.

15

16 CamCOPS is distributed in the hope that it will be useful,

17 but WITHOUT ANY WARRANTY; without even the implied warranty of

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

19 GNU General Public License for more details.

20

21 You should have received a copy of the GNU General Public License

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.

23

24===============================================================================

25

26**PARADISE 24 task.**

27

28"""

29

30 from typing import Any, cast, Optional, Type

31

32 from cardinal_pythonlib.stringfunc import strseq

33 from sqlalchemy.sql.sqltypes import Integer

34

35 from camcops_server.cc_modules.cc_constants import CssClass

36 from camcops_server.cc_modules.cc_db import add_multiple_columns

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

38 from camcops_server.cc_modules.cc_request import CamcopsRequest

39 from camcops_server.cc_modules.cc_task import TaskHasPatientMixin, Task

40

41

42 class Paradise24( # type: ignore[misc]

43 TaskHasPatientMixin,

44 Task,

45):

46 __tablename__ = "paradise24"

47 shortname = "PARADISE 24"

48

49 Q_PREFIX = "q"

50 FIRST_Q = 1

51 LAST_Q = 24

52

53 @classmethod

54 def extend_columns(cls: Type["Paradise24"], **kwargs: Any) -> None:

55

56 add_multiple_columns(

57 cls,

58 cls.Q_PREFIX,

59 cls.FIRST_Q,

60 cls.LAST_Q,

61 coltype=Integer,

62 minimum=0,

63 maximum=2,

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

65 comment_strings=[

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

90 ],

91 )

92

93 ALL_FIELD_NAMES = strseq(Q_PREFIX, FIRST_Q, LAST_Q)

94

95 @staticmethod

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

97 _ = req.gettext

98 return _(

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

100 )

101

102 def is_complete(self) -> bool:

103 if self.any_fields_none(self.ALL_FIELD_NAMES):

104 return False

105

106 return True

107

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

109 if not self.is_complete():

110 return None

111

112 return cast(int, self.sum_fields(self.ALL_FIELD_NAMES))

113

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

115 total_score = self.total_score()

116

117 if total_score is None:

118 return None

119

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

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

122 # - Indexes are raw scores.

123 # - Values are transformed scores.

124 score_lookup = [

125 0, # 0

126 10,

127 19,

128 25,

129 29,

130 33,

131 36,

132 38,

133 41,

134 43,

135 45, # 10

136 46,

137 48,

138 50,

139 51,

140 53,

141 54,

142 55,

143 57,

144 58,

145 59, # 20

146 60,

147 61,

148 63,

149 64,

150 65,

151 66,

152 67,

153 68,

154 69,

155 71, # 30

156 72,

157 73,

158 74,

159 76,

160 77,

161 78,

162 80,

163 81,

164 83,

165 85, # 40

166 87,

167 89,

168 91,

169 92,

170 94,

171 96,

172 98,

173 100, # 48

174 ]

175

176 try:

177 return score_lookup[total_score]

178 except (IndexError, TypeError):

179 return None

180

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

182 rows = ""

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

184 field = self.Q_PREFIX + str(q_num)

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

186

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

188

189 html = """

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

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

192 {tr_is_complete}

193 {total_score}

194 {metric_score}

195 </table>

196 </div>

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

198 <tr>

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

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

201 </tr>

202 {rows}

203 </table>

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

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

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

207 </div>

208 """.format(

209 CssClass=CssClass,

210 tr_is_complete=self.get_is_complete_tr(req),

211 total_score=tr(

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

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

214 ),

215 metric_score=tr(

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

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

218 ),

219 rows=rows,

220 )

221 return html

222

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

224 q_field = self.Q_PREFIX + str(q_num)

225

226 score = getattr(self, q_field)

227 meaning = self.get_score_meaning(req, score)

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

229

230 return answer_cell

231

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

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