Coverage for tasks/empsa.py: 73%

1"""

2 camcops_server/tasks/empsa.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 Eating and Meal Preparation Skills Assessment (EMPSA)

27

28"""

29

30 from typing import Optional, TYPE_CHECKING

31

32 import cardinal_pythonlib.rnc_web as ws

33 from cardinal_pythonlib.stringfunc import strseq

34 from sqlalchemy import UnicodeText

35 from sqlalchemy.orm import Mapped

36

37 from camcops_server.cc_modules.cc_constants import CssClass

38 from camcops_server.cc_modules.cc_html import answer, tr

39 from camcops_server.cc_modules.cc_sqla_coltypes import (

40 mapped_camcops_column,

41 ZERO_TO_10_CHECKER,

42)

43 from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin

44

45 if TYPE_CHECKING:

46 from camcops_server.cc_modules.cc_request import CamcopsRequest

47

48 DP = 2

49 RANGE_SUFFIX = " (0 none - 10 total)"

50

51

52 class Empsa(TaskHasPatientMixin, Task): # type: ignore[misc]

53 __tablename__ = "empsa"

54 shortname = "EMPSA"

55

56 q1_ability: Mapped[Optional[int]] = mapped_camcops_column(

57 permitted_value_checker=ZERO_TO_10_CHECKER,

58 comment="Q1 ability (planning)" + RANGE_SUFFIX,

59 )

60 q1_motivation: Mapped[Optional[int]] = mapped_camcops_column(

61 permitted_value_checker=ZERO_TO_10_CHECKER,

62 comment="Q1 motivation (planning)" + RANGE_SUFFIX,

63 )

64 q1_comments: Mapped[Optional[str]] = mapped_camcops_column(

65 UnicodeText, comment="Q1 comments (planning)"

66 )

67

68 q2_ability: Mapped[Optional[int]] = mapped_camcops_column(

69 permitted_value_checker=ZERO_TO_10_CHECKER,

70 comment="Q2 ability (budget)" + RANGE_SUFFIX,

71 )

72 q2_motivation: Mapped[Optional[int]] = mapped_camcops_column(

73 permitted_value_checker=ZERO_TO_10_CHECKER,

74 comment="Q2 motivation (budget)" + RANGE_SUFFIX,

75 )

76 q2_comments: Mapped[Optional[str]] = mapped_camcops_column(

77 UnicodeText, comment="Q2 comments (budget)"

78 )

79

80 q3_ability: Mapped[Optional[int]] = mapped_camcops_column(

81 permitted_value_checker=ZERO_TO_10_CHECKER,

82 comment="Q3 ability (shopping)" + RANGE_SUFFIX,

83 )

84 q3_motivation: Mapped[Optional[int]] = mapped_camcops_column(

85 permitted_value_checker=ZERO_TO_10_CHECKER,

86 comment="Q3 motivation (shopping)" + RANGE_SUFFIX,

87 )

88 q3_comments: Mapped[Optional[str]] = mapped_camcops_column(

89 UnicodeText, comment="Q3 comments (shopping)"

90 )

91

92 q4_ability: Mapped[Optional[int]] = mapped_camcops_column(

93 permitted_value_checker=ZERO_TO_10_CHECKER,

94 comment="Q4 ability (cooking)" + RANGE_SUFFIX,

95 )

96 q4_motivation: Mapped[Optional[int]] = mapped_camcops_column(

97 permitted_value_checker=ZERO_TO_10_CHECKER,

98 comment="Q4 motivation (cooking)" + RANGE_SUFFIX,

99 )

100 q4_comments: Mapped[Optional[str]] = mapped_camcops_column(

101 UnicodeText, comment="Q4 comments (cooking)"

102 )

103

104 q5_ability: Mapped[Optional[int]] = mapped_camcops_column(

105 permitted_value_checker=ZERO_TO_10_CHECKER,

106 comment="Q5 ability (preparing)" + RANGE_SUFFIX,

107 )

108 q5_motivation: Mapped[Optional[int]] = mapped_camcops_column(

109 permitted_value_checker=ZERO_TO_10_CHECKER,

110 comment="Q5 motivation (preparing)" + RANGE_SUFFIX,

111 )

112 q5_comments: Mapped[Optional[str]] = mapped_camcops_column(

113 UnicodeText, comment="Q5 comments (preparing)"

114 )

