Coverage for tasks/cgisch.py: 50%

54 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_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 

28from typing import Any, List, Optional, Type 

29 

30from cardinal_pythonlib.stringfunc import strseq 

31 

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 

49 

50 

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

52# CGI-SCH 

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

54 

55QUESTION_FRAGMENTS = [ 

56 "positive", 

57 "negative", 

58 "depressive", 

59 "cognitive", 

60 "overall", 

61] 

62 

63 

64class 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