Coverage for tasks/cgisch.py: 50%
54 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/cgisch.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, List, Optional, Type
30from cardinal_pythonlib.stringfunc import strseq
32from camcops_server.cc_modules.cc_constants import CssClass
33from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
34from camcops_server.cc_modules.cc_db import add_multiple_columns
35from camcops_server.cc_modules.cc_html import (
36 subheading_spanning_two_columns,
37 tr_qa,
38 tr_span_col,
39)
40from camcops_server.cc_modules.cc_request import CamcopsRequest
41from camcops_server.cc_modules.cc_summaryelement import SummaryElement
42from camcops_server.cc_modules.cc_task import (
43 get_from_dict,
44 Task,
45 TaskHasClinicianMixin,
46 TaskHasPatientMixin,
47)
48from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
51# =============================================================================
52# CGI-SCH
53# =============================================================================
55QUESTION_FRAGMENTS = [
56 "positive",
57 "negative",
58 "depressive",
59 "cognitive",
60 "overall",
61]
64class CgiSch( # type: ignore[misc]
65 TaskHasPatientMixin,
66 TaskHasClinicianMixin,
67 Task,
68):
69 """
70 Server implementation of the CGI-SCH task.
71 """
73 __tablename__ = "cgisch"
74 shortname = "CGI-SCH"
75 provides_trackers = True
77 @classmethod
78 def extend_columns(cls: Type["CgiSch"], **kwargs: Any) -> None:
79 add_multiple_columns(
80 cls,
81 "severity",
82 1,
83 5,
84 minimum=1,
85 maximum=7,
86 comment_fmt="Severity Q{n}, {s} (1-7, higher worse)",
87 comment_strings=QUESTION_FRAGMENTS,
88 )
89 add_multiple_columns(
90 cls,
91 "change",
92 1,
93 5,
94 pv=list(range(1, 7 + 1)) + [9],
95 comment_fmt="Change Q{n}, {s} (1-7, higher worse, or 9 N/A)",
96 comment_strings=QUESTION_FRAGMENTS,
97 )
99 TASK_FIELDS = strseq("severity", 1, 5) + strseq("change", 1, 5)
101 @staticmethod
102 def longname(req: "CamcopsRequest") -> str:
103 _ = req.gettext
104 return _("Clinical Global Impression – Schizophrenia")
106 # noinspection PyUnresolvedReferences
107 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
108 prefix = "CGI-SCH severity: "
109 ylabel = "Score (1-7)"
110 return [
111 TrackerInfo(
112 value=self.severity1, # type: ignore[attr-defined]
113 plot_label=prefix + "positive symptoms",
114 axis_label=ylabel,
115 axis_min=0.5,
116 axis_max=7.5,
117 ),
118 TrackerInfo(
119 value=self.severity2, # type: ignore[attr-defined]
120 plot_label=prefix + "negative symptoms",
121 axis_label=ylabel,
122 axis_min=0.5,
123 axis_max=7.5,
124 ),
125 TrackerInfo(
126 value=self.severity3, # type: ignore[attr-defined]
127 plot_label=prefix + "depressive symptoms",
128 axis_label=ylabel,
129 axis_min=0.5,
130 axis_max=7.5,
131 ),
132 TrackerInfo(
133 value=self.severity4, # type: ignore[attr-defined]
134 plot_label=prefix + "cognitive symptoms",
135 axis_label=ylabel,
136 axis_min=0.5,
137 axis_max=7.5,
138 ),
139 TrackerInfo(
140 value=self.severity5, # type: ignore[attr-defined]
141 plot_label=prefix + "overall severity",
142 axis_label=ylabel,
143 axis_min=0.5,
144 axis_max=7.5,
145 ),
146 ]
148 # noinspection PyUnresolvedReferences
149 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
150 if not self.is_complete():
151 return CTV_INCOMPLETE
152 return [
153 CtvInfo(
154 content=(
155 f"CGI-SCH. Severity: positive {self.severity1}, " # type: ignore[attr-defined] # noqa: E501
156 f"negative {self.severity2}, " # type: ignore[attr-defined] # noqa: E501
157 f"depressive {self.severity3}, " # type: ignore[attr-defined] # noqa: E501
158 f"cognitive {self.severity4}, " # type: ignore[attr-defined] # noqa: E501
159 f"overall {self.severity5}. " # type: ignore[attr-defined]
160 f"Change: positive {self.change1}, " # type: ignore[attr-defined] # noqa: E501
161 f"negative {self.change2}, " # type: ignore[attr-defined]
162 f"depressive {self.change3}, " # type: ignore[attr-defined] # noqa: E501
163 f"cognitive {self.change4}, " # type: ignore[attr-defined]
164 f"overall {self.change5}." # type: ignore[attr-defined]
165 )
166 )
167 ]
169 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
170 # pylint: disable=unused-argument
171 return self.standard_task_summary_fields()
173 def is_complete(self) -> bool:
174 return (
175 self.all_fields_not_none(self.TASK_FIELDS)
176 and self.field_contents_valid()
177 )
179 # noinspection PyUnresolvedReferences
180 def get_task_html(self, req: CamcopsRequest) -> str:
181 severity_dict = {
182 None: None,
183 1: self.wxstring(req, "i_option1"),
184 2: self.wxstring(req, "i_option2"),
185 3: self.wxstring(req, "i_option3"),
186 4: self.wxstring(req, "i_option4"),
187 5: self.wxstring(req, "i_option5"),
188 6: self.wxstring(req, "i_option6"),
189 7: self.wxstring(req, "i_option7"),
190 }
191 change_dict = {
192 None: None,
193 1: self.wxstring(req, "ii_option1"),
194 2: self.wxstring(req, "ii_option2"),
195 3: self.wxstring(req, "ii_option3"),
196 4: self.wxstring(req, "ii_option4"),
197 5: self.wxstring(req, "ii_option5"),
198 6: self.wxstring(req, "ii_option6"),
199 7: self.wxstring(req, "ii_option7"),
200 9: self.wxstring(req, "ii_option9"),
201 }
203 def tr_severity(xstring_name: str, value: Optional[int]) -> str:
204 return tr_qa(
205 self.wxstring(req, xstring_name),
206 get_from_dict(severity_dict, value),
207 )
209 def tr_change(xstring_name: str, value: Optional[int]) -> str:
210 return tr_qa(
211 self.wxstring(req, xstring_name),
212 get_from_dict(change_dict, value),
213 )
215 change_1 = tr_change("q1", self.change1) # type: ignore[attr-defined]
216 change_2 = tr_change("q2", self.change2) # type: ignore[attr-defined]
217 change_3 = tr_change("q3", self.change3) # type: ignore[attr-defined]
218 change_4 = tr_change("q4", self.change4) # type: ignore[attr-defined]
219 change_5 = tr_change("q5", self.change5) # type: ignore[attr-defined]
221 severity_1 = tr_severity("q1", self.severity1) # type: ignore[attr-defined] # noqa: E501
222 severity_2 = tr_severity("q2", self.severity2) # type: ignore[attr-defined] # noqa: E501
223 severity_3 = tr_severity("q3", self.severity3) # type: ignore[attr-defined] # noqa: E501
224 severity_4 = tr_severity("q4", self.severity4) # type: ignore[attr-defined] # noqa: E501
225 severity_5 = tr_severity("q5", self.severity5) # type: ignore[attr-defined] # noqa: E501
227 return f"""
228 <div class="{CssClass.SUMMARY}">
229 <table class="{CssClass.SUMMARY}">
230 {self.get_is_complete_tr(req)}
231 </table>
232 </div>
233 <table class="{CssClass.TASKDETAIL}">
234 <tr>
235 <th width="70%">Question</th>
236 <th width="30%">Answer <sup>[1]</sup></th>
237 </tr>
238 {subheading_spanning_two_columns(self.wxstring(req, "i_title"))}
239 {tr_span_col(self.wxstring(req, "i_question"), cols=2)}
240 {severity_1}
241 {severity_2}
242 {severity_3}
243 {severity_4}
244 {severity_5}
246 {subheading_spanning_two_columns(self.wxstring(req, "ii_title"))}
247 {tr_span_col(self.wxstring(req, "ii_question"), cols=2)}
248 {change_1}
249 {change_2}
250 {change_3}
251 {change_4}
252 {change_5}
253 </table>
254 <div class="{CssClass.FOOTNOTES}">
255 [1] All questions are scored 1–7, or 9 (not applicable, for
256 change questions).
257 {self.wxstring(req, "ii_postscript")}
258 </div>
260 """ # noqa: E501