Coverage for tasks/cgisch.py: 64%
47 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/cgisch.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, Optional, Tuple, Type
32from cardinal_pythonlib.stringfunc import strseq
33from sqlalchemy.ext.declarative import DeclarativeMeta
35from camcops_server.cc_modules.cc_constants import CssClass
36from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
37from camcops_server.cc_modules.cc_db import add_multiple_columns
38from camcops_server.cc_modules.cc_html import (
39 subheading_spanning_two_columns,
40 tr_qa,
41 tr_span_col,
42)
43from camcops_server.cc_modules.cc_request import CamcopsRequest
44from camcops_server.cc_modules.cc_summaryelement import SummaryElement
45from camcops_server.cc_modules.cc_task import (
46 get_from_dict,
47 Task,
48 TaskHasClinicianMixin,
49 TaskHasPatientMixin,
50)
51from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
54# =============================================================================
55# CGI-SCH
56# =============================================================================
58QUESTION_FRAGMENTS = [
59 "positive",
60 "negative",
61 "depressive",
62 "cognitive",
63 "overall",
64]
67class CgiSchMetaclass(DeclarativeMeta):
68 """
69 Metaclass for :class:`CgiSch`.
70 """
72 # noinspection PyInitNewSignature
73 def __init__(
74 cls: Type["CgiSch"],
75 name: str,
76 bases: Tuple[Type, ...],
77 classdict: Dict[str, Any],
78 ) -> 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 )
98 super().__init__(name, bases, classdict)
101class CgiSch(
102 TaskHasPatientMixin, TaskHasClinicianMixin, Task, metaclass=CgiSchMetaclass
103):
104 """
105 Server implementation of the CGI-SCH task.
106 """
108 __tablename__ = "cgisch"
109 shortname = "CGI-SCH"
110 provides_trackers = True
112 TASK_FIELDS = strseq("severity", 1, 5) + strseq("change", 1, 5)
114 @staticmethod
115 def longname(req: "CamcopsRequest") -> str:
116 _ = req.gettext
117 return _("Clinical Global Impression – Schizophrenia")
119 # noinspection PyUnresolvedReferences
120 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
121 prefix = "CGI-SCH severity: "
122 ylabel = "Score (1-7)"
123 return [
124 TrackerInfo(
125 value=self.severity1,
126 plot_label=prefix + "positive symptoms",
127 axis_label=ylabel,
128 axis_min=0.5,
129 axis_max=7.5,
130 ),
131 TrackerInfo(
132 value=self.severity2,
133 plot_label=prefix + "negative symptoms",
134 axis_label=ylabel,
135 axis_min=0.5,
136 axis_max=7.5,
137 ),
138 TrackerInfo(
139 value=self.severity3,
140 plot_label=prefix + "depressive symptoms",
141 axis_label=ylabel,
142 axis_min=0.5,
143 axis_max=7.5,
144 ),
145 TrackerInfo(
146 value=self.severity4,
147 plot_label=prefix + "cognitive symptoms",
148 axis_label=ylabel,
149 axis_min=0.5,
150 axis_max=7.5,
151 ),
152 TrackerInfo(
153 value=self.severity5,
154 plot_label=prefix + "overall severity",
155 axis_label=ylabel,
156 axis_min=0.5,
157 axis_max=7.5,
158 ),
159 ]
161 # noinspection PyUnresolvedReferences
162 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
163 if not self.is_complete():
164 return CTV_INCOMPLETE
165 return [
166 CtvInfo(
167 content=(
168 f"CGI-SCH. Severity: positive {self.severity1}, "
169 f"negative {self.severity2}, "
170 f"depressive {self.severity3}, "
171 f"cognitive {self.severity4}, "
172 f"overall {self.severity5}. "
173 f"Change: positive {self.change1}, "
174 f"negative {self.change2}, "
175 f"depressive {self.change3}, "
176 f"cognitive {self.change4}, "
177 f"overall {self.change5}."
178 )
179 )
180 ]
182 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
183 # pylint: disable=unused-argument
184 return self.standard_task_summary_fields()
186 def is_complete(self) -> bool:
187 return (
188 self.all_fields_not_none(self.TASK_FIELDS)
189 and self.field_contents_valid()
190 )
192 # noinspection PyUnresolvedReferences
193 def get_task_html(self, req: CamcopsRequest) -> str:
194 severity_dict = {
195 None: None,
196 1: self.wxstring(req, "i_option1"),
197 2: self.wxstring(req, "i_option2"),
198 3: self.wxstring(req, "i_option3"),
199 4: self.wxstring(req, "i_option4"),
200 5: self.wxstring(req, "i_option5"),
201 6: self.wxstring(req, "i_option6"),
202 7: self.wxstring(req, "i_option7"),
203 }
204 change_dict = {
205 None: None,
206 1: self.wxstring(req, "ii_option1"),
207 2: self.wxstring(req, "ii_option2"),
208 3: self.wxstring(req, "ii_option3"),
209 4: self.wxstring(req, "ii_option4"),
210 5: self.wxstring(req, "ii_option5"),
211 6: self.wxstring(req, "ii_option6"),
212 7: self.wxstring(req, "ii_option7"),
213 9: self.wxstring(req, "ii_option9"),
214 }
216 def tr_severity(xstring_name: str, value: Optional[int]) -> str:
217 return tr_qa(
218 self.wxstring(req, xstring_name),
219 get_from_dict(severity_dict, value),
220 )
222 def tr_change(xstring_name: str, value: Optional[int]) -> str:
223 return tr_qa(
224 self.wxstring(req, xstring_name),
225 get_from_dict(change_dict, value),
226 )
228 return f"""
229 <div class="{CssClass.SUMMARY}">
230 <table class="{CssClass.SUMMARY}">
231 {self.get_is_complete_tr(req)}
232 </table>
233 </div>
234 <table class="{CssClass.TASKDETAIL}">
235 <tr>
236 <th width="70%">Question</th>
237 <th width="30%">Answer <sup>[1]</sup></th>
238 </tr>
239 {subheading_spanning_two_columns(self.wxstring(req, "i_title"))}
240 {tr_span_col(self.wxstring(req, "i_question"), cols=2)}
241 {tr_severity("q1", self.severity1)}
242 {tr_severity("q2", self.severity2)}
243 {tr_severity("q3", self.severity3)}
244 {tr_severity("q4", self.severity4)}
245 {tr_severity("q5", self.severity5)}
247 {subheading_spanning_two_columns(self.wxstring(req, "ii_title"))}
248 {tr_span_col(self.wxstring(req, "ii_question"), cols=2)}
249 {tr_change("q1", self.change1)}
250 {tr_change("q2", self.change2)}
251 {tr_change("q3", self.change3)}
252 {tr_change("q4", self.change4)}
253 {tr_change("q5", self.change5)}
254 </table>
255 <div class="{CssClass.FOOTNOTES}">
256 [1] All questions are scored 1–7, or 9 (not applicable, for
257 change questions).
258 {self.wxstring(req, "ii_postscript")}
259 </div>
261 """ # noqa