Coverage for tasks/gmcpq.py: 45%

105 statements  

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

1""" 

2camcops_server/tasks/gmcpq.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 Optional 

29 

30import cardinal_pythonlib.rnc_web as ws 

31from sqlalchemy.orm import Mapped, mapped_column 

32from sqlalchemy.sql.sqltypes import Integer, UnicodeText 

33 

34from camcops_server.cc_modules.cc_constants import ( 

35 CssClass, 

36 POSSIBLE_SEX_VALUES, 

37) 

38from camcops_server.cc_modules.cc_html import ( 

39 get_yes_no_none, 

40 subheading_spanning_two_columns, 

41 td, 

42 tr, 

43 tr_qa, 

44) 

45from camcops_server.cc_modules.cc_request import CamcopsRequest 

46from camcops_server.cc_modules.cc_sqla_coltypes import ( 

47 BIT_CHECKER, 

48 mapped_camcops_column, 

49 ONE_TO_FIVE_CHECKER, 

50 PermittedValueChecker, 

51 ZERO_TO_FIVE_CHECKER, 

52) 

53from camcops_server.cc_modules.cc_sqla_coltypes import SexColType 

54from camcops_server.cc_modules.cc_task import get_from_dict, Task 

55from camcops_server.cc_modules.cc_text import SS 

56 

57 

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

59# GMCPQ 

60# ============================================================================= 

61 

62 

63class GMCPQ(Task): 

64 """ 

65 Server implementation of the GMC-PQ task. 

