Coverage for tasks/cgisch.py: 64%

47 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/cgisch.py 

5 

6=============================================================================== 

7 

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

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

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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

25 

26=============================================================================== 

27 

28""" 

29 

30from typing import Any, Dict, List, Optional, Tuple, Type 

31 

32from cardinal_pythonlib.stringfunc import strseq 

33from sqlalchemy.ext.declarative import DeclarativeMeta 

34 

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 

52 

53 

54# ============================================================================= 

55# CGI-SCH 

56# ============================================================================= 

57 

58QUESTION_FRAGMENTS = [ 

59 "positive", 

60 "negative", 

61 "depressive", 

62 "cognitive", 

63 "overall", 

64] 

65 

66 

67class CgiSchMetaclass(DeclarativeMeta): 

68 """ 

69 Metaclass for :class:`CgiSch`. 

70 """ 

71 

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) 

99 

100 

101class CgiSch( 

102 TaskHasPatientMixin, TaskHasClinicianMixin, Task, metaclass=CgiSchMetaclass 

103): 

104 """ 

105 Server implementation of the CGI-SCH task. 

106 """ 

107 

108 __tablename__ = "cgisch" 

109 shortname = "CGI-SCH" 

110 provides_trackers = True 

111 

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

113 

114 @staticmethod 

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

116 _ = req.gettext 

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

118 

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 ] 

160 

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 ] 

181 

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

183 # pylint: disable=unused-argument 

184 return self.standard_task_summary_fields() 

185 

186 def is_complete(self) -> bool: 

187 return ( 

188 self.all_fields_not_none(self.TASK_FIELDS) 

189 and self.field_contents_valid() 

190 ) 

191 

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 } 

215 

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 ) 

221 

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 ) 

227 

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)} 

246 

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> 

260 

261 """ # noqa