Coverage for tasks/wemwbs.py: 59%
98 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/tasks/wemwbs.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
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.
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.
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/>.
24===============================================================================
26"""
28from typing import Any, cast, List, Type
30from cardinal_pythonlib.stringfunc import strseq
31from sqlalchemy.sql.sqltypes import Integer
33from camcops_server.cc_modules.cc_constants import CssClass
34from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
35from camcops_server.cc_modules.cc_db import add_multiple_columns
36from camcops_server.cc_modules.cc_html import answer, tr, tr_qa
37from camcops_server.cc_modules.cc_request import CamcopsRequest
38from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup
39from camcops_server.cc_modules.cc_summaryelement import SummaryElement
40from camcops_server.cc_modules.cc_task import (
41 get_from_dict,
42 Task,
43 TaskHasPatientMixin,
44)
45from camcops_server.cc_modules.cc_text import SS
46from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
49# =============================================================================
50# WEMWBS
51# =============================================================================
54class Wemwbs( # type: ignore[misc]
55 TaskHasPatientMixin,
56 Task,
57):
58 """
59 Server implementation of the WEMWBS task.
60 """
62 __tablename__ = "wemwbs"
63 shortname = "WEMWBS"
64 provides_trackers = True
66 MINQSCORE = 1
67 MAXQSCORE = 5
68 N_QUESTIONS = 14
69 MINTOTALSCORE = N_QUESTIONS * MINQSCORE
70 MAXTOTALSCORE = N_QUESTIONS * MAXQSCORE
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 )
100 TASK_FIELDS = strseq("q", 1, N_QUESTIONS)
102 @staticmethod
103 def longname(req: "CamcopsRequest") -> str:
104 _ = req.gettext
105 return _("Warwick–Edinburgh Mental Well-Being Scale")
107 def is_complete(self) -> bool:
108 return (
109 self.all_fields_not_none(self.TASK_FIELDS)
110 and self.field_contents_valid()
111 )
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 ]
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 ]
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 ]
145 def total_score(self) -> int:
146 return cast(int, self.sum_fields(self.TASK_FIELDS))
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
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
224# =============================================================================
225# SWEMWBS
226# =============================================================================
229class Swemwbs( # type: ignore[misc]
230 TaskHasPatientMixin,
231 Task,
232):
233 """
234 Server implementation of the SWEMWBS task.
235 """
237 __tablename__ = "swemwbs"
238 shortname = "SWEMWBS"
239 extrastring_taskname = "wemwbs" # shares
240 info_filename_stem = extrastring_taskname
242 MINQSCORE = 1
243 MAXQSCORE = 5
244 N_QUESTIONS = 7
245 MINTOTALSCORE = N_QUESTIONS * MINQSCORE
246 MAXTOTALSCORE = N_QUESTIONS * MAXQSCORE
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 )
276 TASK_FIELDS = strseq("q", 1, N_QUESTIONS)
278 @staticmethod
279 def longname(req: "CamcopsRequest") -> str:
280 _ = req.gettext
281 return _("Short Warwick–Edinburgh Mental Well-Being Scale")
283 def is_complete(self) -> bool:
284 return (
285 self.all_fields_not_none(self.TASK_FIELDS)
286 and self.field_contents_valid()
287 )
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 ]
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 ]
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 ]
321 def total_score(self) -> int:
322 return cast(int, self.sum_fields(self.TASK_FIELDS))
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 )
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
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