66 """ 

67 

68 __tablename__ = "gmcpq" 

69 shortname = "GMC-PQ" 

70 

71 RATING_TEXT = " (1 poor - 5 very good, 0 does not apply)" 

72 AGREE_TEXT = " (1 strongly disagree - 5 strongly agree, 0 does not apply)" 

73 

74 doctor: Mapped[Optional[str]] = mapped_column( 

75 UnicodeText, comment="Doctor's name" 

76 ) 

77 q1: Mapped[Optional[int]] = mapped_camcops_column( 

78 permitted_value_checker=PermittedValueChecker(minimum=1, maximum=4), 

79 comment="Filling in questionnaire for... (1 yourself, " 

80 "2 child, 3 spouse/partner, 4 other relative/friend)", 

81 ) 

82 q2a: Mapped[Optional[int]] = mapped_camcops_column( 

83 permitted_value_checker=BIT_CHECKER, 

84 comment="Reason: advice? (0 no, 1 yes)", 

85 ) 

86 q2b: Mapped[Optional[int]] = mapped_camcops_column( 

87 permitted_value_checker=BIT_CHECKER, 

88 comment="Reason: one-off problem? (0 no, 1 yes)", 

89 ) 

90 q2c: Mapped[Optional[int]] = mapped_camcops_column( 

91 permitted_value_checker=BIT_CHECKER, 

92 comment="Reason: ongoing problem? (0 no, 1 yes)", 

93 ) 

94 q2d: Mapped[Optional[int]] = mapped_camcops_column( 

95 permitted_value_checker=BIT_CHECKER, 

96 comment="Reason: routine check? (0 no, 1 yes)", 

97 ) 

98 q2e: Mapped[Optional[int]] = mapped_camcops_column( 

99 permitted_value_checker=BIT_CHECKER, 

100 comment="Reason: treatment? (0 no, 1 yes)", 

101 ) 

102 q2f: Mapped[Optional[int]] = mapped_camcops_column( 

103 permitted_value_checker=BIT_CHECKER, 

104 comment="Reason: other? (0 no, 1 yes)", 

105 ) 

106 q2f_details: Mapped[Optional[str]] = mapped_column( 

107 UnicodeText, comment="Reason, other, details" 

108 ) 

109 q3: Mapped[Optional[int]] = mapped_camcops_column( 

110 permitted_value_checker=ONE_TO_FIVE_CHECKER, 

111 comment="How important to health/wellbeing was the reason " 

112 "(1 not very - 5 very)", 

113 ) 

114 q4a: Mapped[Optional[int]] = mapped_camcops_column( 

115 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

116 comment="How good: being polite" + RATING_TEXT, 

117 ) 

118 q4b: Mapped[Optional[int]] = mapped_camcops_column( 

119 "q4b", 

120 Integer, 

121 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

122 comment="How good: making you feel at ease" + RATING_TEXT, 

123 ) 

124 q4c: Mapped[Optional[int]] = mapped_camcops_column( 

125 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

126 comment="How good: listening" + RATING_TEXT, 

127 ) 

128 q4d: Mapped[Optional[int]] = mapped_camcops_column( 

129 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

130 comment="How good: assessing medical condition" + RATING_TEXT, 

131 ) 

132 q4e: Mapped[Optional[int]] = mapped_camcops_column( 

133 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

134 comment="How good: explaining" + RATING_TEXT, 

135 ) 

136 q4f: Mapped[Optional[int]] = mapped_camcops_column( 

137 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

138 comment="How good: involving you in decisions" + RATING_TEXT, 

139 ) 

140 q4g: Mapped[Optional[int]] = mapped_camcops_column( 

141 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

142 comment="How good: providing/arranging treatment" + RATING_TEXT, 

143 ) 

144 q5a: Mapped[Optional[int]] = mapped_camcops_column( 

145 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

146 comment="Agree/disagree: will keep info confidential" + AGREE_TEXT, 

147 ) 

148 q5b: Mapped[Optional[int]] = mapped_camcops_column( 

149 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

150 comment="Agree/disagree: honest/trustworthy" + AGREE_TEXT, 

151 ) 

152 q6: Mapped[Optional[int]] = mapped_camcops_column( 

153 permitted_value_checker=BIT_CHECKER, 

154 comment="Confident in doctor's ability to provide care (0 no, 1 yes)", 

155 ) 

156 q7: Mapped[Optional[int]] = mapped_camcops_column( 

157 permitted_value_checker=BIT_CHECKER, 

158 comment="Would be completely happy to see this doctor again " 

159 "(0 no, 1 yes)", 

160 ) 

161 q8: Mapped[Optional[int]] = mapped_camcops_column( 

162 permitted_value_checker=BIT_CHECKER, 

163 comment="Was this visit with your usual doctor (0 no, 1 yes)", 

164 ) 

165 q9: Mapped[Optional[str]] = mapped_column( 

166 UnicodeText, comment="Other comments" 

167 ) 

168 q10: Mapped[Optional[str]] = mapped_camcops_column( 

169 SexColType, 

170 permitted_value_checker=PermittedValueChecker( 

171 permitted_values=POSSIBLE_SEX_VALUES 

172 ), 

173 comment="Sex of rater (M, F, X)", 

174 ) 

175 q11: Mapped[Optional[int]] = mapped_camcops_column( 

176 permitted_value_checker=ONE_TO_FIVE_CHECKER, 

177 comment="Age (1 = under 15, 2 = 15-20, 3 = 21-40, " 

178 "4 = 40-60, 5 = 60 or over", # yes, I know it's daft 

179 ) 

180 q12: Mapped[Optional[int]] = mapped_camcops_column( 

181 permitted_value_checker=PermittedValueChecker(minimum=1, maximum=16), 

182 comment="Ethnicity (1 = White British, 2 = White Irish, " 

183 "3 = White other, 4 = Mixed W/B Caribbean, " 

184 "5 = Mixed W/B African, 6 = Mixed W/Asian, 7 = Mixed other, " 

185 "8 = Asian/Asian British - Indian, 9 = A/AB - Pakistani, " 

186 "10 = A/AB - Bangladeshi, 11 = A/AB - other, " 

187 "12 = Black/Black British - Caribbean, 13 = B/BB - African, " 

188 "14 = B/BB - other, 15 = Chinese, 16 = other)", 

189 ) 

190 q12_details: Mapped[Optional[str]] = mapped_column( 

191 UnicodeText, comment="Ethnic group, other, details" 

192 ) 

193 

194 @staticmethod 

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

196 _ = req.gettext 

197 return _("GMC Patient Questionnaire") 

198 

199 def is_complete(self) -> bool: 

200 return ( 

201 self.is_field_not_none("q1") 

202 and self.is_field_not_none("q3") 

203 and self.is_field_not_none("q4a") 

204 and self.is_field_not_none("q4b") 

205 and self.is_field_not_none("q4c") 

206 and self.is_field_not_none("q4d") 

207 and self.is_field_not_none("q4e") 

208 and self.is_field_not_none("q4f") 

209 and self.is_field_not_none("q4g") 

210 and self.is_field_not_none("q5a") 

211 and self.is_field_not_none("q5b") 

212 and self.is_field_not_none("q6") 

213 and self.is_field_not_none("q7") 

214 and self.is_field_not_none("q8") 

215 and self.field_contents_valid() 

216 ) 

217 

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

219 dict_q1: dict[Optional[int], Optional[str]] = {None: None} 

220 dict_q3: dict[Optional[int], Optional[str]] = {None: None} 

221 dict_q4: dict[Optional[int], Optional[str]] = {None: None} 

222 dict_q5: dict[Optional[int], Optional[str]] = {None: None} 

223 dict_q11: dict[Optional[int], Optional[str]] = {None: None} 

224 dict_q12: dict[Optional[int], Optional[str]] = {None: None} 

225 for option in range(1, 5): 

226 dict_q1[option] = self.wxstring(req, "q1_option" + str(option)) 

227 for option in range(1, 6): 

228 dict_q3[option] = self.wxstring(req, "q3_option" + str(option)) 

229 dict_q11[option] = self.wxstring(req, "q11_option" + str(option)) 

230 for option in range(0, 6): 

231 prefix = str(option) + " – " if option > 0 else "" 

232 dict_q4[option] = prefix + self.wxstring( 

233 req, "q4_option" + str(option) 

234 ) 

235 dict_q5[option] = prefix + self.wxstring( 

236 req, "q5_option" + str(option) 

237 ) 

238 for option in range(1, 17): 

239 dict_q12[option] = self.wxstring( 

240 req, "ethnicity_option" + str(option) 

241 ) 

242 h = f""" 

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

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

