Coverage for tasks/apeq_cpft_perinatal.py: 49%

131 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/apeq_cpft_perinatal.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, Optional, Tuple, Type 

31 

32from cardinal_pythonlib.classes import classproperty 

33 

34from pyramid.renderers import render_to_response 

35from pyramid.response import Response 

36from sqlalchemy.sql.expression import and_, column, select 

37from sqlalchemy.sql.schema import Column 

38from sqlalchemy.sql.sqltypes import Integer, UnicodeText 

39 

40from camcops_server.cc_modules.cc_constants import CssClass 

41from camcops_server.cc_modules.cc_html import tr_qa 

42from camcops_server.cc_modules.cc_pyramid import ViewParam 

43from camcops_server.cc_modules.cc_report import ( 

44 DateTimeFilteredReportMixin, 

45 PercentageSummaryReportMixin, 

46 Report, 

47) 

48from camcops_server.cc_modules.cc_request import CamcopsRequest 

49from camcops_server.cc_modules.cc_sqla_coltypes import ( 

50 CamcopsColumn, 

51 ZERO_TO_FIVE_CHECKER, 

52 ZERO_TO_TWO_CHECKER, 

53) 

54from camcops_server.cc_modules.cc_task import Task 

55from camcops_server.cc_modules.cc_spreadsheet import SpreadsheetPage 

56 

57 

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

59# APEQCPFTPerinatal 

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

61 

62 

63class APEQCPFTPerinatal(Task): 

64 """ 

65 Server implementation of the APEQ-CPFT-Perinatal task. 

66 """ 

67 

68 __tablename__ = "apeq_cpft_perinatal" 

69 shortname = "APEQ-CPFT-Perinatal" 

70 

71 FIRST_MAIN_Q = 1 

72 LAST_MAIN_Q = 6 

73 FN_QPREFIX = "q" 

74 MAIN_EXPLANATION = " (0 no, 1 yes to some extent, 2 yes)" 

75 

76 q1 = CamcopsColumn( 

77 "q1", 

78 Integer, 

79 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

80 comment="Q1. Treated with respect/dignity" + MAIN_EXPLANATION, 

81 ) 

82 q2 = CamcopsColumn( 

83 "q2", 

84 Integer, 

85 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

86 comment="Q2. Felt listened to" + MAIN_EXPLANATION, 

87 ) 

88 q3 = CamcopsColumn( 

89 "q3", 

90 Integer, 

91 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

92 comment="Q3. Needs were understood" + MAIN_EXPLANATION, 

93 ) 

94 q4 = CamcopsColumn( 

95 "q4", 

96 Integer, 

97 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

98 comment="Q4. Given info about team" + MAIN_EXPLANATION, 

99 ) 

100 q5 = CamcopsColumn( 

101 "q5", 

102 Integer, 

103 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

104 comment="Q5. Family considered/included" + MAIN_EXPLANATION, 

105 ) 

106 q6 = CamcopsColumn( 

107 "q6", 

108 Integer, 

109 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

110 comment="Q6. Views on treatment taken into account" + MAIN_EXPLANATION, 

111 ) 

112 ff_rating = CamcopsColumn( 

113 "ff_rating", 

114 Integer, 

115 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

116 comment="How likely to recommend service to friends and family " 

117 "(0 don't know, 1 extremely unlikely, 2 unlikely, " 

118 "3 neither likely nor unlikely, 4 likely, 5 extremely likely)", 

119 ) 

120 ff_why = Column( 

121 "ff_why", 

122 UnicodeText, 

123 comment="Why was friends/family rating given as it was?", 

124 ) 

125 comments = Column("comments", UnicodeText, comment="General comments") 

126 

127 REQUIRED_FIELDS = ["q1", "q2", "q3", "q4", "q5", "q6", "ff_rating"] 

128 

129 @staticmethod 

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

131 _ = req.gettext 

132 return _( 

133 "Assessment Patient Experience Questionnaire for " 

134 "CPFT Perinatal Services" 

135 ) 

136 

137 def is_complete(self) -> bool: 

138 return self.all_fields_not_none(self.REQUIRED_FIELDS) 

139 

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

141 options_main = {None: "?"} # type: Dict[Optional[int], str] 

142 for o in range(0, 2 + 1): 

143 options_main[o] = self.wxstring(req, f"main_a{o}") 

144 options_ff = {None: "?"} # type: Dict[Optional[int], str] 

