Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/wemwbs.py 

5 

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

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

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

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

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

15 (at your option) any later version. 

16 

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

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

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

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

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

24 

25=============================================================================== 

26 

27""" 

28 

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

30 

31from cardinal_pythonlib.stringfunc import strseq 

32from sqlalchemy.ext.declarative import DeclarativeMeta 

33from sqlalchemy.sql.sqltypes import Integer 

34 

35from camcops_server.cc_modules.cc_constants import CssClass 

36from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

37from camcops_server.cc_modules.cc_db import add_multiple_columns 

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

39from camcops_server.cc_modules.cc_request import CamcopsRequest 

40from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

41from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

42from camcops_server.cc_modules.cc_task import ( 

43 get_from_dict, 

44 Task, 

45 TaskHasPatientMixin, 

46) 

47from camcops_server.cc_modules.cc_text import SS 

48from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

49 

50 

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

52# WEMWBS 

53# ============================================================================= 

54 

55class WemwbsMetaclass(DeclarativeMeta): 

56 # noinspection PyInitNewSignature 

57 def __init__(cls: Type['Wemwbs'], 

58 name: str, 

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

60 classdict: Dict[str, Any]) -> None: 

61 add_multiple_columns( 

62 cls, "q", 1, cls.N_QUESTIONS, 

63 minimum=1, maximum=5, 

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

65 comment_strings=[ 

66 "optimistic", 

67 "useful", 

68 "relaxed", 

69 "interested in other people", 

70 "energy", 

71 "dealing with problems well", 

72 "thinking clearly", 

73 "feeling good about myself", 

74 "feeling close to others", 

75 "confident", 

76 "able to make up my own mind", 

77 "feeling loved", 

78 "interested in new things", 

79 "cheerful", 

80 ] 

81 ) 

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

83 

84 

85class Wemwbs(TaskHasPatientMixin, Task, 

86 metaclass=WemwbsMetaclass): 

87 """ 

88 Server implementation of the WEMWBS task. 

89 """ 

90 __tablename__ = "wemwbs" 

91 shortname = "WEMWBS" 

92 provides_trackers = True 

93 

94 MINQSCORE = 1 

95 MAXQSCORE = 5 

96 N_QUESTIONS = 14 

97 MINTOTALSCORE = N_QUESTIONS * MINQSCORE 

98 MAXTOTALSCORE = N_QUESTIONS * MAXQSCORE 

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

100 

101 @staticmethod 

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

103 _ = req.gettext 

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

105 

106 def is_complete(self) -> bool: 

107 return ( 

108 self.all_fields_not_none(self.TASK_FIELDS) and 

109 self.field_contents_valid() 

110 ) 

111 

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

113 return [TrackerInfo( 

114 value=self.total_score(), 

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

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

117 axis_min=self.MINTOTALSCORE - 0.5, 

118 axis_max=self.MAXTOTALSCORE + 0.5 

119 )] 

120 

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

122 if not self.is_complete(): 

123 return CTV_INCOMPLETE 

124 return [CtvInfo( 

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

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

127 )] 

128 

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

130 return self.standard_task_summary_fields() + [ 

131 SummaryElement( 

132 name="total", 

133 coltype=Integer(), 

134 value=self.total_score(), 

135 comment=f"Total score (range " 

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

137 ] 

138 

139 def total_score(self) -> int: 

140 return self.sum_fields(self.TASK_FIELDS) 

141 

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

143 main_dict = { 

144 None: None, 

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

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

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

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

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

150 } 

151 q_a = "" 

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

153 nstr = str(i) 

154 q_a += tr_qa(self.wxstring(req, "wemwbs_q" + nstr), 

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

156 h = """ 

157 <div class="{css_summary}"> 

158 <table class="{css_summary}"> 

159 {tr_is_complete} 

160 {tr_total_score} 

161 </table> 

162 </div> 

163 <div class="{css_explanation}"> 

164 Ratings are over the last 2 weeks. 

165 </div> 

166 <table class="{css_taskdetail}"> 

167 <tr> 

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

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

170 </tr> 

171 {q_a} 

172 </table> 

173 <div class="{css_copyright}"> 

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

175 Life Outcomes</i> 5:63, 

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

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

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

179 Creative Commons Attribution License. 

180 </div> 

