Coverage for tasks/cgi_task.py: 58%

76 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/cgi_task.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 Dict, List 

31 

32from sqlalchemy.sql.sqltypes import Integer 

33 

34from camcops_server.cc_modules.cc_constants import CssClass 

35from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

36from camcops_server.cc_modules.cc_html import answer, italic, tr, tr_qa 

37from camcops_server.cc_modules.cc_request import CamcopsRequest 

38from camcops_server.cc_modules.cc_sqla_coltypes import ( 

39 CamcopsColumn, 

40 PermittedValueChecker, 

41) 

42from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

43from camcops_server.cc_modules.cc_task import ( 

44 get_from_dict, 

45 Task, 

46 TaskHasClinicianMixin, 

47 TaskHasPatientMixin, 

48) 

49from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

50 

51 

52# ============================================================================= 

53# CGI 

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

55 

56 

57class Cgi(TaskHasPatientMixin, TaskHasClinicianMixin, Task): 

58 """ 

59 Server implementation of the CGI task. 

60 """ 

61 

62 __tablename__ = "cgi" 

63 shortname = "CGI" 

64 provides_trackers = True 

65 

66 q1 = CamcopsColumn( 

67 "q1", 

68 Integer, 

69 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=7), 

70 comment="Q1. Severity (1-7, higher worse, 0 not assessed)", 

71 ) 

72 q2 = CamcopsColumn( 

73 "q2", 

74 Integer, 

75 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=7), 

76 comment="Q2. Global improvement (1-7, higher worse, 0 not assessed)", 

77 ) 

78 q3t = CamcopsColumn( 

79 "q3t", 

80 Integer, 

81 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=4), 

82 comment="Q3T. Therapeutic effects (1-4, higher worse, 0 not assessed)", 

83 ) 

84 q3s = CamcopsColumn( 

85 "q3s", 

86 Integer, 

87 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=4), 

88 comment="Q3S. Side effects (1-4, higher worse, 0 not assessed)", 

89 ) 

90 q3 = CamcopsColumn( 

91 "q3", 

92 Integer, 

93 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=16), 

94 comment="Q3 (calculated). Efficacy index [(Q3T - 1) * 4 + Q3S].", 

95 ) 

96 

97 TASK_FIELDS = ["q1", "q2", "q3t", "q3s", "q3"] 

98 MAX_SCORE = 30 

99 

100 @staticmethod 

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

102 _ = req.gettext 

103 return _("Clinical Global Impressions") 

104 

105 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]: 

106 return [ 

107 TrackerInfo( 

108 value=self.total_score(), 

109 plot_label="CGI total score", 

110 axis_label=f"Total score (out of {self.MAX_SCORE})", 

111 axis_min=-0.5, 

112 axis_max=self.MAX_SCORE + 0.5, 

113 ) 

114 ] 

115 

116 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: 

117 if not self.is_complete(): 

118 return CTV_INCOMPLETE 

119 return [ 

120 CtvInfo( 

121 content=( 

122 f"CGI total score {self.total_score()}/{self.MAX_SCORE}" 

123 ) 

124 ) 

125 ] 

126 

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

128 return self.standard_task_summary_fields() + [ 

129 SummaryElement( 

130 name="total", coltype=Integer(), value=self.total_score() 

131 ) 

132 ] 

133 

134 def is_complete(self) -> bool: 

135 if not ( 

136 self.all_fields_not_none(self.TASK_FIELDS) 

137 and self.field_contents_valid() 

138 ): 

139 return False 

140 # Requirement for everything to be non-zero removed in v2.0.0 

141 # if self.q1 == 0 or self.q2 == 0 or self.q3t == 0 or self.q3s == 0: 

142 # return False 

143 return True 

144 

145 def total_score(self) -> int: 

146 return self.sum_fields(["q1", "q2", "q3"]) 

147 

148 def get_task_html(self, req: CamcopsRequest) -> str: 

149 q1_dict = { 

150 None: None, 

151 0: self.wxstring(req, "q1_option0"), 

152 1: self.wxstring(req, "q1_option1"), 

153 2: self.wxstring(req, "q1_option2"), 

154 3: self.wxstring(req, "q1_option3"), 

155 4: self.wxstring(req, "q1_option4"), 

156 5: self.wxstring(req, "q1_option5"), 

157 6: self.wxstring(req, "q1_option6"), 

158 7: self.wxstring(req, "q1_option7"), 

159 } 

160 q2_dict = { 

161 None: None, 

162 0: self.wxstring(req, "q2_option0"), 

163 1: self.wxstring(req, "q2_option1"), 

164 2: self.wxstring(req, "q2_option2"), 

165 3: self.wxstring(req, "q2_option3"), 

166 4: self.wxstring(req, "q2_option4"), 

167 5: self.wxstring(req, "q2_option5"), 

168 6: self.wxstring(req, "q2_option6"), 

169 7: self.wxstring(req, "q2_option7"), 

170 } 

171 q3t_dict = { 

172 None: None, 

173 0: self.wxstring(req, "q3t_option0"), 

174 1: self.wxstring(req, "q3t_option1"), 

175 2: self.wxstring(req, "q3t_option2"), 

176 3: self.wxstring(req, "q3t_option3"), 

177 4: self.wxstring(req, "q3t_option4"), 

178 } 