145 for o in range(0, 5 + 1): 

146 options_ff[o] = self.wxstring(req, f"ff_a{o}") 

147 

148 qlines = [] # type: List[str] 

149 for qnum in range(self.FIRST_MAIN_Q, self.LAST_MAIN_Q + 1): 

150 xstring_attr_name = f"q{qnum}" 

151 qlines.append( 

152 tr_qa( 

153 self.wxstring(req, xstring_attr_name), 

154 options_main.get(getattr(self, xstring_attr_name)), 

155 ) 

156 ) 

157 q_a = "".join(qlines) 

158 return f""" 

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

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

161 {self.get_is_complete_tr(req)} 

162 </table> 

163 </div> 

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

165 <tr> 

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

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

168 </tr> 

169 {q_a} 

170 {tr_qa(self.wxstring(req, "q_ff_rating"), 

171 options_ff.get(self.ff_rating))} 

172 {tr_qa(self.wxstring(req, "q_ff_why"), 

173 self.ff_why or "")} 

174 {tr_qa(self.wxstring(req, "q_comments"), 

175 self.comments or "")} 

176 </table> 

177 """ 

178 

179 def get_main_options(self, req: "CamcopsRequest") -> List[str]: 

180 options = [] 

181 

182 for n in range(0, 2 + 1): 

183 options.append(self.wxstring(req, f"main_a{n}")) 

184 

185 return options 

186 

187 def get_ff_options(self, req: "CamcopsRequest") -> List[str]: 

188 options = [] 

189 

190 for n in range(0, 5 + 1): 

191 options.append(self.wxstring(req, f"ff_a{n}")) 

192 

193 return options 

194 

195 

196# ============================================================================= 

197# Reports 

198# ============================================================================= 

199 

200 

201class APEQCPFTPerinatalReport( 

202 DateTimeFilteredReportMixin, Report, PercentageSummaryReportMixin 

203): 

204 """ 

205 Provides a summary of each question, x% of people said each response etc. 

206 Then a summary of the comments. 

207 """ 

208 

209 COL_Q = 0 

210 COL_TOTAL = 1 

211 COL_RESPONSE_START = 2 

212 

213 COL_FF_WHY = 1 

214 

215 def __init__(self, *args, **kwargs): 

216 super().__init__(*args, **kwargs) 

217 self.task = APEQCPFTPerinatal() # dummy task, never written to DB 

218 

219 @classproperty 

220 def task_class(self) -> Type["Task"]: 

221 return APEQCPFTPerinatal 

222 

223 # noinspection PyMethodParameters 

224 @classproperty 

225 def report_id(cls) -> str: 

226 return "apeq_cpft_perinatal" 

227 

228 @classmethod 

229 def title(cls, req: "CamcopsRequest") -> str: 

230 _ = req.gettext 

231 return _("APEQ CPFT Perinatal — Question summaries") 

232 

233 # noinspection PyMethodParameters 

234 @classproperty 

235 def superuser_only(cls) -> bool: 

236 return False 

237 

238 @classmethod 

239 def get_specific_http_query_keys(cls) -> List[str]: 

240 return [ViewParam.START_DATETIME, ViewParam.END_DATETIME] 

241 

242 def render_html(self, req: "CamcopsRequest") -> Response: 

243 cell_format = "{0:.1f}%" 

244 

245 return render_to_response( 

246 "apeq_cpft_perinatal_report.mako", 

247 dict( 

248 title=self.title(req), 

249 report_id=self.report_id, 

250 start_datetime=self.start_datetime, 

251 end_datetime=self.end_datetime, 

252 main_column_headings=self._get_main_column_headings(req), 

253 main_rows=self._get_main_rows(req, cell_format=cell_format), 

254 ff_column_headings=self._get_ff_column_headings(req), 

255 ff_rows=self._get_ff_rows(req, cell_format=cell_format), 

256 ff_why_rows=self._get_ff_why_rows(req), 

257 comments=self._get_comments(req), 

258 ), 

259 request=req, 

260 ) 

261 

262 def get_spreadsheet_pages( 

263 self, req: "CamcopsRequest" 

264 ) -> List[SpreadsheetPage]: 

265 _ = req.gettext 

266 

267 main_page = self.get_spreadsheet_page( 

268 name=_("Main questions"), 

269 column_names=self._get_main_column_headings(req), 

270 rows=self._get_main_rows(req), 

271 ) 

