Coverage for tasks/wsas.py: 62%
69 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/wsas.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.schema import Column
35from sqlalchemy.sql.sqltypes import Boolean, Integer
37from camcops_server.cc_modules.cc_constants import (
38 CssClass,
39 DATA_COLLECTION_UNLESS_UPGRADED_DIV,
40)
41from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
42from camcops_server.cc_modules.cc_db import add_multiple_columns
43from camcops_server.cc_modules.cc_html import answer, get_true_false, tr, tr_qa
44from camcops_server.cc_modules.cc_request import CamcopsRequest
45from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup
46from camcops_server.cc_modules.cc_string import AS
47from camcops_server.cc_modules.cc_summaryelement import SummaryElement
48from camcops_server.cc_modules.cc_task import (
49 get_from_dict,
50 Task,
51 TaskHasPatientMixin,
52)
53from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
56# =============================================================================
57# WSAS
58# =============================================================================
61class WsasMetaclass(DeclarativeMeta):
62 # noinspection PyInitNewSignature
63 def __init__(
64 cls: Type["Wsas"],
65 name: str,
66 bases: Tuple[Type, ...],
67 classdict: Dict[str, Any],
68 ) -> None:
69 add_multiple_columns(
70 cls,
71 "q",
72 1,
73 cls.NQUESTIONS,
74 minimum=cls.MIN_PER_Q,
75 maximum=cls.MAX_PER_Q,
76 comment_fmt="Q{n}, {s} (0-4, higher worse)",
77 comment_strings=[
78 "work",
79 "home management",
80 "social leisure",
81 "private leisure",
82 "relationships",
83 ],
84 )
85 super().__init__(name, bases, classdict)
88class Wsas(TaskHasPatientMixin, Task, metaclass=WsasMetaclass):
89 """
90 Server implementation of the WSAS task.
91 """
93 __tablename__ = "wsas"
94 shortname = "WSAS"
95 provides_trackers = True
97 retired_etc = Column(
98 "retired_etc",
99 Boolean,
100 comment="Retired or choose not to have job for reason unrelated "
101 "to problem",
102 )
104 MIN_PER_Q = 0
105 MAX_PER_Q = 8
106 NQUESTIONS = 5
107 QUESTION_FIELDS = strseq("q", 1, NQUESTIONS)
108 Q2_TO_END = strseq("q", 2, NQUESTIONS)
109 TASK_FIELDS = QUESTION_FIELDS + ["retired_etc"]
110 MAX_IF_WORKING = MAX_PER_Q * NQUESTIONS
111 MAX_IF_RETIRED = MAX_PER_Q * (NQUESTIONS - 1)
113 @staticmethod
114 def longname(req: "CamcopsRequest") -> str:
115 _ = req.gettext
116 return _("Work and Social Adjustment Scale")
118 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
119 return [
120 TrackerInfo(
121 value=self.total_score(),
122 plot_label="WSAS total score (lower is better)",
123 axis_label=f"Total score (out of "
124 f"{self.MAX_IF_RETIRED}–{self.MAX_IF_WORKING})",
125 axis_min=-0.5,
126 axis_max=self.MAX_IF_WORKING + 0.5,
127 )
128 ]
130 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
131 return self.standard_task_summary_fields() + [
132 SummaryElement(
133 name="total_score",
134 coltype=Integer(),
135 value=self.total_score(),
136 comment=f"Total score (/ {self.max_score()})",
137 )
138 ]
140 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
141 if not self.is_complete():
142 return CTV_INCOMPLETE
143 return [
144 CtvInfo(
145 content=f"WSAS total score "
146 f"{self.total_score()}/{self.max_score()}"
147 )
148 ]
150 def total_score(self) -> int:
151 return self.sum_fields(
152 self.Q2_TO_END if self.retired_etc else self.QUESTION_FIELDS
153 )
155 def max_score(self) -> int:
156 return self.MAX_IF_RETIRED if self.retired_etc else self.MAX_IF_WORKING
158 def is_complete(self) -> bool:
159 return (
160 self.all_fields_not_none(
161 self.Q2_TO_END if self.retired_etc else self.QUESTION_FIELDS
162 )
163 and self.field_contents_valid()
164 )
166 def get_task_html(self, req: CamcopsRequest) -> str:
167 option_dict = {None: None}
168 for a in range(self.MIN_PER_Q, self.MAX_PER_Q + 1):
169 option_dict[a] = req.wappstring(AS.WSAS_A_PREFIX + str(a))
170 q_a = ""
171 for q in range(1, self.NQUESTIONS + 1):
172 a = getattr(self, "q" + str(q))
173 fa = get_from_dict(option_dict, a) if a is not None else None
174 q_a += tr(self.wxstring(req, "q" + str(q)), answer(fa))
175 return f"""
176 <div class="{CssClass.SUMMARY}">
177 <table class="{CssClass.SUMMARY}">
178 {self.get_is_complete_tr(req)}
179 <tr>
180 <td>Total score</td>
181 <td>{answer(self.total_score())} / 40</td>
182 </td>
183 </table>
184 </div>
185 <table class="{CssClass.TASKDETAIL}">
186 <tr>
187 <th width="75%">Question</th>
188 <th width="25%">Answer</th>
189 </tr>
190 {tr_qa(self.wxstring(req, "q_retired_etc"),
191 get_true_false(req, self.retired_etc))}
192 </table>
193 <table class="{CssClass.TASKDETAIL}">
194 <tr>
195 <th width="75%">Question</th>
196 <th width="25%">Answer (0–8)</th>
197 </tr>
198 {q_a}
199 </table>
200 {DATA_COLLECTION_UNLESS_UPGRADED_DIV}
201 """
203 # noinspection PyUnresolvedReferences
204 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
205 codes = [
206 SnomedExpression(
207 req.snomed(SnomedLookup.WSAS_PROCEDURE_ASSESSMENT)
208 )
209 ]
210 if self.is_complete():
211 d = {
212 req.snomed(SnomedLookup.WSAS_SCORE): self.total_score(),
213 req.snomed(SnomedLookup.WSAS_HOME_MANAGEMENT_SCORE): self.q2,
214 req.snomed(SnomedLookup.WSAS_SOCIAL_LEISURE_SCORE): self.q3,
215 req.snomed(SnomedLookup.WSAS_PRIVATE_LEISURE_SCORE): self.q4,
216 req.snomed(SnomedLookup.WSAS_RELATIONSHIPS_SCORE): self.q5,
217 }
218 if not self.retired_etc:
219 d[req.snomed(SnomedLookup.WSAS_WORK_SCORE)] = self.q1
220 codes.append(
221 SnomedExpression(req.snomed(SnomedLookup.WSAS_SCALE), d)
222 )
223 return codes