Coverage for tasks/cgisch.py: 50%

1"""

2 camcops_server/tasks/cgisch.py

3

4===============================================================================

5

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

8

9 This file is part of CamCOPS.

10

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.

15

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.

20

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/>.

23

24===============================================================================

25

26"""

27

28 from typing import Any, List, Optional, Type

29

30 from cardinal_pythonlib.stringfunc import strseq

31

32 from camcops_server.cc_modules.cc_constants import CssClass

33 from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo

34 from camcops_server.cc_modules.cc_db import add_multiple_columns

35 from camcops_server.cc_modules.cc_html import (

36 subheading_spanning_two_columns,

37 tr_qa,

38 tr_span_col,

39)

40 from camcops_server.cc_modules.cc_request import CamcopsRequest

41 from camcops_server.cc_modules.cc_summaryelement import SummaryElement

42 from camcops_server.cc_modules.cc_task import (

43 get_from_dict,

44 Task,

45 TaskHasClinicianMixin,

46 TaskHasPatientMixin,

47)

48 from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo

49

50

51# =============================================================================

52# CGI-SCH

53# =============================================================================

54

55 QUESTION_FRAGMENTS = [

56 "positive",

57 "negative",

58 "depressive",

59 "cognitive",

60 "overall",

61]

62

63

64 class CgiSch( # type: ignore[misc]

65 TaskHasPatientMixin,

66 TaskHasClinicianMixin,

67 Task,

68):

69 """

70 Server implementation of the CGI-SCH task.

71 """

72

73 __tablename__ = "cgisch"

74 shortname = "CGI-SCH"

75 provides_trackers = True

76

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 )

98

99 TASK_FIELDS = strseq("severity", 1, 5) + strseq("change", 1, 5)

100

101 @staticmethod

102 def longname(req: "CamcopsRequest") -> str:

103 _ = req.gettext

104 return _("Clinical Global Impression – Schizophrenia")

105

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 ]

147

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 ]

168

169 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:

170 # pylint: disable=unused-argument

171 return self.standard_task_summary_fields()

172

173 def is_complete(self) -> bool:

174 return (

175 self.all_fields_not_none(self.TASK_FIELDS)

176 and self.field_contents_valid()

177 )

178

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 }

202

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 )

208

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 )

214

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]

220

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

226

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}

245

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>

259

260 """ # noqa: E501