272 ff_page = self.get_spreadsheet_page( 

273 name=_("Friends and family question"), 

274 column_names=self._get_ff_column_headings(req), 

275 rows=self._get_ff_rows(req), 

276 ) 

277 ff_why_page = self.get_spreadsheet_page( 

278 name=_("Reasons given for the above responses"), 

279 column_names=[_("Response"), _("Reason")], 

280 rows=self._get_ff_why_rows(req), 

281 ) 

282 comments_page = self.get_spreadsheet_page( 

283 name=_("Comments"), 

284 column_names=[_("Comment")], 

285 rows=self._get_comment_rows(req), 

286 ) 

287 

288 return [main_page, ff_page, ff_why_page, comments_page] 

289 

290 def _get_main_column_headings(self, req: "CamcopsRequest") -> List[str]: 

291 _ = req.gettext 

292 names = [ 

293 _("Question"), 

294 _("Total responses"), 

295 ] + self.task.get_main_options(req) 

296 

297 return names 

298 

299 def _get_main_rows( 

300 self, req: "CamcopsRequest", cell_format: str = "{}" 

301 ) -> List[List[str]]: 

302 """ 

303 Percentage of people who answered x for each question 

304 """ 

305 column_dict = {} 

306 

307 qnums = range(self.task.FIRST_MAIN_Q, self.task.LAST_MAIN_Q + 1) 

308 

309 for qnum in qnums: 

310 column_name = f"{self.task.FN_QPREFIX}{qnum}" 

311 

312 column_dict[column_name] = self.task.wxstring(req, column_name) 

313 

314 return self.get_percentage_summaries( 

315 req, 

316 column_dict=column_dict, 

317 num_answers=3, 

318 cell_format=cell_format, 

319 ) 

320 

321 def _get_ff_column_headings(self, req: "CamcopsRequest") -> List[str]: 

322 _ = req.gettext 

323 return [ 

324 _("Question"), 

325 _("Total responses"), 

326 ] + self.task.get_ff_options(req) 

327 

328 def _get_ff_rows( 

329 self, req: "CamcopsRequest", cell_format: str = "{}" 

330 ) -> List[List[str]]: 

331 """ 

332 Percentage of people who answered x for the friends/family question 

333 """ 

334 return self.get_percentage_summaries( 

335 req, 

336 column_dict={ 

337 "ff_rating": self.task.wxstring( 

338 req, f"{self.task.FN_QPREFIX}_ff_rating" 

339 ) 

340 }, 

341 num_answers=6, 

342 cell_format=cell_format, 

343 ) 

344 

345 def _get_ff_why_rows(self, req: "CamcopsRequest") -> List[List[str]]: 

346 """ 

347 Reasons for giving a particular answer to the friends/family question 

348 """ 

349 

350 options = self.task.get_ff_options(req) 

351 

352 wheres = [ 

353 column("ff_rating").isnot(None), 

354 column("ff_why").isnot(None), 

355 ] 

356 

357 self.add_task_report_filters(wheres) 

358 

359 # noinspection PyUnresolvedReferences 

360 query = ( 

361 select([column("ff_rating"), column("ff_why")]) 

362 .select_from(self.task.__table__) 

363 .where(and_(*wheres)) 

364 .order_by("ff_why") 

365 ) 

366 

367 rows = [] 

368 

369 for result in req.dbsession.execute(query).fetchall(): 

370 rows.append([options[result[0]], result[1]]) 

371 

372 return rows 

373 

374 def _get_comment_rows(self, req: "CamcopsRequest") -> List[Tuple[str]]: 

375 """ 

376 A list of all the additional comments, as rows. 

377 """ 

378 

379 wheres = [column("comments").isnot(None)] 

380 

381 self.add_task_report_filters(wheres) 

382 

383 # noinspection PyUnresolvedReferences 

384 query = ( 

385 select([column("comments")]) 

386 .select_from(self.task.__table__) 

387 .where(and_(*wheres)) 

388 ) 

389 

390 comment_rows = [] 

391 

392 for result in req.dbsession.execute(query).fetchall(): 

393 comment_rows.append(result) 

394 

395 return comment_rows 

396 

397 def _get_comments(self, req: "CamcopsRequest") -> List[str]: 

398 """ 

399 A list of all the additional comments. 

400 """ 

401 return [x[0] for x in self._get_comment_rows(req)]