115

116 q6_ability: Mapped[Optional[int]] = mapped_camcops_column(

117 permitted_value_checker=ZERO_TO_10_CHECKER,

118 comment="Q6 ability (portions)" + RANGE_SUFFIX,

119 )

120 q6_motivation: Mapped[Optional[int]] = mapped_camcops_column(

121 permitted_value_checker=ZERO_TO_10_CHECKER,

122 comment="Q6 motivation (portions)" + RANGE_SUFFIX,

123 )

124 q6_comments: Mapped[Optional[str]] = mapped_camcops_column(

125 UnicodeText, comment="Q6 comments (portions)"

126 )

127

128 q7_ability: Mapped[Optional[int]] = mapped_camcops_column(

129 permitted_value_checker=ZERO_TO_10_CHECKER,

130 comment="Q7 ability (throwing away)" + RANGE_SUFFIX,

131 )

132 q7_motivation: Mapped[Optional[int]] = mapped_camcops_column(

133 permitted_value_checker=ZERO_TO_10_CHECKER,

134 comment="Q7 motivation (throwing away)" + RANGE_SUFFIX,

135 )

136 q7_comments: Mapped[Optional[str]] = mapped_camcops_column(

137 UnicodeText, comment="Q7 comments (throwing away)"

138 )

139

140 q8_ability: Mapped[Optional[int]] = mapped_camcops_column(

141 permitted_value_checker=ZERO_TO_10_CHECKER,

142 comment="Q8 ability (difficult food)" + RANGE_SUFFIX,

143 )

144 q8_motivation: Mapped[Optional[int]] = mapped_camcops_column(

145 permitted_value_checker=ZERO_TO_10_CHECKER,

146 comment="Q8 motivation (difficult food)" + RANGE_SUFFIX,

147 )

148 q8_comments: Mapped[Optional[str]] = mapped_camcops_column(

149 UnicodeText, comment="Q8 comments (difficult food)"

150 )

151

152 q9_ability: Mapped[Optional[int]] = mapped_camcops_column(

153 permitted_value_checker=ZERO_TO_10_CHECKER,

154 comment="Q9 ability (normal pace)" + RANGE_SUFFIX,

155 )

156 q9_motivation: Mapped[Optional[int]] = mapped_camcops_column(

157 permitted_value_checker=ZERO_TO_10_CHECKER,

158 comment="Q9 motivation (normal pace)" + RANGE_SUFFIX,

159 )

160 q9_comments: Mapped[Optional[str]] = mapped_camcops_column(

161 UnicodeText, comment="Q9 comments (normal pace)"

162 )

163

164 q10_ability: Mapped[Optional[int]] = mapped_camcops_column(

165 permitted_value_checker=ZERO_TO_10_CHECKER,

166 comment="Q10 ability (others)" + RANGE_SUFFIX,

167 )

168 q10_motivation: Mapped[Optional[int]] = mapped_camcops_column(

169 permitted_value_checker=ZERO_TO_10_CHECKER,

170 comment="Q10 motivation (others)" + RANGE_SUFFIX,

171 )

172 q10_comments: Mapped[Optional[str]] = mapped_camcops_column(

173 UnicodeText, comment="Q10 comments (others)"

174 )

175

176 q11_ability: Mapped[Optional[int]] = mapped_camcops_column(

177 permitted_value_checker=ZERO_TO_10_CHECKER,

178 comment="Q11 ability (public)" + RANGE_SUFFIX,

179 )

180 q11_motivation: Mapped[Optional[int]] = mapped_camcops_column(

181 permitted_value_checker=ZERO_TO_10_CHECKER,

182 comment="Q11 motivation (public)" + RANGE_SUFFIX,

183 )

184 q11_comments: Mapped[Optional[str]] = mapped_camcops_column(

185 UnicodeText, comment="Q11 comments (public)"

186 )

187

188 q12_ability: Mapped[Optional[int]] = mapped_camcops_column(

189 permitted_value_checker=ZERO_TO_10_CHECKER,

190 comment="Q12 ability (distress)" + RANGE_SUFFIX,

191 )

192 q12_motivation: Mapped[Optional[int]] = mapped_camcops_column(

193 permitted_value_checker=ZERO_TO_10_CHECKER,

194 comment="Q12 motivation (distress)" + RANGE_SUFFIX,

195 )