179 q3s_dict = { 

180 None: None, 

181 0: self.wxstring(req, "q3s_option0"), 

182 1: self.wxstring(req, "q3s_option1"), 

183 2: self.wxstring(req, "q3s_option2"), 

184 3: self.wxstring(req, "q3s_option3"), 

185 4: self.wxstring(req, "q3s_option4"), 

186 } 

187 

188 tr_total_score = tr( 

189 "Total score <sup>[1]</sup>", answer(self.total_score()) 

190 ) 

191 tr_q1 = tr_qa( 

192 self.wxstring(req, "q1_s") + " <sup>[2]</sup>", 

193 get_from_dict(q1_dict, self.q1), 

194 ) 

195 tr_q2 = tr_qa( 

196 self.wxstring(req, "q2_s") + " <sup>[2]</sup>", 

197 get_from_dict(q2_dict, self.q2), 

198 ) 

199 tr_q3t = tr_qa( 

200 self.wxstring(req, "q3t_s") + " <sup>[3]</sup>", 

201 get_from_dict(q3t_dict, self.q3t), 

202 ) 

203 tr_q3s = tr_qa( 

204 self.wxstring(req, "q3s_s") + " <sup>[3]</sup>", 

205 get_from_dict(q3s_dict, self.q3s), 

206 ) 

207 tr_q3 = tr( 

208 f""" 

209 {self.wxstring(req, "q3_s")} <sup>[4]</sup> 

210 <div class="{CssClass.SMALLPRINT}"> 

211 [(Q3T – 1) × 4 + Q3S] 

212 </div> 

213 """, 

214 answer(self.q3, formatter_answer=italic), 

215 ) 

216 return f""" 

217 <div class="{CssClass.SUMMARY}"> 

218 <table class="{CssClass.SUMMARY}"> 

219 {self.get_is_complete_tr(req)} 

220 {tr_total_score} 

221 </table> 

222 </div> 

223 <table class="{CssClass.TASKDETAIL}"> 

224 <tr> 

225 <th width="30%">Question</th> 

226 <th width="70%">Answer</th> 

227 </tr> 

228 {tr_q1} 

229 {tr_q2} 

230 {tr_q3t} 

231 {tr_q3s} 

232 {tr_q3} 

233 </table> 

234 <div class="{CssClass.FOOTNOTES}"> 

235 [1] Total score: Q1 + Q2 + Q3. Range 3–30 when complete. 

236 [2] Questions 1 and 2 are scored 1–7 (0 for not assessed). 

237 [3] Questions 3T and 3S are scored 1–4 (0 for not assessed). 

238 [4] Q3 is scored 1–16 if Q3T/Q3S complete. 

239 </div> 

240 """ 

241 

242 

243# ============================================================================= 

244# CGI-I 

245# ============================================================================= 

246 

247 

248class CgiI(TaskHasPatientMixin, TaskHasClinicianMixin, Task): 

249 __tablename__ = "cgi_i" 

250 shortname = "CGI-I" 

251 extrastring_taskname = "cgi" # shares with CGI 

252 info_filename_stem = "cgi" 

253 

254 q = CamcopsColumn( 

255 "q", 

256 Integer, 

257 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=7), 

258 comment="Global improvement (1-7, higher worse)", 

259 ) 

260 

261 TASK_FIELDS = ["q"] 

262 

263 @staticmethod 

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

265 _ = req.gettext 

266 return _("Clinical Global Impressions – Improvement") 

267 

268 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: 

269 if not self.is_complete(): 

270 return CTV_INCOMPLETE 

271 return [ 

272 CtvInfo( 

273 content="CGI-I rating: {}".format(self.get_rating_text(req)) 

274 ) 

275 ] 

276 

277 def is_complete(self) -> bool: 

278 return ( 

279 self.all_fields_not_none(self.TASK_FIELDS) 

280 and self.field_contents_valid() 

281 ) 

282 

283 def get_rating_text(self, req: CamcopsRequest) -> str: 

284 qdict = self.get_q_dict(req) 

285 return get_from_dict(qdict, self.q) 

286 

287 def get_q_dict(self, req: CamcopsRequest) -> Dict: 

288 return { 

289 None: None, 

290 0: self.wxstring(req, "q2_option0"), 

291 1: self.wxstring(req, "q2_option1"), 

292 2: self.wxstring(req, "q2_option2"), 

293 3: self.wxstring(req, "q2_option3"), 

294 4: self.wxstring(req, "q2_option4"), 

295 5: self.wxstring(req, "q2_option5"), 

296 6: self.wxstring(req, "q2_option6"), 

297 7: self.wxstring(req, "q2_option7"), 

298 } 

299 

300 def get_task_html(self, req: CamcopsRequest) -> str: 

301 return f""" 

302 <div class="{CssClass.SUMMARY}"> 

303 <table class="{CssClass.SUMMARY}"> 

304 {self.get_is_complete_tr(req)} 

305 </table> 

306 </div> 

307 <table class="{CssClass.TASKDETAIL}"> 

308 <tr> 

309 <th width="50%">Question</th> 

310 <th width="50%">Answer</th> 

311 </tr> 

312 {tr_qa(self.wxstring(req, "i_q"), self.get_rating_text(req))} 

313 </table> 

314 """