245 {self.get_is_complete_tr(req)} 

246 </table> 

247 </div> 

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

249 <tr> 

250 <th width="60%">Question</th> 

251 <th width="40%">Answer</th> 

252 </tr> 

253 """ 

254 ell = "&hellip; " # horizontal ellipsis 

255 sep_row = subheading_spanning_two_columns("<br>") 

256 blank_cell = td("", td_class=CssClass.SUBHEADING) 

257 h += tr_qa(self.wxstring(req, "q_doctor"), ws.webify(self.doctor)) 

258 h += sep_row 

259 h += tr_qa(self.wxstring(req, "q1"), get_from_dict(dict_q1, self.q1)) 

260 h += tr(td(self.wxstring(req, "q2")), blank_cell, literal=True) 

261 h += tr_qa( 

262 ell + self.wxstring(req, "q2_a"), 

263 get_yes_no_none(req, self.q2a), 

264 default="", 

265 ) 

266 h += tr_qa( 

267 ell + self.wxstring(req, "q2_b"), 

268 get_yes_no_none(req, self.q2b), 

269 default="", 

270 ) 

271 h += tr_qa( 

272 ell + self.wxstring(req, "q2_c"), 

273 get_yes_no_none(req, self.q2c), 

274 default="", 

275 ) 

276 h += tr_qa( 

277 ell + self.wxstring(req, "q2_d"), 

278 get_yes_no_none(req, self.q2d), 

279 default="", 

280 ) 

281 h += tr_qa( 

282 ell + self.wxstring(req, "q2_e"), 

283 get_yes_no_none(req, self.q2e), 

284 default="", 

285 ) 

286 h += tr_qa( 

287 ell + self.wxstring(req, "q2_f"), 

288 get_yes_no_none(req, self.q2f), 

289 default="", 

290 ) 

291 h += tr_qa( 

292 ell + ell + self.wxstring(req, "q2f_s"), 

293 ws.webify(self.q2f_details), 

294 ) 

295 h += tr_qa(self.wxstring(req, "q3"), get_from_dict(dict_q3, self.q3)) 

296 h += tr(td(self.wxstring(req, "q4")), blank_cell, literal=True) 

297 h += tr_qa( 

298 ell + self.wxstring(req, "q4_a"), get_from_dict(dict_q4, self.q4a) 

299 ) 

300 h += tr_qa( 

301 ell + self.wxstring(req, "q4_b"), get_from_dict(dict_q4, self.q4b) 

302 ) 

303 h += tr_qa( 

304 ell + self.wxstring(req, "q4_c"), get_from_dict(dict_q4, self.q4c) 

305 ) 

306 h += tr_qa( 

307 ell + self.wxstring(req, "q4_d"), get_from_dict(dict_q4, self.q4d) 

308 ) 

309 h += tr_qa( 

310 ell + self.wxstring(req, "q4_e"), get_from_dict(dict_q4, self.q4e) 

311 ) 

312 h += tr_qa( 

313 ell + self.wxstring(req, "q4_f"), get_from_dict(dict_q4, self.q4f) 

314 ) 

315 h += tr_qa( 

316 ell + self.wxstring(req, "q4_g"), get_from_dict(dict_q4, self.q4g) 

317 ) 

318 h += tr(td(self.wxstring(req, "q5")), blank_cell, literal=True) 

319 h += tr_qa( 

320 ell + self.wxstring(req, "q5_a"), get_from_dict(dict_q5, self.q5a) 

321 ) 

322 h += tr_qa( 

323 ell + self.wxstring(req, "q5_b"), get_from_dict(dict_q5, self.q5b) 

324 ) 

325 h += tr_qa(self.wxstring(req, "q6"), get_yes_no_none(req, self.q6)) 

326 h += tr_qa(self.wxstring(req, "q7"), get_yes_no_none(req, self.q7)) 

327 h += tr_qa(self.wxstring(req, "q8"), get_yes_no_none(req, self.q8)) 

328 h += tr_qa(self.wxstring(req, "q9_s"), ws.webify(self.q9)) 

329 h += sep_row 

330 h += tr_qa(req.sstring(SS.SEX), ws.webify(self.q10)) 

331 h += tr_qa( 

332 self.wxstring(req, "q11"), get_from_dict(dict_q11, self.q11) 

333 ) 

334 h += tr_qa( 

335 self.wxstring(req, "q12"), get_from_dict(dict_q12, self.q12) 

336 ) 

337 h += tr_qa( 

338 ell + self.wxstring(req, "ethnicity_other_s"), 

339 ws.webify(self.q12_details), 

340 ) 

341 h += """ 

342 </table> 

343 """ 

344 return h