Coverage for tasks/wemwbs.py: 59%

1"""

2 camcops_server/tasks/wemwbs.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"""

27

28 from typing import Any, cast, List, Type

29

30 from cardinal_pythonlib.stringfunc import strseq

31 from sqlalchemy.sql.sqltypes import Integer

32

33 from camcops_server.cc_modules.cc_constants import CssClass

34 from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo

35 from camcops_server.cc_modules.cc_db import add_multiple_columns

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

37 from camcops_server.cc_modules.cc_request import CamcopsRequest

38 from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup

39 from camcops_server.cc_modules.cc_summaryelement import SummaryElement

40 from camcops_server.cc_modules.cc_task import (

41 get_from_dict,

42 Task,

43 TaskHasPatientMixin,

44)

45 from camcops_server.cc_modules.cc_text import SS

46 from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo

47

48

49# =============================================================================

50# WEMWBS

51# =============================================================================

52

53

54 class Wemwbs( # type: ignore[misc]

55 TaskHasPatientMixin,

56 Task,

57):

58 """

59 Server implementation of the WEMWBS task.

60 """

61

62 __tablename__ = "wemwbs"

63 shortname = "WEMWBS"

64 provides_trackers = True

65

66 MINQSCORE = 1

67 MAXQSCORE = 5

68 N_QUESTIONS = 14

69 MINTOTALSCORE = N_QUESTIONS * MINQSCORE

70 MAXTOTALSCORE = N_QUESTIONS * MAXQSCORE

71

72 @classmethod

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

74 add_multiple_columns(

75 cls,

76 "q",

77 1,

78 cls.N_QUESTIONS,

79 minimum=1,

80 maximum=5,

81 comment_fmt="Q{n} ({s}) (1 none of the time - 5 all of the time)",

82 comment_strings=[

83 "optimistic",

84 "useful",

85 "relaxed",

86 "interested in other people",

87 "energy",

88 "dealing with problems well",

89 "thinking clearly",

90 "feeling good about myself",

91 "feeling close to others",

92 "confident",

93 "able to make up my own mind",

94 "feeling loved",

95 "interested in new things",

96 "cheerful",

97 ],

98 )

99

100 TASK_FIELDS = strseq("q", 1, N_QUESTIONS)

101

102 @staticmethod

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

104 _ = req.gettext

105 return _("Warwick–Edinburgh Mental Well-Being Scale")

106

107 def is_complete(self) -> bool:

108 return (

109 self.all_fields_not_none(self.TASK_FIELDS)

110 and self.field_contents_valid()

111 )

112

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

114 return [

115 TrackerInfo(

116 value=self.total_score(),

117 plot_label="WEMWBS total score (rating mental well-being)",

118 axis_label=f"Total score ({self.MINTOTALSCORE}-{self.MAXTOTALSCORE})", # noqa

119 axis_min=self.MINTOTALSCORE - 0.5,

120 axis_max=self.MAXTOTALSCORE + 0.5,

121 )

122 ]

123

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

125 if not self.is_complete():

126 return CTV_INCOMPLETE

127 return [

128 CtvInfo(

129 content=f"WEMWBS total score {self.total_score()} "

130 f"(range {self.MINTOTALSCORE}–{self.MAXTOTALSCORE})"

131 )

132 ]

133

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

135 return self.standard_task_summary_fields() + [

136 SummaryElement(

137 name="total",

138 coltype=Integer(),

139 value=self.total_score(),

140 comment=f"Total score (range "

141 f"{self.MINTOTALSCORE}-{self.MAXTOTALSCORE})",

142 )

143 ]

144

145 def total_score(self) -> int:

146 return cast(int, self.sum_fields(self.TASK_FIELDS))

147

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

