Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/cgi_task.py 

5 

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

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

12 CamCOPS is free software: you can redistribute it and/or modify 

13 it under the terms of the GNU General Public License as published by 

14 the Free Software Foundation, either version 3 of the License, or 

15 (at your option) any later version. 

16 

17 CamCOPS is distributed in the hope that it will be useful, 

18 but WITHOUT ANY WARRANTY; without even the implied warranty of 

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

22 You should have received a copy of the GNU General Public License 

23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

24 

25=============================================================================== 

26 

27""" 

28 

29from typing import Dict, List 

30 

31from sqlalchemy.sql.sqltypes import Integer 

32 

33from camcops_server.cc_modules.cc_constants import CssClass 

34from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

35from camcops_server.cc_modules.cc_html import ( 

36 answer, 

37 italic, 

38 tr, 

39 tr_qa, 

40) 

41from camcops_server.cc_modules.cc_request import CamcopsRequest 

42from camcops_server.cc_modules.cc_sqla_coltypes import ( 

43 CamcopsColumn, 

44 PermittedValueChecker, 

45) 

46from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

47from camcops_server.cc_modules.cc_task import ( 

48 get_from_dict, 

49 Task, 

50 TaskHasClinicianMixin, 

51 TaskHasPatientMixin, 

52) 

53from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

54 

55 

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

57# CGI 

58# ============================================================================= 

59 

60class Cgi(TaskHasPatientMixin, TaskHasClinicianMixin, Task): 

61 """ 

62 Server implementation of the CGI task. 

63 """ 

64 __tablename__ = "cgi" 

65 shortname = "CGI" 

66 provides_trackers = True 

67 

68 q1 = CamcopsColumn( 

69 "q1", Integer, 

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

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

72 ) 

73 q2 = CamcopsColumn( 

74 "q2", 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", Integer, 

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

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

82 ) 

83 q3s = CamcopsColumn( 

84 "q3s", Integer, 

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

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

87 ) 

88 q3 = CamcopsColumn( 

89 "q3", Integer, 

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

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

92 ) 

93 

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

95 MAX_SCORE = 30 

96 

97 @staticmethod 

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

99 _ = req.gettext 

100 return _("Clinical Global Impressions") 

101 

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

103 return [TrackerInfo( 

104 value=self.total_score(), 

105 plot_label="CGI total score", 

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

107 axis_min=-0.5, 

108 axis_max=self.MAX_SCORE + 0.5 

109 )] 

110 

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

112 if not self.is_complete(): 

113 return CTV_INCOMPLETE 

114 return [CtvInfo( 

115 content=f"CGI total score {self.total_score()}/{self.MAX_SCORE}" 

116 )] 

117 

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

119 return self.standard_task_summary_fields() + [ 

120 SummaryElement(name="total", 

121 coltype=Integer(), 

122 value=self.total_score()), 

123 ] 

124 

125 def is_complete(self) -> bool: 

126 if not (self.all_fields_not_none(self.TASK_FIELDS) and 

127 self.field_contents_valid()): 

128 return False 

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

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

131 # return False 

132 return True 

133 

134 def total_score(self) -> int: 

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

136 

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

138 q1_dict = { 

139 None: None, 

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

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

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

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

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

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

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

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

148 } 

149 q2_dict = { 

150 None: None, 

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

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

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

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

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

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

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

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

159 } 

160 q3t_dict = { 

161 None: None, 

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

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

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

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

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

167 } 

168 q3s_dict = { 

169 None: None, 

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

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

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

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

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

175 } 

176 

177 tr_total_score = tr( 

178 "Total score <sup>[1]</sup>", 

179 answer(self.total_score()) 

180 ) 

181 tr_q1 = tr_qa( 

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

183 get_from_dict(q1_dict, self.q1) 

184 ) 

185 tr_q2 = tr_qa( 

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

187 get_from_dict(q2_dict, self.q2) 

188 ) 

189 tr_q3t = tr_qa( 

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

191 get_from_dict(q3t_dict, self.q3t) 

192 ) 

193 tr_q3s = tr_qa( 

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

195 get_from_dict(q3s_dict, self.q3s) 

196 ) 

197 tr_q3 = tr( 

198 f""" 

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

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

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

202 </div> 

203 """, 

204 answer(self.q3, formatter_answer=italic) 

205 ) 

206 return f""" 

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

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

209 {self.get_is_complete_tr(req)} 

210 {tr_total_score} 

211 </table> 

212 </div> 

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

214 <tr> 

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

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

217 </tr> 

218 {tr_q1} 

219 {tr_q2} 

220 {tr_q3t} 

221 {tr_q3s} 

222 {tr_q3} 

223 </table> 

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

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

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

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

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

229 </div> 

230 """ 

231 

232 

233# ============================================================================= 

234# CGI-I 

235# ============================================================================= 

236 

237class CgiI(TaskHasPatientMixin, TaskHasClinicianMixin, Task): 

238 __tablename__ = "cgi_i" 

239 shortname = "CGI-I" 

240 extrastring_taskname = "cgi" # shares with CGI 

241 

242 q = CamcopsColumn( 

243 "q", Integer, 

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

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

246 ) 

247 

248 TASK_FIELDS = ["q"] 

249 

250 @staticmethod 

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

252 _ = req.gettext 

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

254 

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

256 if not self.is_complete(): 

257 return CTV_INCOMPLETE 

258 return [CtvInfo( 

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

260 )] 

261 

262 def is_complete(self) -> bool: 

263 return (self.all_fields_not_none(self.TASK_FIELDS) and 

264 self.field_contents_valid()) 

265 

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

267 qdict = self.get_q_dict(req) 

268 return get_from_dict(qdict, self.q) 

269 

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

271 return { 

272 None: None, 

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

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

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

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

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

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

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

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

281 } 

282 

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

284 return f""" 

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

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

287 {self.get_is_complete_tr(req)} 

288 </table> 

289 </div> 

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

291 <tr> 

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

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

294 </tr> 

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

296 </table> 

297 """