Coverage for tasks/wemwbs.py: 61%
102 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/tasks/wemwbs.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
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.
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.
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/>.
26===============================================================================
28"""
30from typing import Any, Dict, List, Tuple, Type
32from cardinal_pythonlib.stringfunc import strseq
33from sqlalchemy.ext.declarative import DeclarativeMeta
34from sqlalchemy.sql.sqltypes import Integer
36from camcops_server.cc_modules.cc_constants import CssClass
37from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
38from camcops_server.cc_modules.cc_db import add_multiple_columns
39from camcops_server.cc_modules.cc_html import answer, tr, tr_qa
40from camcops_server.cc_modules.cc_request import CamcopsRequest
41from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup
42from camcops_server.cc_modules.cc_summaryelement import SummaryElement
43from camcops_server.cc_modules.cc_task import (
44 get_from_dict,
45 Task,
46 TaskHasPatientMixin,
47)
48from camcops_server.cc_modules.cc_text import SS
49from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
52# =============================================================================
53# WEMWBS
54# =============================================================================
57class WemwbsMetaclass(DeclarativeMeta):
58 # noinspection PyInitNewSignature
59 def __init__(
60 cls: Type["Wemwbs"],
61 name: str,
62 bases: Tuple[Type, ...],
63 classdict: Dict[str, Any],
64 ) -> None:
65 add_multiple_columns(
66 cls,
67 "q",
68 1,
69 cls.N_QUESTIONS,
70 minimum=1,
71 maximum=5,
72 comment_fmt="Q{n} ({s}) (1 none of the time - 5 all of the time)",
73 comment_strings=[
74 "optimistic",
75 "useful",
76 "relaxed",
77 "interested in other people",
78 "energy",
79 "dealing with problems well",
80 "thinking clearly",
81 "feeling good about myself",
82 "feeling close to others",
83 "confident",
84 "able to make up my own mind",
85 "feeling loved",
86 "interested in new things",
87 "cheerful",
88 ],
89 )
90 super().__init__(name, bases, classdict)
93class Wemwbs(TaskHasPatientMixin, Task, metaclass=WemwbsMetaclass):
94 """
95 Server implementation of the WEMWBS task.
96 """
98 __tablename__ = "wemwbs"
99 shortname = "WEMWBS"
100 provides_trackers = True
102 MINQSCORE = 1
103 MAXQSCORE = 5
104 N_QUESTIONS = 14
105 MINTOTALSCORE = N_QUESTIONS * MINQSCORE
106 MAXTOTALSCORE = N_QUESTIONS * MAXQSCORE
107 TASK_FIELDS = strseq("q", 1, N_QUESTIONS)
109 @staticmethod
110 def longname(req: "CamcopsRequest") -> str:
111 _ = req.gettext
112 return _("Warwick–Edinburgh Mental Well-Being Scale")
114 def is_complete(self) -> bool:
115 return (
116 self.all_fields_not_none(self.TASK_FIELDS)
117 and self.field_contents_valid()
118 )
120 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
121 return [
122 TrackerInfo(
123 value=self.total_score(),
124 plot_label="WEMWBS total score (rating mental well-being)",
125 axis_label=f"Total score ({self.MINTOTALSCORE}-{self.MAXTOTALSCORE})", # noqa
126 axis_min=self.MINTOTALSCORE - 0.5,
127 axis_max=self.MAXTOTALSCORE + 0.5,
128 )
129 ]
131 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
132 if not self.is_complete():
133 return CTV_INCOMPLETE
134 return [
135 CtvInfo(
136 content=f"WEMWBS total score {self.total_score()} "
137 f"(range {self.MINTOTALSCORE}–{self.MAXTOTALSCORE})"
138 )
139 ]
141 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
142 return self.standard_task_summary_fields() + [
143 SummaryElement(
144 name="total",
145 coltype=Integer(),
146 value=self.total_score(),
147 comment=f"Total score (range "
148 f"{self.MINTOTALSCORE}-{self.MAXTOTALSCORE})",
149 )
150 ]
152 def total_score(self) -> int:
153 return self.sum_fields(self.TASK_FIELDS)
155 def get_task_html(self, req: CamcopsRequest) -> str:
156 main_dict = {
157 None: None,
158 1: "1 — " + self.wxstring(req, "wemwbs_a1"),
159 2: "2 — " + self.wxstring(req, "wemwbs_a2"),
160 3: "3 — " + self.wxstring(req, "wemwbs_a3"),
161 4: "4 — " + self.wxstring(req, "wemwbs_a4"),
162 5: "5 — " + self.wxstring(req, "wemwbs_a5"),
163 }
164 q_a = ""
165 for i in range(1, self.N_QUESTIONS + 1):
166 nstr = str(i)
167 q_a += tr_qa(
168 self.wxstring(req, "wemwbs_q" + nstr),
169 get_from_dict(main_dict, getattr(self, "q" + nstr)),
170 )
171 h = """
172 <div class="{css_summary}">
173 <table class="{css_summary}">
174 {tr_is_complete}
175 {tr_total_score}
176 </table>
177 </div>
178 <div class="{css_explanation}">
179 Ratings are over the last 2 weeks.
180 </div>
181 <table class="{css_taskdetail}">
182 <tr>
183 <th width="60%">Question</th>
184 <th width="40%">Answer</th>
185 </tr>
186 {q_a}
187 </table>
188 <div class="{css_copyright}">
189 WEMWBS: from Tennant et al. (2007), <i>Health and Quality of
190 Life Outcomes</i> 5:63,
191 <a href="http://www.hqlo.com/content/5/1/63">
192 http://www.hqlo.com/content/5/1/63</a>;
193 © 2007 Tennant et al.; distributed under the terms of the
194 Creative Commons Attribution License.
195 </div>
196 """.format(
197 css_summary=CssClass.SUMMARY,
198 tr_is_complete=self.get_is_complete_tr(req),
199 tr_total_score=tr(
200 req.sstring(SS.TOTAL_SCORE),
201 answer(self.total_score())
202 + f" (range {self.MINTOTALSCORE}–{self.MAXTOTALSCORE})",
203 ),
204 css_explanation=CssClass.EXPLANATION,
205 css_taskdetail=CssClass.TASKDETAIL,
206 q_a=q_a,
207 css_copyright=CssClass.COPYRIGHT,
208 )
209 return h
211 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
212 codes = [
213 SnomedExpression(
214 req.snomed(SnomedLookup.WEMWBS_PROCEDURE_ASSESSMENT)
215 )
216 ]
217 if self.is_complete():
218 codes.append(
219 SnomedExpression(
220 req.snomed(SnomedLookup.WEMWBS_SCALE),
221 {
222 req.snomed(
223 SnomedLookup.WEMWBS_SCORE
224 ): self.total_score()
225 },
226 )
227 )
228 return codes
231# =============================================================================
232# SWEMWBS
233# =============================================================================
236class SwemwbsMetaclass(DeclarativeMeta):
237 # noinspection PyInitNewSignature
238 def __init__(
239 cls: Type["Swemwbs"],
240 name: str,
241 bases: Tuple[Type, ...],
242 classdict: Dict[str, Any],
243 ) -> None:
244 add_multiple_columns(
245 cls,
246 "q",
247 1,
248 cls.N_QUESTIONS,
249 minimum=1,
250 maximum=5,
251 comment_fmt="Q{n} ({s}) (1 none of the time - 5 all of the time)",
252 comment_strings=[
253 "optimistic",
254 "useful",
255 "relaxed",
256 "interested in other people",
257 "energy",
258 "dealing with problems well",
259 "thinking clearly",
260 "feeling good about myself",
261 "feeling close to others",
262 "confident",
263 "able to make up my own mind",
264 "feeling loved",
265 "interested in new things",
266 "cheerful",
267 ],
268 )
269 super().__init__(name, bases, classdict)
272class Swemwbs(TaskHasPatientMixin, Task, metaclass=SwemwbsMetaclass):
273 """
274 Server implementation of the SWEMWBS task.
275 """
277 __tablename__ = "swemwbs"
278 shortname = "SWEMWBS"
279 extrastring_taskname = "wemwbs" # shares
280 info_filename_stem = extrastring_taskname
282 MINQSCORE = 1
283 MAXQSCORE = 5
284 N_QUESTIONS = 7
285 MINTOTALSCORE = N_QUESTIONS * MINQSCORE
286 MAXTOTALSCORE = N_QUESTIONS * MAXQSCORE
287 TASK_FIELDS = strseq("q", 1, N_QUESTIONS)
289 @staticmethod
290 def longname(req: "CamcopsRequest") -> str:
291 _ = req.gettext
292 return _("Short Warwick–Edinburgh Mental Well-Being Scale")
294 def is_complete(self) -> bool:
295 return (
296 self.all_fields_not_none(self.TASK_FIELDS)
297 and self.field_contents_valid()
298 )
300 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
301 return [
302 TrackerInfo(
303 value=self.total_score(),
304 plot_label="SWEMWBS total score (rating mental well-being)",
305 axis_label=f"Total score ({self.MINTOTALSCORE}-{self.MAXTOTALSCORE})", # noqa
306 axis_min=self.MINTOTALSCORE - 0.5,
307 axis_max=self.MAXTOTALSCORE + 0.5,
308 )
309 ]
311 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
312 if not self.is_complete():
313 return CTV_INCOMPLETE
314 return [
315 CtvInfo(
316 content=f"SWEMWBS total score {self.total_score()} "
317 f"(range {self.MINTOTALSCORE}–{self.MAXTOTALSCORE})"
318 )
319 ]
321 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
322 return self.standard_task_summary_fields() + [
323 SummaryElement(
324 name="total",
325 coltype=Integer(),
326 value=self.total_score(),
327 comment=f"Total score (range "
328 f"{self.MINTOTALSCORE}-{self.MAXTOTALSCORE})",
329 )
330 ]
332 def total_score(self) -> int:
333 return self.sum_fields(self.TASK_FIELDS)
335 def get_task_html(self, req: CamcopsRequest) -> str:
336 main_dict = {
337 None: None,
338 1: "1 — " + self.wxstring(req, "wemwbs_a1"),
339 2: "2 — " + self.wxstring(req, "wemwbs_a2"),
340 3: "3 — " + self.wxstring(req, "wemwbs_a3"),
341 4: "4 — " + self.wxstring(req, "wemwbs_a4"),
342 5: "5 — " + self.wxstring(req, "wemwbs_a5"),
343 }
344 q_a = ""
345 for i in range(1, self.N_QUESTIONS + 1):
346 nstr = str(i)
347 q_a += tr_qa(
348 self.wxstring(req, "swemwbs_q" + nstr),
349 get_from_dict(main_dict, getattr(self, "q" + nstr)),
350 )
352 h = """
353 <div class="{CssClass.SUMMARY}">
354 <table class="{CssClass.SUMMARY}">
355 {tr_is_complete}
356 {total_score}
357 </table>
358 </div>
359 <div class="{CssClass.EXPLANATION}">
360 Ratings are over the last 2 weeks.
361 </div>
362 <table class="{CssClass.TASKDETAIL}">
363 <tr>
364 <th width="60%">Question</th>
365 <th width="40%">Answer</th>
366 </tr>
367 {q_a}
368 </table>
369 <div class="{CssClass.COPYRIGHT}">
370 SWEMWBS: from Stewart-Brown et al. (2009), <i>Health and
371 Quality of Life Outcomes</i> 7:15,
372 http://www.hqlo.com/content/7/1/15;
373 © 2009 Stewart-Brown et al.; distributed under the terms of the
374 Creative Commons Attribution License.
375 </div>
376 """.format(
377 CssClass=CssClass,
378 tr_is_complete=self.get_is_complete_tr(req),
379 total_score=tr(
380 req.sstring(SS.TOTAL_SCORE),
381 answer(self.total_score())
382 + f" (range {self.MINTOTALSCORE}–{self.MAXTOTALSCORE})",
383 ),
384 q_a=q_a,
385 )
386 return h
388 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
389 codes = [
390 SnomedExpression(
391 req.snomed(SnomedLookup.SWEMWBS_PROCEDURE_ASSESSMENT)
392 )
393 ]
394 if self.is_complete():
395 codes.append(
396 SnomedExpression(
397 req.snomed(SnomedLookup.SWEMWBS_SCALE),
398 {
399 req.snomed(
400 SnomedLookup.SWEMWBS_SCORE
401 ): self.total_score()
402 },
403 )
404 )
405 return codes