196 q12_comments: Mapped[Optional[str]] = mapped_camcops_column(

197 UnicodeText, comment="Q12 comments (distress)"

198 )

199

200 PREFIX = "q"

201 ABILITY_SUFFIX = "_ability"

202 MOTIVATION_SUFFIX = "_motivation"

203 COMMENTS_SUFFIX = "_comments"

204 ALL_ABILITY_FIELD_NAMES = strseq(PREFIX, 1, 12, ABILITY_SUFFIX)

205 ALL_MOTIVATION_FIELD_NAMES = strseq(PREFIX, 1, 12, MOTIVATION_SUFFIX)

206 ALL_MANDATORY_FIELD_NAMES = (

207 ALL_ABILITY_FIELD_NAMES + ALL_MOTIVATION_FIELD_NAMES

208 )

209 FIRST_Q = 1

210 LAST_Q = 12

211 MAX_SCORE = 10 # per question, or for subscales that are means

212

213 @staticmethod

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

215 _ = req.gettext

216 return _("Eating and Meal Preparation Skills Assessment")

217

218 def is_complete(self) -> bool:

219 if self.any_fields_none(self.ALL_MANDATORY_FIELD_NAMES):

220 return False

221

222 return True

223

224 def ability_subscale(self) -> Optional[float]:

225 return self.mean_fields(self.ALL_ABILITY_FIELD_NAMES, ignorevalues=[])

226

227 def motivation_subscale(self) -> Optional[float]:

228 return self.mean_fields(

229 self.ALL_MOTIVATION_FIELD_NAMES, ignorevalues=[]

230 )

231

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

233 rows = self.get_task_html_rows(req)

234

235 html = """

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

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

238 {tr_is_complete}

239 {ability_subscale}

240 {motivation_subscale}

241 </table>

242 </div>

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

244 {rows}

245 </table>

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

247 [1] {ability_footnote}

248 [2] {motivation_footnote}

249 </div>

250 """.format(

251 CssClass=CssClass,

252 tr_is_complete=self.get_is_complete_tr(req),

253 ability_subscale=tr(

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

255 answer(

256 ws.number_to_dp(self.ability_subscale(), DP, default=None)

257 )

258 + f" / {self.MAX_SCORE}",

259 ),

260 motivation_subscale=tr(

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

262 answer(

263 ws.number_to_dp(

264 self.motivation_subscale(), DP, default=None

265 ),

266 )

267 + f" / {self.MAX_SCORE}",

268 ),

269 rows=rows,

270 ability_footnote=self.wxstring(req, "ability_footnote"),

271 motivation_footnote=self.wxstring(req, "motivation_footnote"),

272 )

273 return html

274

275 def get_task_html_rows(self, req: "CamcopsRequest") -> str:

276 task_text = self.xstring(req, "task")

277 ability_text = self.xstring(req, "ability")

278 motivation_text = self.xstring(req, "motivation")

279 comments_text = self.xstring(req, "comments")

280 header = f"""

281 <tr>

282 <th width="2%"></th>

283 <th width="41%">{task_text}</th>

284 <th width="8%">{ability_text}</th>

285 <th width="8%">{motivation_text}</th>

286 <th width="41%">{comments_text}</th>

287 </tr>

288 """

289 return header + self.get_task_html_rows_for_range(

290 req, self.FIRST_Q, self.LAST_Q

291 )

292

293 def get_task_html_rows_for_range(

294 self, req: "CamcopsRequest", first_q: int, last_q: int

295 ) -> str:

296 rows = ""

297 for q_num in range(first_q, last_q + 1):

298 q_str = f"{self.PREFIX}{q_num}"

299 ability_field = f"{q_str}{self.ABILITY_SUFFIX}"

300 motivation_field = f"{q_str}{self.MOTIVATION_SUFFIX}"

301 comments_field = f"{q_str}{self.COMMENTS_SUFFIX}"

302 question_cell = self.xstring(req, q_str)

303

304 rows += tr(

305 str(q_num),

306 question_cell,

307 answer(getattr(self, ability_field)),

308 answer(getattr(self, motivation_field)),

309 answer(getattr(self, comments_field), default=""),

310 )

311

312 return rows