Coverage for tasks/wemwbs.py : 60%

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
3"""
4camcops_server/tasks/wemwbs.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
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.
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.
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/>.
25===============================================================================
27"""
29from typing import Any, Dict, List, Tuple, Type
31from cardinal_pythonlib.stringfunc import strseq
32from sqlalchemy.ext.declarative import DeclarativeMeta
33from sqlalchemy.sql.sqltypes import Integer
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
51# =============================================================================
52# WEMWBS
53# =============================================================================
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)
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
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)
101 @staticmethod
102 def longname(req: "CamcopsRequest") -> str:
103 _ = req.gettext
104 return _("Warwick–Edinburgh Mental Well-Being Scale")
106 def is_complete(self) -> bool:
107 return (
108 self.all_fields_not_none(self.TASK_FIELDS) and
109 self.field_contents_valid()
110 )
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 )]
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 )]
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 ]
139 def total_score(self) -> int:
140 return self.sum_fields(self.TASK_FIELDS)
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
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
208# =============================================================================
209# SWEMWBS
210# =============================================================================
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)
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
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)
258 @staticmethod
259 def longname(req: "CamcopsRequest") -> str:
260 _ = req.gettext
261 return _("Short Warwick–Edinburgh Mental Well-Being Scale")
263 def is_complete(self) -> bool:
264 return (
265 self.all_fields_not_none(self.TASK_FIELDS) and
266 self.field_contents_valid()
267 )
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 )]
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 )]
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 ]
296 def total_score(self) -> int:
297 return self.sum_fields(self.TASK_FIELDS)
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)))
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
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