Coverage for tasks/cgi_task.py: 58%
76 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/cgi_task.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 Dict, List
32from sqlalchemy.sql.sqltypes import Integer
34from camcops_server.cc_modules.cc_constants import CssClass
35from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
36from camcops_server.cc_modules.cc_html import answer, italic, tr, tr_qa
37from camcops_server.cc_modules.cc_request import CamcopsRequest
38from camcops_server.cc_modules.cc_sqla_coltypes import (
39 CamcopsColumn,
40 PermittedValueChecker,
41)
42from camcops_server.cc_modules.cc_summaryelement import SummaryElement
43from camcops_server.cc_modules.cc_task import (
44 get_from_dict,
45 Task,
46 TaskHasClinicianMixin,
47 TaskHasPatientMixin,
48)
49from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
52# =============================================================================
53# CGI
54# =============================================================================
57class Cgi(TaskHasPatientMixin, TaskHasClinicianMixin, Task):
58 """
59 Server implementation of the CGI task.
60 """
62 __tablename__ = "cgi"
63 shortname = "CGI"
64 provides_trackers = True
66 q1 = CamcopsColumn(
67 "q1",
68 Integer,
69 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=7),
70 comment="Q1. Severity (1-7, higher worse, 0 not assessed)",
71 )
72 q2 = CamcopsColumn(
73 "q2",
74 Integer,
75 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=7),
76 comment="Q2. Global improvement (1-7, higher worse, 0 not assessed)",
77 )
78 q3t = CamcopsColumn(
79 "q3t",
80 Integer,
81 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=4),
82 comment="Q3T. Therapeutic effects (1-4, higher worse, 0 not assessed)",
83 )
84 q3s = CamcopsColumn(
85 "q3s",
86 Integer,
87 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=4),
88 comment="Q3S. Side effects (1-4, higher worse, 0 not assessed)",
89 )
90 q3 = CamcopsColumn(
91 "q3",
92 Integer,
93 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=16),
94 comment="Q3 (calculated). Efficacy index [(Q3T - 1) * 4 + Q3S].",
95 )
97 TASK_FIELDS = ["q1", "q2", "q3t", "q3s", "q3"]
98 MAX_SCORE = 30
100 @staticmethod
101 def longname(req: "CamcopsRequest") -> str:
102 _ = req.gettext
103 return _("Clinical Global Impressions")
105 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
106 return [
107 TrackerInfo(
108 value=self.total_score(),
109 plot_label="CGI total score",
110 axis_label=f"Total score (out of {self.MAX_SCORE})",
111 axis_min=-0.5,
112 axis_max=self.MAX_SCORE + 0.5,
113 )
114 ]
116 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
117 if not self.is_complete():
118 return CTV_INCOMPLETE
119 return [
120 CtvInfo(
121 content=(
122 f"CGI total score {self.total_score()}/{self.MAX_SCORE}"
123 )
124 )
125 ]
127 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
128 return self.standard_task_summary_fields() + [
129 SummaryElement(
130 name="total", coltype=Integer(), value=self.total_score()
131 )
132 ]
134 def is_complete(self) -> bool:
135 if not (
136 self.all_fields_not_none(self.TASK_FIELDS)
137 and self.field_contents_valid()
138 ):
139 return False
140 # Requirement for everything to be non-zero removed in v2.0.0
141 # if self.q1 == 0 or self.q2 == 0 or self.q3t == 0 or self.q3s == 0:
142 # return False
143 return True
145 def total_score(self) -> int:
146 return self.sum_fields(["q1", "q2", "q3"])
148 def get_task_html(self, req: CamcopsRequest) -> str:
149 q1_dict = {
150 None: None,
151 0: self.wxstring(req, "q1_option0"),
152 1: self.wxstring(req, "q1_option1"),
153 2: self.wxstring(req, "q1_option2"),
154 3: self.wxstring(req, "q1_option3"),
155 4: self.wxstring(req, "q1_option4"),
156 5: self.wxstring(req, "q1_option5"),
157 6: self.wxstring(req, "q1_option6"),
158 7: self.wxstring(req, "q1_option7"),
159 }
160 q2_dict = {
161 None: None,
162 0: self.wxstring(req, "q2_option0"),
163 1: self.wxstring(req, "q2_option1"),
164 2: self.wxstring(req, "q2_option2"),
165 3: self.wxstring(req, "q2_option3"),
166 4: self.wxstring(req, "q2_option4"),
167 5: self.wxstring(req, "q2_option5"),
168 6: self.wxstring(req, "q2_option6"),
169 7: self.wxstring(req, "q2_option7"),
170 }
171 q3t_dict = {
172 None: None,
173 0: self.wxstring(req, "q3t_option0"),
174 1: self.wxstring(req, "q3t_option1"),
175 2: self.wxstring(req, "q3t_option2"),
176 3: self.wxstring(req, "q3t_option3"),
177 4: self.wxstring(req, "q3t_option4"),
178 }
179 q3s_dict = {
180 None: None,
181 0: self.wxstring(req, "q3s_option0"),
182 1: self.wxstring(req, "q3s_option1"),
183 2: self.wxstring(req, "q3s_option2"),
184 3: self.wxstring(req, "q3s_option3"),
185 4: self.wxstring(req, "q3s_option4"),
186 }
188 tr_total_score = tr(
189 "Total score <sup>[1]</sup>", answer(self.total_score())
190 )
191 tr_q1 = tr_qa(
192 self.wxstring(req, "q1_s") + " <sup>[2]</sup>",
193 get_from_dict(q1_dict, self.q1),
194 )
195 tr_q2 = tr_qa(
196 self.wxstring(req, "q2_s") + " <sup>[2]</sup>",
197 get_from_dict(q2_dict, self.q2),
198 )
199 tr_q3t = tr_qa(
200 self.wxstring(req, "q3t_s") + " <sup>[3]</sup>",
201 get_from_dict(q3t_dict, self.q3t),
202 )
203 tr_q3s = tr_qa(
204 self.wxstring(req, "q3s_s") + " <sup>[3]</sup>",
205 get_from_dict(q3s_dict, self.q3s),
206 )
207 tr_q3 = tr(
208 f"""
209 {self.wxstring(req, "q3_s")} <sup>[4]</sup>
210 <div class="{CssClass.SMALLPRINT}">
211 [(Q3T – 1) × 4 + Q3S]
212 </div>
213 """,
214 answer(self.q3, formatter_answer=italic),
215 )
216 return f"""
217 <div class="{CssClass.SUMMARY}">
218 <table class="{CssClass.SUMMARY}">
219 {self.get_is_complete_tr(req)}
220 {tr_total_score}
221 </table>
222 </div>
223 <table class="{CssClass.TASKDETAIL}">
224 <tr>
225 <th width="30%">Question</th>
226 <th width="70%">Answer</th>
227 </tr>
228 {tr_q1}
229 {tr_q2}
230 {tr_q3t}
231 {tr_q3s}
232 {tr_q3}
233 </table>
234 <div class="{CssClass.FOOTNOTES}">
235 [1] Total score: Q1 + Q2 + Q3. Range 3–30 when complete.
236 [2] Questions 1 and 2 are scored 1–7 (0 for not assessed).
237 [3] Questions 3T and 3S are scored 1–4 (0 for not assessed).
238 [4] Q3 is scored 1–16 if Q3T/Q3S complete.
239 </div>
240 """
243# =============================================================================
244# CGI-I
245# =============================================================================
248class CgiI(TaskHasPatientMixin, TaskHasClinicianMixin, Task):
249 __tablename__ = "cgi_i"
250 shortname = "CGI-I"
251 extrastring_taskname = "cgi" # shares with CGI
252 info_filename_stem = "cgi"
254 q = CamcopsColumn(
255 "q",
256 Integer,
257 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=7),
258 comment="Global improvement (1-7, higher worse)",
259 )
261 TASK_FIELDS = ["q"]
263 @staticmethod
264 def longname(req: "CamcopsRequest") -> str:
265 _ = req.gettext
266 return _("Clinical Global Impressions – Improvement")
268 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
269 if not self.is_complete():
270 return CTV_INCOMPLETE
271 return [
272 CtvInfo(
273 content="CGI-I rating: {}".format(self.get_rating_text(req))
274 )
275 ]
277 def is_complete(self) -> bool:
278 return (
279 self.all_fields_not_none(self.TASK_FIELDS)
280 and self.field_contents_valid()
281 )
283 def get_rating_text(self, req: CamcopsRequest) -> str:
284 qdict = self.get_q_dict(req)
285 return get_from_dict(qdict, self.q)
287 def get_q_dict(self, req: CamcopsRequest) -> Dict:
288 return {
289 None: None,
290 0: self.wxstring(req, "q2_option0"),
291 1: self.wxstring(req, "q2_option1"),
292 2: self.wxstring(req, "q2_option2"),
293 3: self.wxstring(req, "q2_option3"),
294 4: self.wxstring(req, "q2_option4"),
295 5: self.wxstring(req, "q2_option5"),
296 6: self.wxstring(req, "q2_option6"),
297 7: self.wxstring(req, "q2_option7"),
298 }
300 def get_task_html(self, req: CamcopsRequest) -> str:
301 return f"""
302 <div class="{CssClass.SUMMARY}">
303 <table class="{CssClass.SUMMARY}">
304 {self.get_is_complete_tr(req)}
305 </table>
306 </div>
307 <table class="{CssClass.TASKDETAIL}">
308 <tr>
309 <th width="50%">Question</th>
310 <th width="50%">Answer</th>
311 </tr>
312 {tr_qa(self.wxstring(req, "i_q"), self.get_rating_text(req))}
313 </table>
314 """