149 main_dict = {

150 None: None,

151 1: "1 — " + self.wxstring(req, "wemwbs_a1"),

152 2: "2 — " + self.wxstring(req, "wemwbs_a2"),

153 3: "3 — " + self.wxstring(req, "wemwbs_a3"),

154 4: "4 — " + self.wxstring(req, "wemwbs_a4"),

155 5: "5 — " + self.wxstring(req, "wemwbs_a5"),

156 }

157 q_a = ""

158 for i in range(1, self.N_QUESTIONS + 1):

159 nstr = str(i)

160 q_a += tr_qa(

161 self.wxstring(req, "wemwbs_q" + nstr),

162 get_from_dict(main_dict, getattr(self, "q" + nstr)),

163 )

164 h = """

165 <div class="{css_summary}">

166 <table class="{css_summary}">

167 {tr_is_complete}

168 {tr_total_score}

169 </table>

170 </div>

171 <div class="{css_explanation}">

172 Ratings are over the last 2 weeks.

173 </div>

174 <table class="{css_taskdetail}">

175 <tr>

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

177 <th width="40%">Answer</th>

178 </tr>

179 {q_a}

180 </table>

181 <div class="{css_copyright}">

182 WEMWBS: from Tennant et al. (2007), <i>Health and Quality of

183 Life Outcomes</i> 5:63,

184 <a href="http://www.hqlo.com/content/5/1/63">

185 http://www.hqlo.com/content/5/1/63</a>;

186 © 2007 Tennant et al.; distributed under the terms of the

187 Creative Commons Attribution License.

188 </div>

189 """.format(

190 css_summary=CssClass.SUMMARY,

191 tr_is_complete=self.get_is_complete_tr(req),

192 tr_total_score=tr(

193 req.sstring(SS.TOTAL_SCORE),

194 answer(self.total_score())

195 + f" (range {self.MINTOTALSCORE}–{self.MAXTOTALSCORE})",

196 ),

197 css_explanation=CssClass.EXPLANATION,

198 css_taskdetail=CssClass.TASKDETAIL,

199 q_a=q_a,

200 css_copyright=CssClass.COPYRIGHT,

201 )

202 return h

203

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

205 codes = [

206 SnomedExpression(

207 req.snomed(SnomedLookup.WEMWBS_PROCEDURE_ASSESSMENT)

208 )

209 ]

210 if self.is_complete():

211 codes.append(

212 SnomedExpression(

213 req.snomed(SnomedLookup.WEMWBS_SCALE),

214 {

215 req.snomed(

216 SnomedLookup.WEMWBS_SCORE

217 ): self.total_score()

218 },

219 )

220 )

221 return codes

222

223

224# =============================================================================

225# SWEMWBS

226# =============================================================================

227

228

229 class Swemwbs( # type: ignore[misc]

230 TaskHasPatientMixin,

231 Task,

232):

233 """

234 Server implementation of the SWEMWBS task.

235 """

236

237 __tablename__ = "swemwbs"

238 shortname = "SWEMWBS"

239 extrastring_taskname = "wemwbs" # shares

240 info_filename_stem = extrastring_taskname

241

242 MINQSCORE = 1

243 MAXQSCORE = 5

244 N_QUESTIONS = 7

245 MINTOTALSCORE = N_QUESTIONS * MINQSCORE

246 MAXTOTALSCORE = N_QUESTIONS * MAXQSCORE

247

248 @classmethod

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

250 add_multiple_columns(

251 cls,

252 "q",

253 1,

254 cls.N_QUESTIONS,

255 minimum=1,

256 maximum=5,

257 comment_fmt="Q{n} ({s}) (1 none of the time - 5 all of the time)",

258 comment_strings=[

259 "optimistic",

260 "useful",

261 "relaxed",

262 "interested in other people",

263 "energy",

264 "dealing with problems well",

265 "thinking clearly",

266 "feeling good about myself",

267 "feeling close to others",

268 "confident",

269 "able to make up my own mind",

270 "feeling loved",

271 "interested in new things",

272 "cheerful",

273 ],

274 )

275