181 """.format( 

182 css_summary=CssClass.SUMMARY, 

183 tr_is_complete=self.get_is_complete_tr(req), 

184 tr_total_score=tr( 

185 req.sstring(SS.TOTAL_SCORE), 

186 answer(self.total_score()) + 

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

188 ), 

189 css_explanation=CssClass.EXPLANATION, 

190 css_taskdetail=CssClass.TASKDETAIL, 

191 q_a=q_a, 

192 css_copyright=CssClass.COPYRIGHT, 

193 ) 

194 return h 

195 

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

197 codes = [SnomedExpression(req.snomed(SnomedLookup.WEMWBS_PROCEDURE_ASSESSMENT))] # noqa 

198 if self.is_complete(): 

199 codes.append(SnomedExpression( 

200 req.snomed(SnomedLookup.WEMWBS_SCALE), 

201 { 

202 req.snomed(SnomedLookup.WEMWBS_SCORE): self.total_score(), 

203 } 

204 )) 

205 return codes 

206 

207 

208# ============================================================================= 

209# SWEMWBS 

210# ============================================================================= 

211 

212class SwemwbsMetaclass(DeclarativeMeta): 

213 # noinspection PyInitNewSignature 

214 def __init__(cls: Type['Swemwbs'], 

215 name: str, 

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

217 classdict: Dict[str, Any]) -> None: 

218 add_multiple_columns( 

219 cls, "q", 1, cls.N_QUESTIONS, 

220 minimum=1, maximum=5, 

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

222 comment_strings=[ 

223 "optimistic", 

224 "useful", 

225 "relaxed", 

226 "interested in other people", 

227 "energy", 

228 "dealing with problems well", 

229 "thinking clearly", 

230 "feeling good about myself", 

231 "feeling close to others", 

232 "confident", 

233 "able to make up my own mind", 

234 "feeling loved", 

235 "interested in new things", 

236 "cheerful", 

237 ] 

238 ) 

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

240 

241 

242class Swemwbs(TaskHasPatientMixin, Task, 

243 metaclass=SwemwbsMetaclass): 

244 """ 

245 Server implementation of the SWEMWBS task. 

246 """ 

247 __tablename__ = "swemwbs" 

248 shortname = "SWEMWBS" 

249 extrastring_taskname = "wemwbs" # shares 

250 

251 MINQSCORE = 1 

252 MAXQSCORE = 5 

253 N_QUESTIONS = 7 

254 MINTOTALSCORE = N_QUESTIONS * MINQSCORE 

255 MAXTOTALSCORE = N_QUESTIONS * MAXQSCORE 

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

257 

258 @staticmethod 

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

260 _ = req.gettext 

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

262 

263 def is_complete(self) -> bool: 

264 return ( 

265 self.all_fields_not_none(self.TASK_FIELDS) and 

266 self.field_contents_valid() 

267 ) 

268 

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

270 return [TrackerInfo( 

271 value=self.total_score(), 

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

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

274 axis_min=self.MINTOTALSCORE - 0.5, 

275 axis_max=self.MAXTOTALSCORE + 0.5 

276 )] 

277 

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

279 if not self.is_complete(): 

280 return CTV_INCOMPLETE 

281 return [CtvInfo( 

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

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

284 )] 

285 

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

287 return self.standard_task_summary_fields() + [ 

288 SummaryElement( 

289 name="total", 

290 coltype=Integer(), 

291 value=self.total_score(), 

292 comment=f"Total score (range " 

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

294 ] 

295 

296 def total_score(self) -> int: 

297 return self.sum_fields(self.TASK_FIELDS) 

298 

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

300 main_dict = { 

301 None: None, 

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

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

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

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

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

307 } 

308 q_a = "" 

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

310 nstr = str(i) 

311 q_a += tr_qa(self.wxstring(req, "swemwbs_q" + nstr), 

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

313 

314 h = """ 

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

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

317 {tr_is_complete} 

318 {total_score} 

319 </table> 

320 </div> 

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

322 Ratings are over the last 2 weeks. 

323 </div> 

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

325 <tr> 

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

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

328 </tr> 

329 {q_a} 

330 </table> 

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

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

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

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

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

336 Creative Commons Attribution License. 

337 </div> 

338 """.format( 

339 CssClass=CssClass, 

340 tr_is_complete=self.get_is_complete_tr(req), 

341 total_score=tr( 

342 req.sstring(SS.TOTAL_SCORE), 

343 answer(self.total_score()) + 

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

345 ), 

346 q_a=q_a, 

347 ) 

348 return h 

349 

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

351 codes = [SnomedExpression(req.snomed(SnomedLookup.SWEMWBS_PROCEDURE_ASSESSMENT))] # noqa 

352 if self.is_complete(): 

353 codes.append(SnomedExpression( 

354 req.snomed(SnomedLookup.SWEMWBS_SCALE), 

355 { 

356 req.snomed(SnomedLookup.SWEMWBS_SCORE): self.total_score(), 

357 } 

358 )) 

359 return codes