Coverage for tasks/wsas.py : 62%

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