276 TASK_FIELDS = strseq("q", 1, N_QUESTIONS)

277

278 @staticmethod

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

280 _ = req.gettext

281 return _("Short Warwick–Edinburgh Mental Well-Being Scale")

282

283 def is_complete(self) -> bool:

284 return (

285 self.all_fields_not_none(self.TASK_FIELDS)

286 and self.field_contents_valid()

287 )

288

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

290 return [

291 TrackerInfo(

292 value=self.total_score(),

293 plot_label="SWEMWBS total score (rating mental well-being)",

294 axis_label=f"Total score ({self.MINTOTALSCORE}-{self.MAXTOTALSCORE})", # noqa

295 axis_min=self.MINTOTALSCORE - 0.5,

296 axis_max=self.MAXTOTALSCORE + 0.5,

297 )

298 ]

299

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

301 if not self.is_complete():

302 return CTV_INCOMPLETE

303 return [

304 CtvInfo(

305 content=f"SWEMWBS total score {self.total_score()} "

306 f"(range {self.MINTOTALSCORE}–{self.MAXTOTALSCORE})"

307 )

308 ]

309

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

311 return self.standard_task_summary_fields() + [

312 SummaryElement(

313 name="total",

314 coltype=Integer(),

315 value=self.total_score(),

316 comment=f"Total score (range "

317 f"{self.MINTOTALSCORE}-{self.MAXTOTALSCORE})",

318 )

319 ]

320

321 def total_score(self) -> int:

322 return cast(int, self.sum_fields(self.TASK_FIELDS))

323

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

325 main_dict = {

326 None: None,

327 1: "1 — " + self.wxstring(req, "wemwbs_a1"),

328 2: "2 — " + self.wxstring(req, "wemwbs_a2"),

329 3: "3 — " + self.wxstring(req, "wemwbs_a3"),

330 4: "4 — " + self.wxstring(req, "wemwbs_a4"),

331 5: "5 — " + self.wxstring(req, "wemwbs_a5"),

332 }

333 q_a = ""

334 for i in range(1, self.N_QUESTIONS + 1):

335 nstr = str(i)

336 q_a += tr_qa(

337 self.wxstring(req, "swemwbs_q" + nstr),

338 get_from_dict(main_dict, getattr(self, "q" + nstr)),

339 )

340

341 h = """

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

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

344 {tr_is_complete}

345 {total_score}

346 </table>

347 </div>

348 <div class="{CssClass.EXPLANATION}">

349 Ratings are over the last 2 weeks.

350 </div>

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

352 <tr>

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

354 <th width="40%">Answer</th>

355 </tr>

356 {q_a}

357 </table>

358 <div class="{CssClass.COPYRIGHT}">

359 SWEMWBS: from Stewart-Brown et al. (2009), <i>Health and

360 Quality of Life Outcomes</i> 7:15,

361 http://www.hqlo.com/content/7/1/15;

362 © 2009 Stewart-Brown et al.; distributed under the terms of the

363 Creative Commons Attribution License.

364 </div>

365 """.format(

366 CssClass=CssClass,

367 tr_is_complete=self.get_is_complete_tr(req),

368 total_score=tr(

369 req.sstring(SS.TOTAL_SCORE),

370 answer(self.total_score())

371 + f" (range {self.MINTOTALSCORE}–{self.MAXTOTALSCORE})",

372 ),

373 q_a=q_a,

374 )

375 return h

376

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

378 codes = [

379 SnomedExpression(

380 req.snomed(SnomedLookup.SWEMWBS_PROCEDURE_ASSESSMENT)

381 )

382 ]

383 if self.is_complete():

384 codes.append(

385 SnomedExpression(

386 req.snomed(SnomedLookup.SWEMWBS_SCALE),

387 {

388 req.snomed(

389 SnomedLookup.SWEMWBS_SCORE

390 ): self.total_score()

391 },

392 )

393 )

394 return codes